summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Lobdell <slobdell@google.com>2020-10-15 11:45:45 -0700
committerDaniel Norman <danielnorman@google.com>2020-10-27 13:09:24 -0700
commit449f659b21d039b8692602294b040038ac1e6ccb (patch)
tree8d7f4ce2697ad1b591d2f953e69656bc9b0a4080
parent5cced294627474a5a42b42e442a50690d5c76661 (diff)
parentfb4ede1992df0344082bbdac60bcbc2a3a0a65ba (diff)
Merge SP1A.201015.001
Change-Id: Ie33f12a2f4c73443640c28ad128be96b3533fd8c
-rwxr-xr-xAndroid.bp22
-rw-r--r--ApiDocs.bp12
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--StubLibraries.bp97
-rw-r--r--apct-tests/perftests/autofill/Android.bp1
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java76
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestHelper.java81
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.java196
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java86
-rw-r--r--apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java68
-rw-r--r--apct-tests/perftests/core/Android.bp23
-rw-r--r--apct-tests/perftests/core/AndroidManifest.xml12
-rw-r--r--apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java89
-rw-r--r--apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java26
-rw-r--r--apct-tests/perftests/core/src/android/os/SomeProvider.java73
-rw-r--r--apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java16
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java2
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java3
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java2
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java2
-rw-r--r--apex/appsearch/framework/Android.bp57
-rw-r--r--apex/appsearch/framework/api/current.txt1
-rw-r--r--apex/appsearch/framework/api/module-lib-current.txt1
-rw-r--r--apex/appsearch/framework/api/module-lib-removed.txt1
-rw-r--r--apex/appsearch/framework/api/removed.txt1
-rw-r--r--apex/appsearch/framework/api/system-current.txt9
-rw-r--r--apex/appsearch/framework/api/system-removed.txt1
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java6
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java25
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java220
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl16
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java237
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java (renamed from apex/appsearch/service/java/com/android/server/appsearch/AppSearchException.java)23
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java (renamed from apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java)6
-rw-r--r--apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java (renamed from apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java)8
-rw-r--r--apex/appsearch/service/Android.bp8
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java132
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java (renamed from apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java)33
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java865
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverter.java127
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SearchSpecToProtoConverter.java111
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java332
-rw-r--r--apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java216
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java36
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java225
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java483
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java17
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java45
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java27
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java16
-rw-r--r--apex/media/OWNERS3
-rw-r--r--apex/media/aidl/Android.bp10
-rw-r--r--apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl19
-rw-r--r--apex/media/framework/Android.bp12
-rw-r--r--apex/media/framework/api/module-lib-current.txt14
-rw-r--r--apex/media/framework/java/android/media/BaseMediaParceledListSlice.java215
-rw-r--r--apex/media/framework/java/android/media/MediaParceledListSlice.java103
-rw-r--r--apex/statsd/framework/java/android/app/StatsManager.java2
-rw-r--r--api/Android.bp183
-rw-r--r--api/current.txt419
-rwxr-xr-xapi/gen_combined_removed_dex.sh11
-rw-r--r--api/module-lib-current.txt40
-rw-r--r--api/system-current.txt269
-rw-r--r--api/system-lint-baseline.txt5
-rw-r--r--api/test-current.txt439
-rw-r--r--api/test-lint-baseline.txt12
-rw-r--r--cmds/bootanimation/BootAnimation.cpp110
-rw-r--r--cmds/bootanimation/BootAnimation.h12
-rw-r--r--cmds/bootanimation/FORMAT.md8
-rw-r--r--cmds/statsd/src/atoms.proto185
-rw-r--r--cmds/statsd/src/condition/ConditionTimer.h48
-rw-r--r--cmds/statsd/src/condition/ConditionTracker.h6
-rw-r--r--cmds/statsd/src/main.cpp2
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.cpp42
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.h16
-rw-r--r--cmds/statsd/src/metrics/EventMetricProducer.cpp47
-rw-r--r--cmds/statsd/src/metrics/EventMetricProducer.h16
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.cpp58
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.h24
-rw-r--r--cmds/statsd/src/metrics/MetricProducer.cpp32
-rw-r--r--cmds/statsd/src/metrics/MetricProducer.h53
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.cpp21
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp115
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.h20
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp441
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/config_update_utils.h81
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp641
-rw-r--r--cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h103
-rw-r--r--cmds/statsd/tests/condition/ConditionTimer_test.cpp6
-rw-r--r--cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp2053
-rw-r--r--cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp1637
-rw-r--r--core/java/android/accessibilityservice/AccessibilityGestureEvent.java12
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java9
-rw-r--r--core/java/android/app/ActivityManager.java52
-rw-r--r--core/java/android/app/ActivityManagerInternal.java32
-rw-r--r--core/java/android/app/ActivityOptions.java10
-rw-r--r--core/java/android/app/ActivityTaskManager.java38
-rw-r--r--core/java/android/app/ActivityThread.java586
-rw-r--r--core/java/android/app/AppOpsManager.java377
-rw-r--r--core/java/android/app/ApplicationPackageManager.java42
-rw-r--r--core/java/android/app/ClientTransactionHandler.java54
-rw-r--r--core/java/android/app/ContextImpl.java20
-rw-r--r--core/java/android/app/HomeVisibilityListener.java (renamed from core/java/android/app/HomeVisibilityObserver.java)63
-rw-r--r--core/java/android/app/IActivityManager.aidl33
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl32
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl2
-rw-r--r--core/java/android/app/LocalActivityManager.java43
-rw-r--r--core/java/android/app/Notification.java3
-rw-r--r--core/java/android/app/NotificationHistory.java7
-rw-r--r--core/java/android/app/NotificationManager.java31
-rw-r--r--core/java/android/app/PendingIntent.java57
-rw-r--r--core/java/android/app/PictureInPictureParams.java10
-rw-r--r--core/java/android/app/ResourcesManager.java15
-rw-r--r--core/java/android/app/StatusBarManager.java2
-rw-r--r--core/java/android/app/SystemServiceRegistry.java30
-rw-r--r--core/java/android/app/UiAutomation.java46
-rw-r--r--core/java/android/app/UiAutomationConnection.java27
-rw-r--r--core/java/android/app/WindowConfiguration.java9
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java8
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java18
-rw-r--r--core/java/android/app/admin/DevicePolicyManagerInternal.java18
-rw-r--r--core/java/android/app/admin/FreezePeriod.java20
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/backup/BackupAgent.java32
-rw-r--r--core/java/android/app/people/IPeopleManager.aidl6
-rw-r--r--core/java/android/app/role/RoleControllerManager.java4
-rw-r--r--core/java/android/app/role/RoleManager.java8
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java12
-rw-r--r--core/java/android/app/servertransaction/ActivityLifecycleItem.java2
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java30
-rw-r--r--core/java/android/app/servertransaction/ActivityResultItem.java11
-rw-r--r--core/java/android/app/servertransaction/ActivityTransactionItem.java69
-rw-r--r--core/java/android/app/servertransaction/DestroyActivityItem.java8
-rw-r--r--core/java/android/app/servertransaction/EnterPipRequestedItem.java8
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java3
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java13
-rw-r--r--core/java/android/app/servertransaction/NewIntentItem.java11
-rw-r--r--core/java/android/app/servertransaction/PauseActivityItem.java9
-rw-r--r--core/java/android/app/servertransaction/ResumeActivityItem.java8
-rw-r--r--core/java/android/app/servertransaction/StartActivityItem.java9
-rw-r--r--core/java/android/app/servertransaction/StopActivityItem.java8
-rw-r--r--core/java/android/app/servertransaction/TopResumedActivityChangeItem.java26
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java12
-rw-r--r--core/java/android/app/time/ITimeZoneDetectorListener.aidl22
-rw-r--r--core/java/android/app/time/TEST_MAPPING12
-rw-r--r--core/java/android/app/time/TimeManager.java217
-rw-r--r--core/java/android/app/time/TimeZoneCapabilities.aidl (renamed from core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl)2
-rw-r--r--core/java/android/app/time/TimeZoneCapabilities.java (renamed from core/java/android/app/timezonedetector/TimeZoneCapabilities.java)177
-rw-r--r--core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl19
-rw-r--r--core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java120
-rw-r--r--core/java/android/app/time/TimeZoneConfiguration.aidl (renamed from core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl)2
-rw-r--r--core/java/android/app/time/TimeZoneConfiguration.java (renamed from core/java/android/app/timezonedetector/TimeZoneConfiguration.java)126
-rw-r--r--core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl12
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java64
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java97
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java4
-rw-r--r--core/java/android/bluetooth/BluetoothCodecConfig.java37
-rw-r--r--core/java/android/bluetooth/BluetoothCodecStatus.java5
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java25
-rw-r--r--core/java/android/bluetooth/BluetoothGattCallback.java14
-rw-r--r--core/java/android/bluetooth/BluetoothHidDevice.java56
-rw-r--r--core/java/android/bluetooth/le/AdvertiseData.java54
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeAdvertiser.java27
-rw-r--r--core/java/android/content/ContentProvider.java1
-rw-r--r--core/java/android/content/Context.java48
-rw-r--r--core/java/android/content/Intent.java64
-rw-r--r--core/java/android/content/PermissionChecker.java22
-rw-r--r--core/java/android/content/om/TEST_MAPPING14
-rw-r--r--core/java/android/content/pm/ActivityInfo.java38
-rw-r--r--core/java/android/content/pm/ApkChecksum.java78
-rw-r--r--core/java/android/content/pm/Checksum.java105
-rw-r--r--core/java/android/content/pm/IDataLoaderStatusListener.aidl23
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl9
-rw-r--r--core/java/android/content/pm/IncrementalStatesInfo.aidl20
-rw-r--r--core/java/android/content/pm/IncrementalStatesInfo.java76
-rw-r--r--core/java/android/content/pm/PackageInstaller.java10
-rw-r--r--core/java/android/content/pm/PackageManager.java219
-rw-r--r--core/java/android/content/pm/PackageUserState.java1
-rw-r--r--core/java/android/content/pm/PermissionInfo.java13
-rw-r--r--core/java/android/content/pm/TEST_MAPPING14
-rw-r--r--core/java/android/content/pm/UserInfo.java5
-rw-r--r--core/java/android/content/pm/parsing/ParsingPackageUtils.java108
-rw-r--r--core/java/android/content/res/Configuration.java63
-rw-r--r--core/java/android/content/res/TEST_MAPPING14
-rw-r--r--core/java/android/hardware/Sensor.java1
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java28
-rw-r--r--core/java/android/hardware/biometrics/BiometricTestSession.java181
-rw-r--r--core/java/android/hardware/biometrics/ITestSession.aidl54
-rw-r--r--core/java/android/hardware/biometrics/SensorProperties.java84
-rw-r--r--core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl18
-rw-r--r--core/java/android/hardware/biometrics/SensorPropertiesInternal.java75
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java16
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java16
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java3
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java30
-rw-r--r--core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java3
-rw-r--r--core/java/android/hardware/camera2/impl/FrameNumberTracker.java104
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManager.java (renamed from libs/hwui/shader/RuntimeShader.h)34
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java58
-rw-r--r--core/java/android/hardware/devicestate/IDeviceStateManager.aidl20
-rw-r--r--core/java/android/hardware/display/DisplayManager.java31
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java26
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl6
-rw-r--r--core/java/android/hardware/face/FaceManager.java33
-rw-r--r--core/java/android/hardware/face/FaceSensorProperties.java69
-rw-r--r--core/java/android/hardware/face/FaceSensorPropertiesInternal.aidl (renamed from core/java/android/hardware/face/FaceSensorProperties.aidl)2
-rw-r--r--core/java/android/hardware/face/FaceSensorPropertiesInternal.java81
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl6
-rw-r--r--core/java/android/hardware/fingerprint/Fingerprint.java4
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java73
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorProperties.java113
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.aidl (renamed from core/java/android/hardware/fingerprint/FingerprintSensorProperties.aidl)2
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java93
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl17
-rw-r--r--core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl6
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java24
-rw-r--r--core/java/android/hardware/input/InputManager.java164
-rw-r--r--core/java/android/hardware/input/InputManagerInternal.java22
-rw-r--r--core/java/android/hardware/soundtrigger/ConversionUtil.java9
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java184
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerModule.java34
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java12
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java19
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java112
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java19
-rw-r--r--core/java/android/net/KeepalivePacketData.java3
-rw-r--r--core/java/android/net/NattKeepalivePacketData.java3
-rw-r--r--core/java/android/net/NetworkProvider.java2
-rw-r--r--core/java/android/net/NetworkScoreManager.java24
-rw-r--r--core/java/android/net/NetworkSpecifier.java8
-rw-r--r--core/java/android/net/util/IpUtils.java151
-rw-r--r--core/java/android/os/BatteryStats.java12
-rw-r--r--core/java/android/os/Binder.java4
-rwxr-xr-xcore/java/android/os/Build.java4
-rw-r--r--core/java/android/os/Debug.java6
-rw-r--r--core/java/android/os/FileBridge.java41
-rw-r--r--core/java/android/os/GraphicsEnvironment.java319
-rw-r--r--core/java/android/os/IPowerManager.aidl2
-rw-r--r--core/java/android/os/IVibratorManagerService.aidl24
-rw-r--r--core/java/android/os/Looper.java2
-rw-r--r--core/java/android/os/NullVibrator.java4
-rw-r--r--core/java/android/os/OWNERS1
-rw-r--r--core/java/android/os/Parcel.java4
-rw-r--r--core/java/android/os/PowerManager.java21
-rw-r--r--core/java/android/os/Process.java7
-rw-r--r--core/java/android/os/ServiceManager.java15
-rw-r--r--core/java/android/os/ServiceManagerNative.java4
-rw-r--r--core/java/android/os/SystemVibrator.java10
-rw-r--r--core/java/android/os/VibrationAttributes.java59
-rw-r--r--core/java/android/os/Vibrator.java20
-rw-r--r--core/java/android/os/ZygoteProcess.java8
-rw-r--r--core/java/android/os/connectivity/CellularBatteryStats.java2
-rw-r--r--core/java/android/os/incremental/IIncrementalService.aidl2
-rw-r--r--core/java/android/os/incremental/IncrementalFileStorages.java10
-rw-r--r--core/java/android/os/incremental/IncrementalStorage.java5
-rw-r--r--core/java/android/os/storage/StorageManagerInternal.java7
-rw-r--r--core/java/android/permission/PermissionControllerManager.java8
-rw-r--r--core/java/android/permission/PermissionManager.java15
-rw-r--r--core/java/android/permission/Permissions.md8
-rw-r--r--core/java/android/provider/CalendarContract.java3
-rw-r--r--core/java/android/provider/DeviceConfig.java9
-rw-r--r--core/java/android/provider/FontRequest.java3
-rw-r--r--core/java/android/provider/FontsContract.java20
-rw-r--r--core/java/android/provider/Settings.java200
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java15
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java44
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java14
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rw-r--r--core/java/android/telephony/CellBroadcastIntents.java3
-rw-r--r--core/java/android/telephony/PhoneStateListener.java19
-rw-r--r--core/java/android/text/StyledTextShaper.java67
-rw-r--r--core/java/android/text/TextLine.java140
-rw-r--r--core/java/android/text/TextUtils.java2
-rw-r--r--core/java/android/text/style/SuggestionSpan.java42
-rw-r--r--core/java/android/util/IconDrawableFactory.java2
-rw-r--r--core/java/android/util/IntArray.java11
-rw-r--r--core/java/android/util/Log.java2
-rw-r--r--core/java/android/util/imetracing/ImeTracing.java114
-rw-r--r--core/java/android/util/imetracing/ImeTracingClientImpl.java71
-rw-r--r--core/java/android/util/imetracing/ImeTracingServerImpl.java154
-rw-r--r--core/java/android/uwb/UwbManager.java269
-rw-r--r--core/java/android/view/Choreographer.java43
-rw-r--r--core/java/android/view/DisplayAdjustments.java40
-rw-r--r--core/java/android/view/DisplayEventReceiver.java31
-rw-r--r--core/java/android/view/FrameMetrics.java15
-rw-r--r--core/java/android/view/IWindow.aidl6
-rw-r--r--core/java/android/view/IWindowManager.aidl11
-rw-r--r--core/java/android/view/IWindowSession.aidl51
-rw-r--r--core/java/android/view/ImeFocusController.java15
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java16
-rw-r--r--core/java/android/view/InputChannel.java7
-rw-r--r--core/java/android/view/InputWindowHandle.java9
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java29
-rw-r--r--core/java/android/view/InsetsAnimationControlRunner.java11
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java7
-rw-r--r--core/java/android/view/InsetsController.java35
-rw-r--r--core/java/android/view/InsetsFlags.java47
-rw-r--r--core/java/android/view/InsetsSource.java22
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java29
-rw-r--r--core/java/android/view/InsetsSourceControl.java28
-rw-r--r--core/java/android/view/InsetsState.java56
-rw-r--r--core/java/android/view/KeyEvent.aidl2
-rw-r--r--core/java/android/view/OnReceiveContentCallback.java377
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java16
-rw-r--r--core/java/android/view/Surface.java44
-rw-r--r--core/java/android/view/SurfaceControl.java148
-rw-r--r--core/java/android/view/SurfaceView.java328
-rw-r--r--core/java/android/view/View.java186
-rw-r--r--core/java/android/view/ViewRootImpl.java266
-rw-r--r--core/java/android/view/WindowManager.java11
-rw-r--r--core/java/android/view/WindowManagerImpl.java9
-rw-r--r--core/java/android/view/WindowlessWindowManager.java10
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java12
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java2
-rw-r--r--core/java/android/view/autofill/AutofillManager.java3
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java36
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java40
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java27
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java131
-rw-r--r--core/java/android/view/textservice/SpellCheckerSubtype.java2
-rw-r--r--core/java/android/view/textservice/SuggestionsInfo.java8
-rw-r--r--core/java/android/view/textservice/TextServicesManager.java2
-rw-r--r--core/java/android/webkit/PacProcessor.java33
-rw-r--r--core/java/android/webkit/UserPackage.java2
-rw-r--r--core/java/android/webkit/WebView.java18
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/webkit/WebViewFactoryProvider.java6
-rw-r--r--core/java/android/widget/Editor.java54
-rw-r--r--core/java/android/widget/RichContentReceiver.java234
-rw-r--r--core/java/android/widget/SpellChecker.java16
-rw-r--r--core/java/android/widget/TEST_MAPPING4
-rw-r--r--core/java/android/widget/TextView.java144
-rw-r--r--core/java/android/widget/TextViewOnReceiveContentCallback.java (renamed from core/java/android/widget/TextViewRichContentReceiver.java)62
-rw-r--r--core/java/android/widget/ToastPresenter.java25
-rw-r--r--core/java/android/window/ITaskOrganizerController.aidl7
-rw-r--r--core/java/android/window/ITransitionPlayer.aidl63
-rw-r--r--core/java/android/window/IWindowOrganizerController.aidl32
-rw-r--r--core/java/android/window/TaskAppearedInfo.aidl24
-rw-r--r--core/java/android/window/TaskAppearedInfo.java86
-rw-r--r--core/java/android/window/TaskOrganizer.java18
-rw-r--r--core/java/android/window/TransitionInfo.aidl20
-rw-r--r--core/java/android/window/TransitionInfo.java290
-rw-r--r--core/java/android/window/WindowContainerToken.java5
-rw-r--r--core/java/android/window/WindowContainerTransaction.java35
-rw-r--r--core/java/android/window/WindowOrganizer.java57
-rw-r--r--core/java/com/android/ims/internal/uce/presence/PresCmdID.aidl (renamed from core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl)0
-rw-r--r--core/java/com/android/internal/BrightnessSynchronizer.java25
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java7
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java29
-rw-r--r--core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java (renamed from core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java)4
-rw-r--r--core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java (renamed from core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java)4
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java41
-rw-r--r--core/java/com/android/internal/app/ChooserFlags.java19
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl7
-rw-r--r--core/java/com/android/internal/app/ISoundTriggerService.aidl78
-rw-r--r--core/java/com/android/internal/app/ISoundTriggerSession.aidl68
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl78
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl95
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java12
-rw-r--r--core/java/com/android/internal/app/chooser/SelectableTargetInfo.java5
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java8
-rw-r--r--core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java33
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodDebug.java2
-rw-r--r--core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java16
-rw-r--r--core/java/com/android/internal/inputmethod/StartInputFlags.java6
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java70
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java309
-rw-r--r--core/java/com/android/internal/logging/UiEventLoggerImpl.java13
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java246
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java19
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java8
-rw-r--r--core/java/com/android/internal/os/KernelCpuThreadReader.java10
-rw-r--r--core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java295
-rw-r--r--core/java/com/android/internal/os/ProcTimeInStateReader.java28
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java31
-rw-r--r--core/java/com/android/internal/os/SystemServerCpuThreadReader.java106
-rw-r--r--core/java/com/android/internal/os/SystemServicePowerCalculator.java42
-rw-r--r--core/java/com/android/internal/os/Zygote.java12
-rw-r--r--core/java/com/android/internal/os/ZygoteArguments.java10
-rw-r--r--core/java/com/android/internal/policy/DecorView.java17
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java16
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java10
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl13
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java12
-rw-r--r--core/java/com/android/internal/util/LocationPermissionChecker.java2
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java2
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java7
-rw-r--r--core/java/com/android/internal/view/IInputMethodClient.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl2
-rw-r--r--core/java/com/android/internal/view/RecyclerViewCaptureHelper.java202
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureInternal.java91
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureViewHelper.java13
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureViewSupport.java72
-rw-r--r--core/java/com/android/internal/view/ScrollViewCaptureHelper.java20
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java27
-rw-r--r--core/java/com/android/server/SystemConfig.java11
-rw-r--r--core/jni/Android.bp6
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_database_CursorWindow.cpp22
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp37
-rw-r--r--core/jni/android_hardware_camera2_CameraMetadata.cpp13
-rw-r--r--core/jni/android_hardware_input_InputWindowHandle.cpp10
-rw-r--r--core/jni/android_media_AudioSystem.cpp11
-rw-r--r--core/jni/android_os_Debug.cpp4
-rw-r--r--core/jni/android_os_GraphicsEnvironment.cpp57
-rw-r--r--core/jni/android_os_HwParcel.cpp2
-rw-r--r--core/jni/android_os_Parcel.cpp93
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp10
-rw-r--r--core/jni/android_view_InputChannel.cpp47
-rw-r--r--core/jni/android_view_InputChannel.h2
-rw-r--r--core/jni/android_view_Surface.cpp24
-rw-r--r--core/jni/android_view_SurfaceControl.cpp48
-rw-r--r--core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp269
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp32
-rw-r--r--core/jni/core_jni_helpers.h6
-rw-r--r--core/proto/OWNERS4
-rw-r--r--core/proto/android/app/settings_enums.proto10
-rw-r--r--core/proto/android/content/configuration.proto1
-rw-r--r--core/proto/android/inputmethodservice/inputmethodservice.proto61
-rw-r--r--core/proto/android/inputmethodservice/softinputwindow.proto32
-rw-r--r--core/proto/android/providers/settings/config.proto5
-rw-r--r--core/proto/android/providers/settings/global.proto4
-rw-r--r--core/proto/android/providers/settings/secure.proto21
-rw-r--r--core/proto/android/server/inputmethod/inputmethodmanagerservice.proto50
-rw-r--r--core/proto/android/server/peopleservice.proto9
-rw-r--r--core/proto/android/server/vibratorservice.proto33
-rw-r--r--core/proto/android/server/windowmanagerservice.proto42
-rw-r--r--core/proto/android/service/package.proto7
-rw-r--r--core/proto/android/stats/style/style_enums.proto9
-rw-r--r--core/proto/android/telephony/enums.proto61
-rw-r--r--core/proto/android/view/imeinsetssourceconsumer.proto6
-rw-r--r--core/proto/android/view/inputmethod/inputmethodeditortrace.proto21
-rw-r--r--core/res/AndroidManifest.xml101
-rw-r--r--core/res/res/values-af/strings.xml13
-rw-r--r--core/res/res/values-am/strings.xml13
-rw-r--r--core/res/res/values-ar/strings.xml13
-rw-r--r--core/res/res/values-as/strings.xml13
-rw-r--r--core/res/res/values-az/strings.xml29
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml19
-rw-r--r--core/res/res/values-be/strings.xml27
-rw-r--r--core/res/res/values-bg/strings.xml13
-rw-r--r--core/res/res/values-bn/strings.xml13
-rw-r--r--core/res/res/values-bs/strings.xml15
-rw-r--r--core/res/res/values-ca/strings.xml19
-rw-r--r--core/res/res/values-cs/strings.xml13
-rw-r--r--core/res/res/values-da/strings.xml13
-rw-r--r--core/res/res/values-de/strings.xml23
-rw-r--r--core/res/res/values-el/strings.xml13
-rw-r--r--core/res/res/values-en-rAU/strings.xml13
-rw-r--r--core/res/res/values-en-rCA/strings.xml19
-rw-r--r--core/res/res/values-en-rGB/strings.xml13
-rw-r--r--core/res/res/values-en-rIN/strings.xml13
-rw-r--r--core/res/res/values-en-rXC/strings.xml13
-rw-r--r--core/res/res/values-es-rUS/strings.xml13
-rw-r--r--core/res/res/values-es/strings.xml15
-rw-r--r--core/res/res/values-et/strings.xml13
-rw-r--r--core/res/res/values-eu/strings.xml13
-rw-r--r--core/res/res/values-fa/strings.xml27
-rw-r--r--core/res/res/values-fi/strings.xml17
-rw-r--r--core/res/res/values-fr-rCA/strings.xml13
-rw-r--r--core/res/res/values-fr/strings.xml15
-rw-r--r--core/res/res/values-gl/strings.xml13
-rw-r--r--core/res/res/values-gu/strings.xml13
-rw-r--r--core/res/res/values-hi/strings.xml13
-rw-r--r--core/res/res/values-hr/strings.xml13
-rw-r--r--core/res/res/values-hu/strings.xml13
-rw-r--r--core/res/res/values-hy/strings.xml31
-rw-r--r--core/res/res/values-in/strings.xml13
-rw-r--r--core/res/res/values-is/strings.xml13
-rw-r--r--core/res/res/values-it/strings.xml13
-rw-r--r--core/res/res/values-iw/strings.xml13
-rw-r--r--core/res/res/values-ja/strings.xml13
-rw-r--r--core/res/res/values-ka/strings.xml13
-rw-r--r--core/res/res/values-kk/strings.xml13
-rw-r--r--core/res/res/values-km/strings.xml13
-rw-r--r--core/res/res/values-kn/strings.xml13
-rw-r--r--core/res/res/values-ko/strings.xml13
-rw-r--r--core/res/res/values-ky/strings.xml15
-rw-r--r--core/res/res/values-lo/strings.xml13
-rw-r--r--core/res/res/values-lt/strings.xml13
-rw-r--r--core/res/res/values-lv/strings.xml13
-rw-r--r--core/res/res/values-mk/strings.xml17
-rw-r--r--core/res/res/values-ml/strings.xml15
-rw-r--r--core/res/res/values-mn/strings.xml23
-rw-r--r--core/res/res/values-mr/strings.xml13
-rw-r--r--core/res/res/values-ms/strings.xml13
-rw-r--r--core/res/res/values-my/strings.xml15
-rw-r--r--core/res/res/values-nb/strings.xml15
-rw-r--r--core/res/res/values-ne/strings.xml23
-rw-r--r--core/res/res/values-nl/strings.xml13
-rw-r--r--core/res/res/values-or/strings.xml13
-rw-r--r--core/res/res/values-pa/strings.xml13
-rw-r--r--core/res/res/values-pl/strings.xml13
-rw-r--r--core/res/res/values-pt-rBR/strings.xml13
-rw-r--r--core/res/res/values-pt-rPT/strings.xml13
-rw-r--r--core/res/res/values-pt/strings.xml13
-rw-r--r--core/res/res/values-ro/strings.xml13
-rw-r--r--core/res/res/values-ru/strings.xml15
-rw-r--r--core/res/res/values-si/strings.xml13
-rw-r--r--core/res/res/values-sk/strings.xml13
-rw-r--r--core/res/res/values-sl/strings.xml13
-rw-r--r--core/res/res/values-sq/strings.xml13
-rw-r--r--core/res/res/values-sr/strings.xml19
-rw-r--r--core/res/res/values-sv/strings.xml13
-rw-r--r--core/res/res/values-sw/strings.xml13
-rw-r--r--core/res/res/values-ta/strings.xml13
-rw-r--r--core/res/res/values-te/strings.xml13
-rw-r--r--core/res/res/values-th/strings.xml15
-rw-r--r--core/res/res/values-tl/strings.xml13
-rw-r--r--core/res/res/values-tr/strings.xml13
-rw-r--r--core/res/res/values-uk/strings.xml17
-rw-r--r--core/res/res/values-ur/strings.xml13
-rw-r--r--core/res/res/values-uz/strings.xml13
-rw-r--r--core/res/res/values-vi/strings.xml13
-rw-r--r--core/res/res/values-zh-rCN/strings.xml13
-rw-r--r--core/res/res/values-zh-rHK/strings.xml13
-rw-r--r--core/res/res/values-zh-rTW/strings.xml13
-rw-r--r--core/res/res/values-zu/strings.xml13
-rw-r--r--core/res/res/values/attrs.xml24
-rw-r--r--core/res/res/values/attrs_manifest.xml23
-rw-r--r--core/res/res/values/config.xml31
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/values/strings.xml21
-rw-r--r--core/res/res/values/styles.xml4
-rw-r--r--core/res/res/values/symbols.xml9
-rw-r--r--core/res/res/values/themes.xml1
-rw-r--r--core/tests/coretests/AndroidManifest.xml21
-rw-r--r--core/tests/coretests/res/layout/activity_view_ic_test.xml (renamed from packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml)17
-rw-r--r--core/tests/coretests/src/android/app/NotificationHistoryTest.java24
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java133
-rw-r--r--core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java3
-rw-r--r--core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java171
-rw-r--r--core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java2
-rw-r--r--core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java74
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java64
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java5
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java160
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java (renamed from core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java)40
-rw-r--r--core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java186
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java38
-rw-r--r--core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java3
-rw-r--r--core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt8
-rw-r--r--core/tests/coretests/src/android/os/FileBridgeTest.java12
-rw-r--r--core/tests/coretests/src/android/os/VibratorTest.java124
-rw-r--r--core/tests/coretests/src/android/text/TextShaperTest.java46
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java1
-rw-r--r--core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java18
-rw-r--r--core/tests/coretests/src/android/view/InsetsFlagsTest.java72
-rw-r--r--core/tests/coretests/src/android/view/InsetsStateTest.java18
-rw-r--r--core/tests/coretests/src/android/view/ViewInputConnectionTest.java292
-rw-r--r--core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java31
-rw-r--r--core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java56
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java3
-rw-r--r--core/tests/coretests/src/android/widget/TextViewProcessTextTest.java116
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java130
-rw-r--r--core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java4
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java38
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java129
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java27
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java140
-rw-r--r--core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java89
-rw-r--r--core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java31
-rw-r--r--core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java2
-rw-r--r--core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java343
-rw-r--r--core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java12
-rw-r--r--core/tests/powertests/PowerStatsViewer/Android.bp13
-rw-r--r--core/tests/powertests/PowerStatsViewer/AndroidManifest.xml42
-rw-r--r--core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml85
-rw-r--r--core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml35
-rw-r--r--core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml49
-rw-r--r--core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml77
-rw-r--r--core/tests/powertests/PowerStatsViewer/res/values/styles.xml34
-rw-r--r--core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java114
-rw-r--r--core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java251
-rw-r--r--core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java240
-rw-r--r--core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java263
-rw-r--r--core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java15
-rw-r--r--core/xsd/vts/Android.bp4
-rw-r--r--core/xsd/vts/AndroidTest.xml30
-rw-r--r--data/etc/com.android.storagemanager.xml1
-rw-r--r--data/etc/platform.xml20
-rw-r--r--data/etc/privapp-permissions-platform.xml13
-rw-r--r--data/etc/services.core.protolog.json876
-rw-r--r--data/fonts/Android.bp3
-rw-r--r--data/fonts/fonts.xml233
-rw-r--r--data/keyboards/Vendor_2378_Product_1008.kl5
-rw-r--r--data/keyboards/Vendor_2378_Product_100a.kl5
-rw-r--r--drm/jni/Android.bp1
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/BinderIdentityChecker.java108
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/CompatChangeChecker.java125
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java10
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsChecker.java98
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java123
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityChecker.java81
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java51
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java2
-rw-r--r--errorprone/java/com/google/errorprone/matchers/FieldMatchers.java15
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/BinderIdentityCheckerTest.java111
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/CompatChangeCheckerTest.java102
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/ContextUserIdCheckerTest.java52
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsCheckerTest.java96
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java105
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityCheckerTest.java292
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemCheckerTest.java23
-rw-r--r--errorprone/tests/res/android/app/PendingIntent.java69
-rw-r--r--errorprone/tests/res/android/content/Intent.java20
-rw-r--r--errorprone/tests/res/android/os/Binder.java8
-rw-r--r--errorprone/tests/res/android/os/Build.java3
-rw-r--r--errorprone/tests/res/android/os/Bundle.java20
-rw-r--r--errorprone/tests/res/android/os/Parcel.java57
-rw-r--r--errorprone/tests/res/android/os/Parcelable.java21
-rw-r--r--errorprone/tests/res/com/android/internal/telephony/ITelephony.java (renamed from core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl)11
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java16
-rw-r--r--graphics/java/android/graphics/BaseCanvas.java61
-rw-r--r--graphics/java/android/graphics/BaseRecordingCanvas.java38
-rw-r--r--graphics/java/android/graphics/BlurShader.java28
-rw-r--r--graphics/java/android/graphics/Canvas.java39
-rw-r--r--graphics/java/android/graphics/FrameInfo.java13
-rw-r--r--graphics/java/android/graphics/GraphicsStatsService.java4
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java2
-rw-r--r--graphics/java/android/graphics/Paint.java2
-rw-r--r--graphics/java/android/graphics/ParcelableColorSpace.java5
-rw-r--r--graphics/java/android/graphics/RenderEffect.java145
-rw-r--r--graphics/java/android/graphics/RenderNode.java20
-rw-r--r--graphics/java/android/graphics/RuntimeShader.java44
-rw-r--r--graphics/java/android/graphics/Shader.java6
-rw-r--r--graphics/java/android/graphics/fonts/Font.java155
-rw-r--r--graphics/java/android/graphics/fonts/NativeFontBufferHelper.java62
-rw-r--r--graphics/java/android/graphics/text/GlyphStyle.java234
-rw-r--r--graphics/java/android/graphics/text/PositionedGlyphs.java277
-rw-r--r--graphics/java/android/graphics/text/TextShaper.java125
-rw-r--r--libs/WindowManager/Shell/Android.bp21
-rw-r--r--libs/WindowManager/Shell/res/drawable/dismiss_circle_background.xml (renamed from packages/SystemUI/res/drawable/dismiss_circle_background.xml)0
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_skip_next_white.xml (renamed from packages/SystemUI/res/drawable/ic_skip_next_white.xml)0
-rw-r--r--libs/WindowManager/Shell/res/drawable/pip_ic_skip_previous_white.xml (renamed from packages/SystemUI/res/drawable/ic_skip_previous_white.xml)0
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_controls.xml8
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml16
-rw-r--r--libs/WindowManager/Shell/res/raw/wm_shell_protolog.json41
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java201
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/FloatProperties.kt (renamed from packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt (renamed from packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt)6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt (renamed from packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt)22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java (renamed from packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt (renamed from packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt)44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java (renamed from libs/hwui/shader/BitmapShader.h)32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt (renamed from packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java262
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java)56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java)12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java)43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java)203
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java (renamed from packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java)19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java)316
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java295
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMediaController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java)72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java)54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java)2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java)43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java)49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java)57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java)416
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java)28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java)4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUtils.java (renamed from packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java)17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java)10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java)303
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java)8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java)47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java)65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java (renamed from packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java)16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java109
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt53
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt217
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java72
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt)28
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java86
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt)2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java231
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java)18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java)7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java53
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java103
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java)46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java)11
-rw-r--r--libs/androidfw/CursorWindow.cpp193
-rw-r--r--libs/androidfw/include/androidfw/CursorWindow.h19
-rw-r--r--libs/hwui/Android.bp10
-rw-r--r--libs/hwui/FrameInfo.cpp3
-rw-r--r--libs/hwui/FrameInfo.h8
-rw-r--r--libs/hwui/OWNERS3
-rw-r--r--libs/hwui/RenderProperties.cpp7
-rw-r--r--libs/hwui/RenderProperties.h7
-rw-r--r--libs/hwui/SkiaCanvas.cpp10
-rw-r--r--libs/hwui/SkiaCanvas.h3
-rw-r--r--libs/hwui/VectorDrawable.cpp7
-rw-r--r--libs/hwui/VectorDrawable.h14
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp2
-rw-r--r--libs/hwui/apex/jni_runtime.cpp4
-rw-r--r--libs/hwui/hwui/Canvas.cpp40
-rw-r--r--libs/hwui/hwui/Canvas.h7
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp3
-rw-r--r--libs/hwui/hwui/MinikinSkia.h2
-rw-r--r--libs/hwui/hwui/MinikinUtils.h2
-rw-r--r--libs/hwui/hwui/Paint.h9
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp27
-rw-r--r--libs/hwui/hwui/Typeface.cpp2
-rw-r--r--libs/hwui/jni/FontFamily.cpp2
-rw-r--r--libs/hwui/jni/FontUtils.h5
-rw-r--r--libs/hwui/jni/Graphics.cpp59
-rw-r--r--libs/hwui/jni/GraphicsJNI.h12
-rw-r--r--libs/hwui/jni/Paint.cpp71
-rw-r--r--libs/hwui/jni/RenderEffect.cpp69
-rw-r--r--libs/hwui/jni/Shader.cpp176
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp17
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp3
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp7
-rw-r--r--libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp4
-rw-r--r--libs/hwui/jni/fonts/Font.cpp169
-rw-r--r--libs/hwui/jni/fonts/FontFamily.cpp2
-rw-r--r--libs/hwui/jni/text/TextShaper.cpp206
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp4
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.cpp4
-rw-r--r--libs/hwui/pipeline/skia/ShaderCache.h2
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp2
-rw-r--r--libs/hwui/renderthread/CacheManager.h6
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp12
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp3
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h2
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp3
-rw-r--r--libs/hwui/renderthread/TimeLord.cpp8
-rw-r--r--libs/hwui/renderthread/TimeLord.h5
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp5
-rw-r--r--libs/hwui/renderthread/VulkanSurface.h6
-rw-r--r--libs/hwui/shader/BlurShader.cpp5
-rw-r--r--libs/hwui/shader/BlurShader.h6
-rw-r--r--libs/hwui/shader/ComposeShader.cpp51
-rw-r--r--libs/hwui/shader/ComposeShader.h43
-rw-r--r--libs/hwui/shader/LinearGradientShader.cpp39
-rw-r--r--libs/hwui/shader/LinearGradientShader.h42
-rw-r--r--libs/hwui/shader/RadialGradientShader.cpp38
-rw-r--r--libs/hwui/shader/RadialGradientShader.h42
-rw-r--r--libs/hwui/shader/RuntimeShader.cpp36
-rw-r--r--libs/hwui/shader/Shader.cpp87
-rw-r--r--libs/hwui/shader/Shader.h84
-rw-r--r--libs/hwui/shader/SweepGradientShader.cpp38
-rw-r--r--libs/hwui/shader/SweepGradientShader.h41
-rw-r--r--libs/hwui/tests/common/scenes/BitmapShaders.cpp22
-rw-r--r--libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp47
-rw-r--r--libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp24
-rw-r--r--libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp11
-rw-r--r--libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp22
-rw-r--r--libs/hwui/tests/macrobench/TestSceneRunner.cpp4
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp54
-rw-r--r--libs/hwui/tests/unit/VectorDrawableTests.cpp21
-rw-r--r--location/java/android/location/ILocationManager.aidl7
-rw-r--r--location/java/android/location/Location.java12
-rw-r--r--location/java/android/location/LocationManager.java122
-rw-r--r--location/java/android/location/LocationRequest.java39
-rw-r--r--location/java/android/location/timezone/LocationTimeZoneEvent.java17
-rw-r--r--location/java/android/location/util/identity/CallerIdentity.java5
-rw-r--r--location/java/com/android/internal/location/GpsNetInitiatedHandler.java3
-rw-r--r--location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java69
-rw-r--r--location/lib/Android.bp5
-rw-r--r--location/lib/api/current.txt32
-rw-r--r--location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java166
-rw-r--r--location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java33
-rw-r--r--location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java19
-rw-r--r--media/Android.bp93
-rw-r--r--media/OWNERS6
-rw-r--r--media/aidl/android/media/audio/common/AudioChannelMask.aidl (renamed from media/java/android/media/audio/common/AudioChannelMask.aidl)0
-rw-r--r--media/aidl/android/media/audio/common/AudioConfig.aidl (renamed from media/java/android/media/audio/common/AudioConfig.aidl)0
-rw-r--r--media/aidl/android/media/audio/common/AudioFormat.aidl (renamed from media/java/android/media/audio/common/AudioFormat.aidl)0
-rw-r--r--media/aidl/android/media/audio/common/AudioOffloadInfo.aidl (renamed from media/java/android/media/audio/common/AudioOffloadInfo.aidl)0
-rw-r--r--media/aidl/android/media/audio/common/AudioStreamType.aidl (renamed from media/java/android/media/audio/common/AudioStreamType.aidl)0
-rw-r--r--media/aidl/android/media/audio/common/AudioUsage.aidl (renamed from media/java/android/media/audio/common/AudioUsage.aidl)0
-rw-r--r--media/aidl/android/media/permission/Identity.aidl32
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl (renamed from media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl (renamed from media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl (renamed from media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl89
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl (renamed from media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl (renamed from media/java/android/media/soundtrigger_middleware/ModelParameter.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl (renamed from media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/OWNERS (renamed from media/java/android/media/soundtrigger_middleware/OWNERS)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/Phrase.aidl (renamed from media/java/android/media/soundtrigger_middleware/Phrase.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl (renamed from media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl (renamed from media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl (renamed from media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl (renamed from media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl (renamed from media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl (renamed from media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl (renamed from media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl (renamed from media/java/android/media/soundtrigger_middleware/SoundModel.aidl)3
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl (renamed from media/java/android/media/soundtrigger_middleware/SoundModelType.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl (renamed from media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl (renamed from media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl)0
-rw-r--r--media/aidl/android/media/soundtrigger_middleware/Status.aidl (renamed from media/java/android/media/soundtrigger_middleware/Status.aidl)0
-rw-r--r--media/java/android/media/AudioAttributes.aidl2
-rw-r--r--media/java/android/media/AudioManager.java22
-rw-r--r--media/java/android/media/ExifInterface.java49
-rw-r--r--media/java/android/media/ExifInterfaceUtils.java18
-rw-r--r--media/java/android/media/MediaCodec.java26
-rw-r--r--media/java/android/media/MediaFormat.java48
-rw-r--r--media/java/android/media/MediaMetadata.aidl2
-rw-r--r--media/java/android/media/MediaMetadata.java102
-rw-r--r--media/java/android/media/MediaMetadataRetriever.java13
-rw-r--r--media/java/android/media/MediaRouter.java97
-rw-r--r--media/java/android/media/MediaTranscodeManager.java126
-rw-r--r--media/java/android/media/RemoteController.java2
-rw-r--r--media/java/android/media/Ringtone.java2
-rw-r--r--media/java/android/media/RingtoneManager.java23
-rw-r--r--media/java/android/media/browse/MediaBrowser.java2
-rw-r--r--media/java/android/media/midi/MidiDeviceServer.java14
-rw-r--r--media/java/android/media/musicrecognition/IMusicRecognitionManager.aidl14
-rw-r--r--media/java/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl15
-rw-r--r--media/java/android/media/musicrecognition/IMusicRecognitionService.aidl18
-rw-r--r--media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl15
-rw-r--r--media/java/android/media/musicrecognition/MusicRecognitionManager.java186
-rw-r--r--media/java/android/media/musicrecognition/MusicRecognitionService.java140
-rw-r--r--media/java/android/media/musicrecognition/RecognitionRequest.aidl18
-rw-r--r--media/java/android/media/musicrecognition/RecognitionRequest.java175
-rw-r--r--media/java/android/media/permission/ClearCallingIdentityContext.java60
-rw-r--r--media/java/android/media/permission/CompositeSafeCloseable.java40
-rw-r--r--media/java/android/media/permission/IdentityContext.java100
-rw-r--r--media/java/android/media/permission/PermissionUtil.java250
-rw-r--r--media/java/android/media/permission/SafeCloseable.java27
-rw-r--r--media/java/android/media/session/ISessionManager.aidl4
-rw-r--r--media/java/android/media/session/MediaController.java3
-rw-r--r--media/java/android/media/session/MediaSession.java4
-rw-r--r--media/java/android/media/session/MediaSessionManager.java135
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java12
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerManager.java53
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl42
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputManagerCallback.aidl2
-rw-r--r--media/java/android/media/tv/TunedInfo.aidl19
-rw-r--r--media/java/android/media/tv/TunedInfo.java217
-rw-r--r--media/java/android/media/tv/TvInputManager.java46
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java107
-rw-r--r--media/java/android/media/tv/tuner/TunerVersionChecker.java153
-rw-r--r--media/java/android/media/tv/tuner/filter/Filter.java15
-rw-r--r--media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java40
-rw-r--r--media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java22
-rw-r--r--media/java/android/media/tv/tuner/filter/TsRecordEvent.java13
-rw-r--r--media/jni/Android.bp6
-rw-r--r--media/jni/android_media_MediaCodec.cpp2
-rw-r--r--media/jni/android_media_Utils.cpp68
-rw-r--r--media/jni/android_media_tv_Tuner.cpp315
-rw-r--r--media/jni/android_media_tv_Tuner.h62
-rw-r--r--media/jni/audioeffect/Android.bp5
-rw-r--r--media/jni/audioeffect/android_media_AudioEffect.cpp2
-rw-r--r--media/jni/soundpool/android_media_SoundPool.cpp2
-rw-r--r--media/tests/MediaRouter/Android.bp3
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java195
-rw-r--r--media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java44
-rw-r--r--native/android/surface_control.cpp3
-rw-r--r--native/graphics/jni/imagedecoder.cpp22
-rw-r--r--native/graphics/jni/libjnigraphics.map.txt2
-rw-r--r--non-updatable-api/Android.bp53
-rw-r--r--non-updatable-api/current.txt408
-rw-r--r--non-updatable-api/module-lib-current.txt30
-rw-r--r--non-updatable-api/system-current.txt259
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml26
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_user_icon.xml2
-rw-r--r--packages/CarSystemUI/res/drawable/system_bar_background_pill.xml33
-rw-r--r--packages/CarSystemUI/res/layout/car_navigation_bar.xml59
-rw-r--r--packages/CarSystemUI/res/layout/car_navigation_button.xml2
-rw-r--r--packages/CarSystemUI/res/layout/car_top_navigation_bar.xml99
-rw-r--r--packages/CarSystemUI/res/layout/system_icons.xml15
-rw-r--r--packages/CarSystemUI/res/values/colors.xml5
-rw-r--r--packages/CarSystemUI/res/values/dimens.xml10
-rw-r--r--packages/CarSystemUI/res/values/strings.xml2
-rw-r--r--packages/CarSystemUI/res/values/styles.xml1
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/Android.bp (renamed from packages/services/PacProcessor/jni/Android.bp)40
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml24
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml25
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml25
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml25
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml25
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml26
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml25
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml33
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml105
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml28
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml (renamed from packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml)16
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/values/config.xml57
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml35
-rw-r--r--packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml66
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java23
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java12
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java14
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java24
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java3
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java13
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java8
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java6
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java4
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java42
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java3
-rw-r--r--packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java2
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm366
-rw-r--r--packages/InputDevices/res/values-af/strings.xml3
-rw-r--r--packages/InputDevices/res/values-am/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ar/strings.xml3
-rw-r--r--packages/InputDevices/res/values-as/strings.xml3
-rw-r--r--packages/InputDevices/res/values-az/strings.xml3
-rw-r--r--packages/InputDevices/res/values-b+sr+Latn/strings.xml3
-rw-r--r--packages/InputDevices/res/values-be/strings.xml3
-rw-r--r--packages/InputDevices/res/values-bg/strings.xml3
-rw-r--r--packages/InputDevices/res/values-bn/strings.xml3
-rw-r--r--packages/InputDevices/res/values-bs/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ca/strings.xml3
-rw-r--r--packages/InputDevices/res/values-cs/strings.xml3
-rw-r--r--packages/InputDevices/res/values-da/strings.xml3
-rw-r--r--packages/InputDevices/res/values-de/strings.xml3
-rw-r--r--packages/InputDevices/res/values-el/strings.xml3
-rw-r--r--packages/InputDevices/res/values-es-rUS/strings.xml3
-rw-r--r--packages/InputDevices/res/values-es/strings.xml3
-rw-r--r--packages/InputDevices/res/values-et/strings.xml3
-rw-r--r--packages/InputDevices/res/values-eu/strings.xml3
-rw-r--r--packages/InputDevices/res/values-fa/strings.xml3
-rw-r--r--packages/InputDevices/res/values-fi/strings.xml3
-rw-r--r--packages/InputDevices/res/values-fr-rCA/strings.xml3
-rw-r--r--packages/InputDevices/res/values-fr/strings.xml3
-rw-r--r--packages/InputDevices/res/values-gl/strings.xml3
-rw-r--r--packages/InputDevices/res/values-gu/strings.xml3
-rw-r--r--packages/InputDevices/res/values-hi/strings.xml3
-rw-r--r--packages/InputDevices/res/values-hr/strings.xml3
-rw-r--r--packages/InputDevices/res/values-hu/strings.xml3
-rw-r--r--packages/InputDevices/res/values-hy/strings.xml3
-rw-r--r--packages/InputDevices/res/values-in/strings.xml3
-rw-r--r--packages/InputDevices/res/values-is/strings.xml3
-rw-r--r--packages/InputDevices/res/values-it/strings.xml3
-rw-r--r--packages/InputDevices/res/values-iw/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ja/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ka/strings.xml3
-rw-r--r--packages/InputDevices/res/values-kk/strings.xml3
-rw-r--r--packages/InputDevices/res/values-km/strings.xml3
-rw-r--r--packages/InputDevices/res/values-kn/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ko/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ky/strings.xml3
-rw-r--r--packages/InputDevices/res/values-lo/strings.xml3
-rw-r--r--packages/InputDevices/res/values-lt/strings.xml3
-rw-r--r--packages/InputDevices/res/values-lv/strings.xml3
-rw-r--r--packages/InputDevices/res/values-mk/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ml/strings.xml3
-rw-r--r--packages/InputDevices/res/values-mn/strings.xml3
-rw-r--r--packages/InputDevices/res/values-mr/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ms/strings.xml3
-rw-r--r--packages/InputDevices/res/values-my/strings.xml3
-rw-r--r--packages/InputDevices/res/values-nb/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ne/strings.xml3
-rw-r--r--packages/InputDevices/res/values-nl/strings.xml3
-rw-r--r--packages/InputDevices/res/values-or/strings.xml3
-rw-r--r--packages/InputDevices/res/values-pa/strings.xml3
-rw-r--r--packages/InputDevices/res/values-pl/strings.xml3
-rw-r--r--packages/InputDevices/res/values-pt-rBR/strings.xml3
-rw-r--r--packages/InputDevices/res/values-pt-rPT/strings.xml3
-rw-r--r--packages/InputDevices/res/values-pt/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ro/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ru/strings.xml13
-rw-r--r--packages/InputDevices/res/values-si/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sk/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sl/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sq/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sr/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sv/strings.xml3
-rw-r--r--packages/InputDevices/res/values-sw/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ta/strings.xml3
-rw-r--r--packages/InputDevices/res/values-te/strings.xml3
-rw-r--r--packages/InputDevices/res/values-th/strings.xml3
-rw-r--r--packages/InputDevices/res/values-tl/strings.xml3
-rw-r--r--packages/InputDevices/res/values-tr/strings.xml3
-rw-r--r--packages/InputDevices/res/values-uk/strings.xml3
-rw-r--r--packages/InputDevices/res/values-ur/strings.xml3
-rw-r--r--packages/InputDevices/res/values-uz/strings.xml3
-rw-r--r--packages/InputDevices/res/values-vi/strings.xml3
-rw-r--r--packages/InputDevices/res/values-zh-rCN/strings.xml3
-rw-r--r--packages/InputDevices/res/values-zh-rHK/strings.xml3
-rw-r--r--packages/InputDevices/res/values-zh-rTW/strings.xml3
-rw-r--r--packages/InputDevices/res/values-zu/strings.xml3
-rw-r--r--packages/InputDevices/res/values/strings.xml3
-rw-r--r--packages/InputDevices/res/xml/keyboard_layouts.xml4
-rw-r--r--packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml51
-rw-r--r--packages/SettingsLib/res/layout/restricted_popup_menu_item.xml47
-rw-r--r--packages/SettingsLib/res/layout/user_creation_progress_dialog.xml27
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-en-rXC/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-hy/arrays.xml2
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml10
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml10
-rw-r--r--packages/SettingsLib/res/values/dimens.xml4
-rw-r--r--packages/SettingsLib/res/values/strings.xml12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java30
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/ActivityStarter.java (renamed from libs/hwui/shader/BitmapShader.cpp)22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java219
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java509
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java76
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java58
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS8
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java5
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java44
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java270
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java7
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java7
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java63
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java84
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java22
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java10
-rw-r--r--packages/Shell/AndroidManifest.xml15
-rw-r--r--packages/Shell/src/com/android/shell/BugreportProgressService.java4
-rw-r--r--packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java2
-rw-r--r--packages/SoundPicker/res/values-uz/strings.xml2
-rw-r--r--packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java22
-rw-r--r--packages/SystemUI/AndroidManifest.xml18
-rw-r--r--packages/SystemUI/docs/plugin_hooks.md30
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java107
-rw-r--r--packages/SystemUI/res-keyguard/values-km/strings.xml2
-rw-r--r--packages/SystemUI/res/drawable-television/ic_volume_media.xml6
-rw-r--r--packages/SystemUI/res/drawable-television/ic_volume_media_low.xml12
-rw-r--r--packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml13
-rw-r--r--packages/SystemUI/res/drawable-television/ic_volume_media_off.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_volume_media_off.xml20
-rw-r--r--packages/SystemUI/res/drawable/people_space_tile_view_card.xml28
-rw-r--r--packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml32
-rw-r--r--packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml26
-rw-r--r--packages/SystemUI/res/layout-land-television/volume_dialog_row.xml8
-rw-r--r--packages/SystemUI/res/layout/activity_create_new_user.xml23
-rw-r--r--packages/SystemUI/res/layout/media_output_dialog.xml24
-rw-r--r--packages/SystemUI/res/layout/media_output_list_item.xml23
-rw-r--r--packages/SystemUI/res/layout/notification_conversation_info.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_info.xml5
-rw-r--r--packages/SystemUI/res/layout/partial_conversation_info.xml5
-rw-r--r--packages/SystemUI/res/layout/people_space_activity.xml45
-rw-r--r--packages/SystemUI/res/layout/people_space_tile_view.xml65
-rw-r--r--packages/SystemUI/res/layout/tv_audio_recording_indicator.xml81
-rw-r--r--packages/SystemUI/res/layout/window_magnifier_view.xml9
-rw-r--r--packages/SystemUI/res/values-af/strings.xml33
-rw-r--r--packages/SystemUI/res/values-am/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml35
-rw-r--r--packages/SystemUI/res/values-as/strings.xml33
-rw-r--r--packages/SystemUI/res/values-az/strings.xml35
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml33
-rw-r--r--packages/SystemUI/res/values-be/strings.xml33
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml33
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml37
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml35
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml39
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml33
-rw-r--r--packages/SystemUI/res/values-da/strings.xml33
-rw-r--r--packages/SystemUI/res/values-de/strings.xml57
-rw-r--r--packages/SystemUI/res/values-el/strings.xml33
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml14
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml14
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml14
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml14
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml12
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml35
-rw-r--r--packages/SystemUI/res/values-es/strings.xml33
-rw-r--r--packages/SystemUI/res/values-et/strings.xml33
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml33
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml45
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml35
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml33
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml33
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml39
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml37
-rw-r--r--packages/SystemUI/res/values-h740dp-port/dimens.xml27
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml33
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml33
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml33
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml35
-rw-r--r--packages/SystemUI/res/values-in/strings.xml37
-rw-r--r--packages/SystemUI/res/values-is/strings.xml37
-rw-r--r--packages/SystemUI/res/values-it/strings.xml35
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml35
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml33
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml35
-rw-r--r--packages/SystemUI/res/values-km/strings.xml33
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml35
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml37
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml35
-rw-r--r--packages/SystemUI/res/values-land-television/dimens.xml17
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml33
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml33
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml33
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml35
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml61
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml37
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml35
-rw-r--r--packages/SystemUI/res/values-my/strings.xml33
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml53
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-or/strings.xml35
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml37
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml33
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml35
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml33
-rw-r--r--packages/SystemUI/res/values-si/strings.xml33
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml33
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml39
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml33
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml33
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml33
-rw-r--r--packages/SystemUI/res/values-te/strings.xml33
-rw-r--r--packages/SystemUI/res/values-television/config.xml9
-rw-r--r--packages/SystemUI/res/values-th/strings.xml33
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml33
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml33
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml33
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml37
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml33
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml33
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml33
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml35
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml35
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml33
-rw-r--r--packages/SystemUI/res/values/colors_tv.xml6
-rw-r--r--packages/SystemUI/res/values/config.xml9
-rw-r--r--packages/SystemUI/res/values/config_tv.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml26
-rw-r--r--packages/SystemUI/res/values/ids.xml8
-rw-r--r--packages/SystemUI/res/values/strings.xml33
-rw-r--r--packages/SystemUI/res/values/styles.xml7
-rw-r--r--packages/SystemUI/res/xml/fileprovider.xml1
-rw-r--r--packages/SystemUI/shared/Android.bp2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl31
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java51
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java71
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java40
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java420
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java41
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java35
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java56
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java58
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java359
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java512
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java103
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java225
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java181
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt (renamed from packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java308
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java169
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/Pip.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java380
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java121
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java318
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt204
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java13
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java223
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt464
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java221
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerService.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserCreator.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java95
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml11
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java143
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java142
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java212
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt)25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt258
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt287
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java111
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java202
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java75
-rw-r--r--packages/Tethering/Android.bp1
-rw-r--r--packages/Tethering/jni/android_net_util_TetheringUtils.cpp52
-rw-r--r--packages/Tethering/src/android/net/ip/DadProxy.java54
-rw-r--r--packages/Tethering/src/android/net/ip/IpServer.java53
-rw-r--r--packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java180
-rw-r--r--packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java16
-rw-r--r--packages/Tethering/src/android/net/util/TetheringUtils.java33
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java28
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java285
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/Tethering.java16
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java8
-rw-r--r--packages/Tethering/tests/integration/Android.bp1
-rw-r--r--packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java1
-rw-r--r--packages/Tethering/tests/privileged/Android.bp21
-rw-r--r--packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java276
-rw-r--r--packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java130
-rw-r--r--packages/Tethering/tests/unit/jarjar-rules.txt5
-rw-r--r--packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java109
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java26
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java527
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java87
-rw-r--r--packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml1
-rw-r--r--packages/overlays/IconShapePebbleOverlay/res/values/config.xml2
-rw-r--r--packages/services/PacProcessor/Android.bp1
-rw-r--r--packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp133
-rw-r--r--packages/services/PacProcessor/jni/jni_init.cpp38
-rw-r--r--packages/services/PacProcessor/src/com/android/pacprocessor/LibpacInterface.java34
-rw-r--r--packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java96
-rw-r--r--packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java25
-rw-r--r--packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java48
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/Android.bp9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java66
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java43
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java1
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java27
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java (renamed from services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java)131
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java9
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java2
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java11
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java43
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java4
-rw-r--r--services/backup/java/com/android/server/backup/DataChangedJournal.java2
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java42
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java2
-rw-r--r--services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java8
-rw-r--r--services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java3
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java9
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java9
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java46
-rw-r--r--services/core/java/android/os/BatteryStatsInternal.java7
-rw-r--r--services/core/java/com/android/server/BatteryService.java6
-rw-r--r--services/core/java/com/android/server/BinderCallsStatsService.java15
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java6
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java17
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java14
-rw-r--r--services/core/java/com/android/server/MmsServiceBroker.java2
-rw-r--r--services/core/java/com/android/server/MountServiceIdler.java1
-rw-r--r--services/core/java/com/android/server/OWNERS3
-rw-r--r--services/core/java/com/android/server/RescueParty.java6
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java81
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java77
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java16
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java47
-rw-r--r--services/core/java/com/android/server/UpdateLockService.java2
-rw-r--r--services/core/java/com/android/server/VibratorManagerService.java154
-rw-r--r--services/core/java/com/android/server/VibratorService.java583
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java115
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java85
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java21
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java161
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java46
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java6
-rw-r--r--services/core/java/com/android/server/am/AppExitInfoTracker.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryExternalStatsWorker.java41
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java34
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java2
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java4
-rw-r--r--services/core/java/com/android/server/am/CoreSettingsObserver.java14
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java4
-rw-r--r--services/core/java/com/android/server/am/PendingIntentController.java10
-rw-r--r--services/core/java/com/android/server/am/PhantomProcessList.java259
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java25
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java28
-rw-r--r--services/core/java/com/android/server/am/ProcessStatsService.java2
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java103
-rw-r--r--services/core/java/com/android/server/am/UserController.java26
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java410
-rw-r--r--services/core/java/com/android/server/appop/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/attention/AttentionManagerService.java57
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java512
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java175
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java377
-rw-r--r--services/core/java/com/android/server/biometrics/HardwareAuthTokenUtils.java138
-rw-r--r--services/core/java/com/android/server/biometrics/SensorConfig.java4
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java21
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java54
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java96
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/Face10.java64
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java384
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java116
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java152
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java112
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java71
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java441
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java78
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java65
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java47
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java294
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java)147
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java)41
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java)18
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java)14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java)14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java)11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java)2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java)2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java)2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java)2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java)2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java (renamed from services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java)2
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java2
-rw-r--r--services/core/java/com/android/server/connectivity/DataConnectionStats.java8
-rwxr-xr-xservices/core/java/com/android/server/connectivity/KeepaliveTracker.java12
-rw-r--r--services/core/java/com/android/server/connectivity/PacManager.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java105
-rw-r--r--services/core/java/com/android/server/connectivity/VpnIkev2Utils.java10
-rw-r--r--services/core/java/com/android/server/content/ContentService.java38
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java2
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java276
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStatePolicy.java40
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateProvider.java69
-rw-r--r--services/core/java/com/android/server/devicestate/OWNERS3
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java12
-rw-r--r--services/core/java/com/android/server/display/DisplayBlanker.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java74
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java191
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java515
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java60
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java23
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java8
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java13
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java7
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java258
-rw-r--r--services/core/java/com/android/server/display/PersistentDataStore.java24
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java192
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java83
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java48
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java69
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java24
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java60
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java3
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java197
-rw-r--r--services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java508
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java354
-rw-r--r--services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java11
-rw-r--r--services/core/java/com/android/server/inputmethod/OWNERS1
-rw-r--r--services/core/java/com/android/server/location/AbstractLocationProvider.java8
-rw-r--r--services/core/java/com/android/server/location/LocationFudger.java2
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java178
-rw-r--r--services/core/java/com/android/server/location/LocationProviderManager.java808
-rw-r--r--services/core/java/com/android/server/location/LocationRequestStatistics.java449
-rw-r--r--services/core/java/com/android/server/location/PassiveLocationProviderManager.java25
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java6
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java13
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java41
-rw-r--r--services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java29
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java24
-rw-r--r--services/core/java/com/android/server/location/timezone/ControllerImpl.java542
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java32
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java175
-rw-r--r--services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java26
-rw-r--r--services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java6
-rw-r--r--services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java174
-rw-r--r--services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java4
-rw-r--r--services/core/java/com/android/server/location/timezone/ThreadingDomain.java52
-rw-r--r--services/core/java/com/android/server/location/util/AlarmHelper.java47
-rw-r--r--services/core/java/com/android/server/location/util/Injector.java8
-rw-r--r--services/core/java/com/android/server/location/util/LocationEventLog.java313
-rw-r--r--services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java16
-rw-r--r--services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java9
-rw-r--r--services/core/java/com/android/server/location/util/SystemAlarmHelper.java57
-rw-r--r--services/core/java/com/android/server/location/util/SystemAppForegroundHelper.java2
-rw-r--r--services/core/java/com/android/server/location/util/SystemAppOpsHelper.java10
-rw-r--r--services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java2
-rw-r--r--services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java3
-rw-r--r--services/core/java/com/android/server/location/util/SystemSettingsHelper.java16
-rw-r--r--services/core/java/com/android/server/location/util/SystemUserInfoHelper.java6
-rw-r--r--services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java19
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java18
-rw-r--r--services/core/java/com/android/server/media/MediaButtonReceiverHolder.java2
-rw-r--r--services/core/java/com/android/server/media/MediaResourceMonitorService.java14
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java4
-rw-r--r--services/core/java/com/android/server/media/MediaShellCommand.java16
-rw-r--r--services/core/java/com/android/server/media/VolumeCtrl.java28
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java4
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java64
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsAccess.java9
-rw-r--r--services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java53
-rw-r--r--services/core/java/com/android/server/net/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/notification/EventConditionProvider.java2
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java25
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryDatabase.java22
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryManager.java6
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerInternal.java4
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java500
-rw-r--r--services/core/java/com/android/server/notification/NotificationShellCmd.java23
-rw-r--r--services/core/java/com/android/server/notification/NotificationUsageStats.java386
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java7
-rw-r--r--services/core/java/com/android/server/notification/ScheduleConditionProvider.java4
-rw-r--r--services/core/java/com/android/server/notification/SnoozeHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/ApexManager.java20
-rw-r--r--services/core/java/com/android/server/pm/ApkChecksums.java177
-rw-r--r--services/core/java/com/android/server/pm/DumpState.java1
-rw-r--r--services/core/java/com/android/server/pm/IncrementalStates.java480
-rw-r--r--services/core/java/com/android/server/pm/InstantAppRegistry.java4
-rw-r--r--services/core/java/com/android/server/pm/InstantAppResolverConnection.java2
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java28
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java14
-rwxr-xr-xservices/core/java/com/android/server/pm/PackageInstallerSession.java353
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java728
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java14
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java14
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java11
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java68
-rw-r--r--services/core/java/com/android/server/pm/ProcessLoggingHandler.java187
-rw-r--r--services/core/java/com/android/server/pm/SELinuxMMAC.java27
-rw-r--r--services/core/java/com/android/server/pm/SettingBase.java21
-rw-r--r--services/core/java/com/android/server/pm/Settings.java278
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java244
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING9
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java35
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/dex/ViewCompiler.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/BasePermission.java86
-rw-r--r--services/core/java/com/android/server/pm/permission/DevicePermissionState.java45
-rw-r--r--services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java44
-rw-r--r--services/core/java/com/android/server/pm/permission/LegacyPermissionState.java320
-rw-r--r--services/core/java/com/android/server/pm/permission/OWNERS2
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java2303
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java14
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionState.java10
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionsState.java970
-rw-r--r--services/core/java/com/android/server/pm/permission/TEST_MAPPING17
-rw-r--r--services/core/java/com/android/server/pm/permission/UidPermissionState.java612
-rw-r--r--services/core/java/com/android/server/pm/permission/UserPermissionState.java47
-rw-r--r--services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java44
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java45
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java53
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java16
-rw-r--r--services/core/java/com/android/server/power/AttentionDetector.java23
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java16
-rw-r--r--services/core/java/com/android/server/power/batterysaver/TEST_MAPPING19
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java10
-rw-r--r--services/core/java/com/android/server/role/RoleManagerService.java4
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java15
-rw-r--r--services/core/java/com/android/server/rollback/RollbackStore.java13
-rw-r--r--services/core/java/com/android/server/search/Searchables.java2
-rw-r--r--services/core/java/com/android/server/slice/SliceManagerService.java6
-rw-r--r--services/core/java/com/android/server/slice/SliceShellCommand.java2
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java17
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java25
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java7
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java216
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java351
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java113
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java309
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java5
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java19
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java79
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarShellCommand.java24
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorService.java3
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java4
-rw-r--r--services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java92
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java27
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java113
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java5
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java38
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java6
-rwxr-xr-xservices/core/java/com/android/server/tv/TvInputManagerService.java153
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerService.java2
-rw-r--r--services/core/java/com/android/server/utils/XmlName.java31
-rw-r--r--services/core/java/com/android/server/utils/XmlPersistence.java36
-rw-r--r--services/core/java/com/android/server/utils/eventlog/LocalEventLog.java338
-rw-r--r--services/core/java/com/android/server/vcn/OWNERS7
-rw-r--r--services/core/java/com/android/server/vr/Vr2dDisplay.java1
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java4
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java19
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java171
-rwxr-xr-xservices/core/java/com/android/server/wm/ActivityStackSupervisor.java111
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java38
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java482
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java6
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java6
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java20
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java70
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaGroup.java57
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java20
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java202
-rw-r--r--services/core/java/com/android/server/wm/DisplayFrames.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java150
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java17
-rw-r--r--services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java2
-rw-r--r--services/core/java/com/android/server/wm/DragState.java15
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java16
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java117
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/wm/HighRefreshRateDenylist.java (renamed from services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java)38
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java18
-rw-r--r--services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java20
-rw-r--r--services/core/java/com/android/server/wm/InputConsumerImpl.java17
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java34
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java49
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java34
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java132
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsController.java6
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java11
-rw-r--r--services/core/java/com/android/server/wm/PolicyControl.java270
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java196
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java9
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java44
-rwxr-xr-xservices/core/java/com/android/server/wm/RefreshRatePolicy.java8
-rw-r--r--services/core/java/com/android/server/wm/ResetTargetTaskHelper.java20
-rw-r--r--services/core/java/com/android/server/wm/RootDisplayArea.java13
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java379
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java8
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java10
-rw-r--r--services/core/java/com/android/server/wm/Session.java32
-rw-r--r--services/core/java/com/android/server/wm/Task.java301
-rwxr-xr-xservices/core/java/com/android/server/wm/TaskDisplayArea.java213
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java79
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java12
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioningController.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotSurface.java10
-rw-r--r--services/core/java/com/android/server/wm/Transition.java487
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java225
-rw-r--r--services/core/java/com/android/server/wm/VrController.java10
-rw-r--r--services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java68
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java150
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java106
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java375
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java219
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java241
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfaceController.java149
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java52
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/core/jni/OWNERS1
-rw-r--r--services/core/jni/com_android_server_VibratorManagerService.cpp87
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp217
-rw-r--r--services/core/jni/com_android_server_location_GnssLocationProvider.cpp112
-rw-r--r--services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp31
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/core/xsd/vts/Android.mk22
-rw-r--r--services/core/xsd/vts/AndroidTest.xml30
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java861
-rw-r--r--services/incremental/BinderIncrementalService.cpp12
-rw-r--r--services/incremental/BinderIncrementalService.h4
-rw-r--r--services/incremental/IncrementalService.cpp113
-rw-r--r--services/incremental/IncrementalService.h8
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java4
-rw-r--r--services/musicrecognition/Android.bp13
-rw-r--r--services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java300
-rw-r--r--services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java116
-rw-r--r--services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java84
-rw-r--r--services/net/java/android/net/TcpKeepalivePacketData.java3
-rw-r--r--services/net/java/android/net/ip/IpClientManager.java2
-rw-r--r--services/people/java/com/android/server/people/PeopleService.java43
-rw-r--r--services/people/java/com/android/server/people/data/ConversationInfo.java73
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java235
-rw-r--r--services/profcollect/Android.bp1
-rw-r--r--services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java8
-rw-r--r--services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java34
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java5
-rw-r--r--services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java18
-rw-r--r--services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java76
-rw-r--r--services/tests/mockingservicestests/AndroidManifest.xml2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java260
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java88
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java122
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java114
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java12
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java254
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java61
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java19
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java159
-rw-r--r--services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java78
-rw-r--r--services/tests/servicestests/src/com/android/server/VibratorServiceTest.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java (renamed from services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java)61
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java122
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java345
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverterTest.java119
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java109
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java127
-rw-r--r--services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/HardwareAuthTokenUtilsTest.java83
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java137
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java224
-rw-r--r--services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java94
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java233
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java80
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java201
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java898
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java309
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java316
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java70
-rw-r--r--services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java205
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java117
-rw-r--r--services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java14
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java33
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java20
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java7
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java328
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java26
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java3
-rw-r--r--services/tests/wmtests/AndroidManifest.xml2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java118
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java59
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/HighRefreshRateDenylistTest.java (renamed from services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java)94
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java78
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/StubTransaction.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java55
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java45
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestIWindow.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java178
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java206
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java98
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java9
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java6
-rw-r--r--services/usb/java/com/android/server/usb/MtpNotificationManager.java9
-rw-r--r--services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java9
-rw-r--r--services/usb/java/com/android/server/usb/UsbSerialReader.java2
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java4
-rw-r--r--services/usb/java/com/android/server/usb/UsbSettingsManager.java1
-rw-r--r--services/usb/java/com/android/server/usb/UsbUserPermissionManager.java31
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java22
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java179
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java1274
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java269
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java2
-rw-r--r--startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt2
-rwxr-xr-xtelecomm/java/android/telecom/Call.java34
-rw-r--r--telecomm/java/android/telecom/CallScreeningService.java52
-rw-r--r--telecomm/java/android/telecom/CallerInfo.java3
-rw-r--r--telecomm/java/android/telecom/CallerInfoAsyncQuery.java4
-rw-r--r--telecomm/java/android/telecom/DefaultDialerManager.java4
-rw-r--r--telecomm/java/android/telecom/DisconnectCause.java4
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java13
-rw-r--r--telephony/api/system-current.txt67
-rw-r--r--telephony/common/android/telephony/LocationAccessPolicy.java2
-rw-r--r--telephony/common/com/android/internal/telephony/SmsApplication.java2
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java4
-rw-r--r--telephony/java/android/telephony/BinderCacheManager.java197
-rw-r--r--telephony/java/android/telephony/CallForwardingInfo.java109
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java52
-rw-r--r--telephony/java/android/telephony/CellIdentityNr.java6
-rw-r--r--telephony/java/android/telephony/CellLocation.java3
-rw-r--r--telephony/java/android/telephony/ModemActivityInfo.java284
-rw-r--r--telephony/java/android/telephony/SmsManager.java80
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java572
-rw-r--r--telephony/java/android/telephony/cdma/CdmaCellLocation.java3
-rw-r--r--telephony/java/android/telephony/data/DataCallResponse.java21
-rw-r--r--telephony/java/android/telephony/gsm/GsmCellLocation.java5
-rw-r--r--telephony/java/android/telephony/ims/ImsMmTelManager.java5
-rw-r--r--telephony/java/android/telephony/ims/ImsRcsManager.java2
-rw-r--r--telephony/java/android/telephony/ims/ImsService.java58
-rw-r--r--telephony/java/android/telephony/ims/ProvisioningManager.java4
-rw-r--r--telephony/java/android/telephony/ims/RcsUceAdapter.java6
-rw-r--r--telephony/java/android/telephony/ims/RegistrationManager.java10
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl7
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl6
-rw-r--r--telephony/java/android/telephony/mbms/InternalDownloadProgressListener.java2
-rw-r--r--telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java6
-rw-r--r--telephony/java/android/telephony/mbms/InternalDownloadStatusListener.java2
-rw-r--r--telephony/java/android/telephony/mbms/InternalGroupCallCallback.java6
-rw-r--r--telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java8
-rw-r--r--telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java10
-rw-r--r--telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java6
-rw-r--r--telephony/java/com/android/ims/ImsFeatureContainer.aidl19
-rw-r--r--telephony/java/com/android/ims/ImsFeatureContainer.java172
-rw-r--r--telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl15
-rw-r--r--telephony/java/com/android/internal/telephony/ICallForwardingInfoCallback.aidl25
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl116
-rw-r--r--tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java6
-rw-r--r--tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java6
-rw-r--r--tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java6
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleDataClass.java6
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java8
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java18
-rw-r--r--tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java27
-rw-r--r--tests/FlickerTests/Android.bp14
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt22
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt129
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt36
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt49
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt110
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt106
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt109
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt102
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt123
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/ime/ImeAssertions.kt)0
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt114
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt56
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt111
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt63
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt123
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt142
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt142
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt151
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt157
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt249
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt140
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt272
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt144
-rw-r--r--tests/HwAccelerationTest/AndroidManifest.xml2
-rw-r--r--tests/HwAccelerationTest/res/layout/image_filter_activity.xml28
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java86
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java13
-rw-r--r--tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java52
-rw-r--r--tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt5
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java20
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java73
-rw-r--r--tests/SilkFX/AndroidManifest.xml13
-rw-r--r--tests/SilkFX/res/layout-television/activity_glass.xml302
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt60
-rw-r--r--tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java22
-rw-r--r--tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java35
-rw-r--r--tests/SurfaceViewBufferTests/Android.bp58
-rw-r--r--tests/SurfaceViewBufferTests/AndroidManifest.xml46
-rw-r--r--tests/SurfaceViewBufferTests/AndroidTest.xml35
-rw-r--r--tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp102
-rw-r--r--tests/SurfaceViewBufferTests/res/values/styles.xml25
-rw-r--r--tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt102
-rw-r--r--tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt32
-rw-r--r--tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt185
-rw-r--r--tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java2
-rw-r--r--tests/net/Android.bp1
-rw-r--r--tests/net/integration/util/com/android/server/NetworkAgentWrapper.java22
-rw-r--r--tests/net/java/android/net/util/IpUtilsTest.java166
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java26
-rw-r--r--tests/net/java/com/android/server/connectivity/VpnTest.java69
-rw-r--r--tests/net/java/com/android/server/net/NetworkStatsAccessTest.java7
-rw-r--r--tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java81
-rw-r--r--tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java3
-rw-r--r--tests/vcn/OWNERS7
-rw-r--r--tools/aapt2/dump/DumpManifest.cpp201
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp2
-rw-r--r--tools/codegen/src/com/android/codegen/FileInfo.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt7
-rw-r--r--tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt7
-rw-r--r--tools/codegen/src/com/android/codegen/SharedConstants.kt2
-rw-r--r--tools/codegen/src/com/android/codegen/Utils.kt4
-rw-r--r--tools/stringslint/stringslint.py12
-rwxr-xr-xtools/stringslint/stringslint_sha.sh2
-rw-r--r--tools/validatekeymaps/Main.cpp10
-rw-r--r--tools/xmlpersistence/Android.bp11
-rw-r--r--tools/xmlpersistence/OWNERS1
-rw-r--r--tools/xmlpersistence/manifest.txt1
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Generator.kt576
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Main.kt45
-rw-r--r--tools/xmlpersistence/src/main/kotlin/Parser.kt248
-rw-r--r--tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt44
-rw-r--r--wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl2
-rw-r--r--wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl2
-rw-r--r--wifi/api/current.txt9
-rw-r--r--wifi/api/system-current.txt10
-rw-r--r--wifi/api/system-lint-baseline.txt7
-rw-r--r--wifi/jarjar-rules.txt1
-rw-r--r--wifi/java/android/net/wifi/SoftApConfiguration.java5
-rw-r--r--wifi/java/android/net/wifi/WifiConfiguration.java75
-rw-r--r--wifi/java/android/net/wifi/WifiInfo.java18
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java43
-rw-r--r--wifi/java/android/net/wifi/WifiNetworkSuggestion.java126
-rw-r--r--wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java33
-rw-r--r--wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java59
-rw-r--r--wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java66
-rw-r--r--wifi/java/android/net/wifi/util/SdkLevelUtil.java14
-rw-r--r--wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java13
-rw-r--r--wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java28
-rw-r--r--wifi/tests/src/android/net/wifi/WifiConfigurationTest.java77
-rw-r--r--wifi/tests/src/android/net/wifi/WifiInfoTest.java2
-rw-r--r--wifi/tests/src/android/net/wifi/WifiManagerTest.java33
-rw-r--r--wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java109
-rw-r--r--wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java2
2251 files changed, 86545 insertions, 34568 deletions
diff --git a/Android.bp b/Android.bp
index c6f9362b4919..1f17932be895 100755
--- a/Android.bp
+++ b/Android.bp
@@ -330,6 +330,7 @@ filegroup {
":gatekeeper_aidl",
":gsiservice_aidl",
":incidentcompanion_aidl",
+ ":inputconstants_aidl",
":installd_aidl",
":keystore_aidl",
":libaudioclient_aidl",
@@ -373,7 +374,7 @@ filegroup {
java_library {
name: "framework-updatable-stubs-module_libs_api",
static_libs: [
- "framework-appsearch-stubs", // TODO: Update to module_libs_api when there is one.
+ "framework-appsearch.stubs.module_lib",
"framework-graphics.stubs.module_lib",
"framework-media.stubs.module_lib",
"framework-mediaprovider.stubs.module_lib",
@@ -392,7 +393,7 @@ java_library {
installable: false,
static_libs: [
"framework-minus-apex",
- "framework-appsearch",
+ "framework-appsearch.impl",
"framework-graphics.impl",
"framework-mediaprovider.impl",
"framework-permission.impl",
@@ -474,12 +475,14 @@ java_library {
"android.hardware.radio-V1.3-java",
"android.hardware.radio-V1.4-java",
"android.hardware.radio-V1.5-java",
+ "android.hardware.radio-V1.6-java",
"android.hardware.thermal-V1.0-java-constants",
"android.hardware.thermal-V1.0-java",
"android.hardware.thermal-V1.1-java",
"android.hardware.thermal-V2.0-java",
"android.hardware.tv.input-V1.0-java-constants",
"android.hardware.tv.tuner-V1.0-java-constants",
+ "android.hardware.tv.tuner-V1.1-java-constants",
"android.hardware.usb-V1.0-java-constants",
"android.hardware.usb-V1.1-java-constants",
"android.hardware.usb-V1.2-java-constants",
@@ -547,6 +550,7 @@ java_library {
exclude_srcs: ["core/java/android/content/pm/AndroidTestBaseUpdater.java"],
aidl: {
generate_get_transaction_name: true,
+ local_include_dirs: ["media/aidl"],
},
dxflags: [
"--core-library",
@@ -583,6 +587,7 @@ java_library {
// in favor of an API stubs dependency in java_library "framework" below.
"mimemap",
"mediatranscoding_aidl_interface-java",
+ "soundtrigger_middleware-aidl-java",
],
// For backwards compatibility.
stem: "framework",
@@ -595,6 +600,13 @@ java_library {
"//frameworks/base/apex/jobscheduler/framework",
"//frameworks/base/packages/Tethering/tests/unit",
],
+ errorprone: {
+ javacflags: [
+ "-Xep:AndroidFrameworkBinderIdentity:ERROR",
+ "-Xep:AndroidFrameworkCompatChange:ERROR",
+ "-Xep:AndroidFrameworkUid:ERROR",
+ ],
+ },
}
// This "framework" module is NOT installed to the device. It's
@@ -613,8 +625,7 @@ java_library {
static_libs: [
"app-compat-annotations",
"framework-minus-apex",
- // TODO(b/146218515): should be removed
- "framework-appsearch",
+ "framework-appsearch.impl", // TODO(b/146218515): should be removed
"framework-updatable-stubs-module_libs_api",
],
sdk_version: "core_platform",
@@ -786,7 +797,6 @@ filegroup {
name: "framework-services-net-module-wifi-shared-srcs",
srcs: [
"core/java/android/net/DhcpResults.java",
- "core/java/android/net/util/IpUtils.java",
"core/java/android/util/LocalLog.java",
],
}
@@ -1346,7 +1356,7 @@ droidstubs {
},
libs: [
"framework-annotations-lib",
- "android.hardware.radio-V1.5-java",
+ "android.hardware.radio-V1.6-java",
],
check_api: {
current: {
diff --git a/ApiDocs.bp b/ApiDocs.bp
index ca921ff97c35..faa0e5dba997 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -65,7 +65,7 @@ stubs_defaults {
"test-base/src/**/*.java",
":opt-telephony-srcs",
":opt-net-voip-srcs",
- ":art-module-public-api-stubs-source",
+ ":art.module.public.api{.public.stubs.source}",
":conscrypt.module.public.api{.public.stubs.source}",
":android_icu4j_public_api_files",
"test-mock/src/**/*.java",
@@ -83,6 +83,10 @@ stubs_defaults {
merge_annotations_dirs: [
"metalava-manual",
],
+ // TODO(b/169090544): remove below aidl includes.
+ aidl: {
+ local_include_dirs: ["media/aidl"],
+ },
}
droidstubs {
@@ -131,7 +135,7 @@ doc_defaults {
],
knowntags: [
"docs/knowntags.txt",
- ":known-oj-tags",
+ ":art.module.public.api{.doctags}",
],
custom_template: "droiddoc-templates-sdk",
resourcesdir: "docs/html/reference/images/",
@@ -150,6 +154,10 @@ doc_defaults {
":current-support-api",
":current-androidx-api",
],
+ // TODO(b/169090544): remove below aidl includes.
+ aidl: {
+ local_include_dirs: ["media/aidl"],
+ },
}
doc_defaults {
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index fc5efc6e03ac..f66d12a69594 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -11,6 +11,8 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
libs/input/
services/core/jni/
services/incremental/
+ tests/
+ tools/
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index bb6538739c49..8ac98423d1d8 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -47,15 +47,36 @@ stubs_defaults {
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
- ":art-module-public-api-stubs-source",
+ ":art.module.public.api{.public.stubs.source}",
":android_icu4j_public_api_files",
"**/package.html",
],
- // TODO(b/147699819): remove below aidl includes.
+ // TODO(b/147699819, b/169090544): remove below aidl includes.
aidl: {
- local_include_dirs: ["telephony/java"],
+ local_include_dirs: [
+ "telephony/java",
+ "media/aidl",
+ ],
},
- libs: ["framework-internal-utils"],
+ // These are libs from framework-internal-utils that are required (i.e. being referenced)
+ // from framework-non-updatable-sources. Add more here when there's a need.
+ // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+ // dependencies gets bigger.
+ libs: [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.radio-V1.5-java",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.tv.tuner-V1.0-java-constants",
+ "android.hardware.tv.tuner-V1.1-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+ ],
installable: false,
annotations_enabled: true,
previous_api: ":android.api.public.latest",
@@ -101,7 +122,6 @@ stubs_defaults {
droidstubs {
name: "api-stubs-docs",
defaults: ["metalava-full-api-stubs-default"],
- removed_dex_api_filename: "removed-dex.txt",
arg_files: [
"core/res/AndroidManifest.xml",
],
@@ -122,12 +142,6 @@ droidstubs {
baseline_file: "api/lint-baseline.txt",
},
},
- dist: {
- targets: ["sdk", "win_sdk"],
- dir: "apistubs/android/public/api",
- dest: "android.txt",
- },
- jdiff_enabled: true,
}
droidstubs {
@@ -140,6 +154,11 @@ droidstubs {
api_file: "non-updatable-api/current.txt",
removed_api_file: "non-updatable-api/removed.txt",
},
+ last_released: {
+ api_file: ":android-non-updatable.api.public.latest",
+ removed_api_file: ":android-non-updatable-removed.api.public.latest",
+ baseline_file: ":public-api-incompatibilities-with-last-released",
+ },
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.public.latest",
@@ -163,7 +182,6 @@ module_libs = " " +
droidstubs {
name: "system-api-stubs-docs",
defaults: ["metalava-full-api-stubs-default"],
- removed_dex_api_filename: "system-removed-dex.txt",
arg_files: [
"core/res/AndroidManifest.xml",
],
@@ -184,12 +202,6 @@ droidstubs {
baseline_file: "api/system-lint-baseline.txt",
},
},
- dist: {
- targets: ["sdk", "win_sdk"],
- dir: "apistubs/android/system/api",
- dest: "android.txt",
- },
- jdiff_enabled: true,
}
droidstubs {
@@ -202,6 +214,11 @@ droidstubs {
api_file: "non-updatable-api/system-current.txt",
removed_api_file: "non-updatable-api/system-removed.txt",
},
+ last_released: {
+ api_file: ":android-non-updatable.api.system.latest",
+ removed_api_file: ":android-non-updatable-removed.api.system.latest",
+ baseline_file: ":system-api-incompatibilities-with-last-released"
+ },
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.system.latest",
@@ -216,7 +233,11 @@ droidstubs {
arg_files: [
"core/res/AndroidManifest.xml",
],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
+ args: metalava_framework_docs_args
+ + " --show-annotation android.annotation.TestApi"
+ + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\("
+ + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS"
+ + "\\)",
check_api: {
current: {
api_file: "api/test-current.txt",
@@ -264,11 +285,6 @@ droidstubs {
baseline_file: "api/module-lib-lint-baseline.txt",
},
},
- dist: {
- targets: ["sdk", "win_sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android.txt",
- },
}
droidstubs {
@@ -281,6 +297,10 @@ droidstubs {
api_file: "non-updatable-api/module-lib-current.txt",
removed_api_file: "non-updatable-api/module-lib-removed.txt",
},
+ last_released: {
+ api_file: ":android-non-updatable.api.module-lib.latest",
+ removed_api_file: ":android-non-updatable-removed.api.module-lib.latest",
+ },
api_lint: {
enabled: true,
new_since: ":android-non-updatable.api.module-lib.latest",
@@ -333,6 +353,7 @@ java_library_static {
srcs: [ ":api-stubs-docs-non-updatable" ],
static_libs: [
"conscrypt.module.public.api.stubs",
+ "framework-appsearch.stubs",
"framework-graphics.stubs",
"framework-media.stubs",
"framework-mediaprovider.stubs",
@@ -378,7 +399,7 @@ java_library_static {
srcs: [ ":system-api-stubs-docs-non-updatable" ],
static_libs: [
"conscrypt.module.public.api.stubs",
- "framework-appsearch-stubs", // TODO: standardize appsearch stubs
+ "framework-appsearch.stubs.system",
"framework-graphics.stubs.system",
"framework-media.stubs.system",
"framework-mediaprovider.stubs.system",
@@ -488,29 +509,3 @@ java_library_static {
":hwbinder-stubs-docs",
],
}
-
-/////////////////////////////////////////////////////////////////////
-// api/*-current.txt files for use by modules in other directories
-// like the CTS test
-/////////////////////////////////////////////////////////////////////
-
-filegroup {
- name: "frameworks-base-api-current.txt",
- srcs: [
- "api/current.txt",
- ],
-}
-
-filegroup {
- name: "frameworks-base-api-system-current.txt",
- srcs: [
- "api/system-current.txt",
- ],
-}
-
-filegroup {
- name: "frameworks-base-api-system-removed.txt",
- srcs: [
- "api/system-removed.txt",
- ],
-}
diff --git a/apct-tests/perftests/autofill/Android.bp b/apct-tests/perftests/autofill/Android.bp
index 4f6c21af9281..79176ff993c4 100644
--- a/apct-tests/perftests/autofill/Android.bp
+++ b/apct-tests/perftests/autofill/Android.bp
@@ -20,6 +20,7 @@ android_test {
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
+ "compatibility-device-util-axt",
"collector-device-lib",
],
platform_apis: true,
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
index 8f8fc29ee3c4..54e1860d3390 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -11,44 +11,54 @@
* 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
+ * limitations under the License.
*/
package android.view.autofill;
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
import static org.junit.Assert.assertTrue;
import android.os.Looper;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.PerfTestActivity;
-import android.perftests.utils.SettingsHelper;
import android.perftests.utils.SettingsStateKeeperRule;
import android.provider.Settings;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.rules.RuleChain;
/**
* Base class for all autofill tests.
*/
public abstract class AbstractAutofillPerfTestCase {
+ private static final String TAG = "AbstractAutofillPerfTestCase";
+
@ClassRule
public static final SettingsStateKeeperRule mServiceSettingsKeeper =
new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
Settings.Secure.AUTOFILL_SERVICE);
- @Rule
- public ActivityTestRule<PerfTestActivity> mActivityRule =
+ protected final AutofillTestWatcher mTestWatcher = MyAutofillService.getTestWatcher();
+ protected ActivityTestRule<PerfTestActivity> mActivityRule =
new ActivityTestRule<>(PerfTestActivity.class);
+ protected PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
- public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ public final RuleChain mAllRules = RuleChain
+ .outerRule(mTestWatcher)
+ .around(mPerfStatusReporter)
+ .around(mActivityRule);
private final int mLayoutId;
@@ -56,6 +66,27 @@ public abstract class AbstractAutofillPerfTestCase {
mLayoutId = layoutId;
}
+ @BeforeClass
+ public static void disableDefaultAugmentedService() {
+ Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
+ setDefaultAugmentedAutofillServiceEnabled(false);
+ }
+
+ @AfterClass
+ public static void enableDefaultAugmentedService() {
+ Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
+ setDefaultAugmentedAutofillServiceEnabled(true);
+ }
+
+ /**
+ * Enables / disables the default augmented autofill service.
+ */
+ private static void setDefaultAugmentedAutofillServiceEnabled(boolean enabled) {
+ Log.d(TAG, "setDefaultAugmentedAutofillServiceEnabled(): " + enabled);
+ runShellCommand("cmd autofill set default-augmented-service-enabled 0 %s",
+ Boolean.toString(enabled));
+ }
+
/**
* Prepares the activity so that by the time the test is run it has reference to its fields.
*/
@@ -72,41 +103,8 @@ public abstract class AbstractAutofillPerfTestCase {
});
}
- @Before
- public void enableService() {
- MyAutofillService.resetStaticState();
- MyAutofillService.setEnabled(true);
- }
-
- @After
- public void disableService() {
- // Must disable service so calls are ignored in case of errors during the test case;
- // otherwise, other tests will fail because these calls are made in the UI thread (as both
- // the service, the tests, and the app run in the same process).
- MyAutofillService.setEnabled(false);
- }
-
/**
* Initializes the {@link PerfTestActivity} after it was launched.
*/
protected abstract void onCreate(PerfTestActivity activity);
-
- /**
- * Uses the {@code settings} binary to set the autofill service.
- */
- protected void setService() {
- SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
- SettingsHelper.NAMESPACE_SECURE,
- Settings.Secure.AUTOFILL_SERVICE,
- MyAutofillService.COMPONENT_NAME);
- }
-
- /**
- * Uses the {@code settings} binary to reset the autofill service.
- */
- protected void resetService() {
- SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
- SettingsHelper.NAMESPACE_SECURE,
- Settings.Secure.AUTOFILL_SERVICE);
- }
}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestHelper.java b/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestHelper.java
new file mode 100644
index 000000000000..0763729c7803
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestHelper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.view.autofill;
+
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
+import android.service.autofill.FillContext;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Helper for common funcionalities.
+ */
+public class AutofillTestHelper {
+ private static final String TAG = "AutofillTestHelper";
+
+ /**
+ * Gets a node given its Android resource id, or {@code null} if not found.
+ */
+ public static ViewNode findNodeByResourceId(List<FillContext> contexts, String resourceId) {
+ for (FillContext context : contexts) {
+ ViewNode node = findNodeByResourceId(context.getStructure(), resourceId);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets a node if it matches the filter criteria for the given id.
+ */
+ private static ViewNode findNodeByResourceId(AssistStructure structure, String id) {
+ Log.v(TAG, "Parsing request for activity " + structure.getActivityComponent());
+ final int nodes = structure.getWindowNodeCount();
+ for (int i = 0; i < nodes; i++) {
+ final WindowNode windowNode = structure.getWindowNodeAt(i);
+ final ViewNode rootNode = windowNode.getRootViewNode();
+ final ViewNode node = findNodeByResourceId(rootNode, id);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets a node if it matches the filter criteria for the given id.
+ */
+ private static ViewNode findNodeByResourceId(ViewNode node, String id) {
+ if (id.equals(node.getIdEntry())) {
+ return node;
+ }
+ final int childrenSize = node.getChildCount();
+ if (childrenSize > 0) {
+ for (int i = 0; i < childrenSize; i++) {
+ final ViewNode found = findNodeByResourceId(node.getChildAt(i), id);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.java b/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.java
new file mode 100644
index 000000000000..f65067fe2d92
--- /dev/null
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AutofillTestWatcher.java
@@ -0,0 +1,196 @@
+/*
+ * 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.view.autofill;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import android.perftests.utils.SettingsHelper;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.Timeout;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Custom {@link TestWatcher} that does the setup and reset tasks for the tests.
+ */
+final class AutofillTestWatcher extends TestWatcher {
+
+ private static final String TAG = "AutofillTestWatcher";
+ private static final long GENERIC_TIMEOUT_MS = 10_000;
+
+ private static ServiceWatcher sServiceWatcher;
+
+ private String mOriginalLogLevel;
+
+ @Override
+ protected void starting(Description description) {
+ super.starting(description);
+ final String testName = description.getDisplayName();
+ Log.i(TAG, "Starting " + testName);
+
+ prepareDevice();
+ enableVerboseLog();
+ // Prepare the service before each test.
+ // Disable the current AutofillService.
+ resetAutofillService();
+ // Set MyAutofillService status enable, it can start to accept the calls.
+ enableMyAutofillService();
+ setServiceWatcher();
+ }
+
+ @Override
+ protected void finished(Description description) {
+ super.finished(description);
+ final String testName = description.getDisplayName();
+ Log.i(TAG, "Finished " + testName);
+ restoreLogLevel();
+ // Set MyAutofillService status disable, so the calls are ignored.
+ disableMyAutofillService();
+ clearServiceWatcher();
+ }
+
+ void waitServiceConnect() throws InterruptedException {
+ if (sServiceWatcher != null) {
+ Log.d(TAG, "waitServiceConnect()");
+ sServiceWatcher.waitOnConnected();
+ }
+ }
+
+ /**
+ * Uses the {@code settings} binary to set the autofill service.
+ */
+ void setAutofillService() {
+ String serviceName = MyAutofillService.COMPONENT_NAME;
+ SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(),
+ SettingsHelper.NAMESPACE_SECURE,
+ Settings.Secure.AUTOFILL_SERVICE,
+ serviceName);
+ // Waits until the service is actually enabled.
+ Timeout timeout = new Timeout("CONNECTION_TIMEOUT", GENERIC_TIMEOUT_MS, 2F,
+ GENERIC_TIMEOUT_MS);
+ try {
+ timeout.run("Enabling Autofill service", () -> {
+ return isAutofillServiceEnabled(serviceName) ? serviceName : null;
+ });
+ } catch (Exception e) {
+ throw new AssertionError("Enabling Autofill service failed.");
+ }
+ }
+
+ /**
+ * Uses the {@code settings} binary to reset the autofill service.
+ */
+ void resetAutofillService() {
+ SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(),
+ SettingsHelper.NAMESPACE_SECURE,
+ Settings.Secure.AUTOFILL_SERVICE);
+ }
+
+ /**
+ * Checks whether the given service is set as the autofill service for the default user.
+ */
+ private boolean isAutofillServiceEnabled(String serviceName) {
+ String actualName = SettingsHelper.get(SettingsHelper.NAMESPACE_SECURE,
+ Settings.Secure.AUTOFILL_SERVICE);
+ return serviceName.equals(actualName);
+ }
+
+ private void prepareDevice() {
+ // Unlock screen.
+ runShellCommand("input keyevent KEYCODE_WAKEUP");
+
+ // Dismiss keyguard, in case it's set as "Swipe to unlock".
+ runShellCommand("wm dismiss-keyguard");
+
+ // Collapse notifications.
+ runShellCommand("cmd statusbar collapse");
+ }
+
+ private void enableMyAutofillService() {
+ MyAutofillService.resetStaticState();
+ MyAutofillService.setEnabled(true);
+ }
+
+ private void disableMyAutofillService() {
+ // Must disable service so calls are ignored in case of errors during the test case;
+ // otherwise, other tests will fail because these calls are made in the UI thread (as both
+ // the service, the tests, and the app run in the same process).
+ MyAutofillService.setEnabled(false);
+ }
+
+ private void enableVerboseLog() {
+ mOriginalLogLevel = runShellCommand("cmd autofill get log_level");
+ Log.d(TAG, "enableVerboseLog(), mOriginalLogLevel=" + mOriginalLogLevel);
+ if (!mOriginalLogLevel.equals("verbose")) {
+ runShellCommand("cmd autofill set log_level verbose");
+ }
+ }
+
+ private void restoreLogLevel() {
+ Log.d(TAG, "restoreLogLevel to " + mOriginalLogLevel);
+ if (!mOriginalLogLevel.equals("verbose")) {
+ runShellCommand("cmd autofill set log_level %s", mOriginalLogLevel);
+ }
+ }
+
+ private static void setServiceWatcher() {
+ if (sServiceWatcher == null) {
+ sServiceWatcher = new ServiceWatcher();
+ }
+ }
+
+ private static void clearServiceWatcher() {
+ if (sServiceWatcher != null) {
+ sServiceWatcher = null;
+ }
+ }
+
+ public static final class ServiceWatcher {
+ private final CountDownLatch mConnected = new CountDownLatch(1);
+
+ public static void onConnected() {
+ Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
+
+ sServiceWatcher.mConnected.countDown();
+ }
+
+ @NonNull
+ public void waitOnConnected() throws InterruptedException {
+ await(mConnected, "not connected");
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String fmt,
+ @Nullable Object... args)
+ throws InterruptedException {
+ final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (!called) {
+ throw new IllegalStateException(String.format(fmt, args)
+ + " in " + GENERIC_TIMEOUT_MS + "ms");
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
index 5f52dc782422..a5d1e00139b5 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package android.view.autofill;
@@ -28,12 +28,14 @@ import androidx.test.filters.LargeTest;
import com.android.perftests.autofill.R;
-import org.junit.Ignore;
import org.junit.Test;
@LargeTest
public class LoginTest extends AbstractAutofillPerfTestCase {
+ public static final String ID_USERNAME = "username";
+ public static final String ID_PASSWORD = "password";
+
private EditText mUsername;
private EditText mPassword;
private AutofillManager mAfm;
@@ -55,15 +57,15 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
*/
@Test
public void testFocus_noService() throws Throwable {
- resetService();
+ mTestWatcher.resetAutofillService();
- mActivityRule.runOnUiThread(() -> {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mActivityRule.runOnUiThread(() -> {
mUsername.requestFocus();
mPassword.requestFocus();
- }
- });
+ });
+ }
}
/**
@@ -73,36 +75,35 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testFocus_serviceDoesNotAutofill() throws Throwable {
MyAutofillService.newCannedResponse().reply();
- setService();
+ mTestWatcher.setAutofillService();
// Must first focus in a field to trigger autofill and wait for service response
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
// Then focus on password so loop start with focus away from username
mActivityRule.runOnUiThread(() -> mPassword.requestFocus());
- mActivityRule.runOnUiThread(() -> {
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mActivityRule.runOnUiThread(() -> {
mUsername.requestFocus();
mPassword.requestFocus();
- }
- });
+ });
+ }
}
/**
* Now the service returns autofill data, for both username and password.
*/
- // TODO(b/162216576): fix fail test and re-enable it
- @Ignore
@Test
public void testFocus_autofillBothFields() throws Throwable {
MyAutofillService.newCannedResponse()
- .setUsername(mUsername.getAutofillId(), "user")
- .setPassword(mPassword.getAutofillId(), "pass")
+ .setUsername(ID_USERNAME, "user")
+ .setPassword(ID_PASSWORD, "pass")
.reply();
- setService();
+ mTestWatcher.setAutofillService();
// Callback is used to slow down the calls made to the autofill server so the
// app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
@@ -112,6 +113,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first trigger autofill and wait for service response outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
@@ -148,16 +150,14 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
/**
* Now the service returns autofill data, but just for username.
*/
- // TODO(b/162216576): fix fail test and re-enable it
- @Ignore
@Test
public void testFocus_autofillUsernameOnly() throws Throwable {
// Must set ignored ids so focus on password does not trigger new requests
MyAutofillService.newCannedResponse()
- .setUsername(mUsername.getAutofillId(), "user")
- .setIgnored(mPassword.getAutofillId())
+ .setUsername(ID_USERNAME, "user")
+ .setIgnored(ID_PASSWORD)
.reply();
- setService();
+ mTestWatcher.setAutofillService();
// Callback is used to slow down the calls made to the autofill server so the
// app is not crashed due to binder exhaustion. But the time spent waiting for the callbacks
@@ -167,6 +167,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first trigger autofill and wait for service response outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
@@ -201,7 +202,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
*/
@Test
public void testChange_noService() throws Throwable {
- resetService();
+ mTestWatcher.resetAutofillService();
changeTest(false);
}
@@ -213,7 +214,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testChange_serviceDoesNotAutofill() throws Throwable {
MyAutofillService.newCannedResponse().reply();
- setService();
+ mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -224,10 +225,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
@Test
public void testChange_autofillBothFields() throws Throwable {
MyAutofillService.newCannedResponse()
- .setUsername(mUsername.getAutofillId(), "user")
- .setPassword(mPassword.getAutofillId(), "pass")
+ .setUsername(ID_USERNAME, "user")
+ .setPassword(ID_PASSWORD, "pass")
.reply();
- setService();
+ mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -239,10 +240,10 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
public void testChange_autofillUsernameOnly() throws Throwable {
// Must set ignored ids so focus on password does not trigger new requests
MyAutofillService.newCannedResponse()
- .setUsername(mUsername.getAutofillId(), "user")
- .setIgnored(mPassword.getAutofillId())
+ .setUsername(ID_USERNAME, "user")
+ .setIgnored(ID_PASSWORD)
.reply();
- setService();
+ mTestWatcher.setAutofillService();
changeTest(true);
}
@@ -252,29 +253,27 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
if (waitForService) {
+ mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
}
- mActivityRule.runOnUiThread(() -> {
-
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mActivityRule.runOnUiThread(() -> {
mUsername.setText("");
mUsername.setText("a");
mPassword.setText("");
mPassword.setText("x");
- }
- });
+ });
+ }
}
- // TODO(b/162216576): fix fail test and re-enable it
- @Ignore
@Test
public void testCallbacks() throws Throwable {
MyAutofillService.newCannedResponse()
- .setUsername(mUsername.getAutofillId(), "user")
- .setPassword(mPassword.getAutofillId(), "pass")
+ .setUsername(ID_USERNAME, "user")
+ .setPassword(ID_PASSWORD, "pass")
.reply();
- setService();
+ mTestWatcher.setAutofillService();
MyAutofillCallback callback = new MyAutofillCallback();
mAfm.registerCallback(callback);
@@ -282,6 +281,7 @@ public class LoginTest extends AbstractAutofillPerfTestCase {
// Must first focus in a field to trigger autofill and wait for service response
// outside the loop
mActivityRule.runOnUiThread(() -> mUsername.requestFocus());
+ mTestWatcher.waitServiceConnect();
MyAutofillService.getLastFillRequest();
callback.expectEvent(mUsername, EVENT_INPUT_SHOWN);
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
index 7060233fc80f..5db659796e5f 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/MyAutofillService.java
@@ -11,10 +11,11 @@
* 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
+ * limitations under the License.
*/
package android.view.autofill;
+import android.app.assist.AssistStructure.ViewNode;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.Dataset;
@@ -28,12 +29,9 @@ import android.util.Pair;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.perftests.autofill.R;
-import java.util.ArrayList;
-import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -44,7 +42,7 @@ import java.util.concurrent.TimeUnit;
public class MyAutofillService extends AutofillService {
private static final String TAG = "MyAutofillService";
- private static final int TIMEOUT_MS = 5000;
+ private static final int TIMEOUT_MS = 5_000;
private static final String PACKAGE_NAME = "com.android.perftests.autofill";
static final String COMPONENT_NAME = PACKAGE_NAME + "/android.view.autofill.MyAutofillService";
@@ -56,6 +54,14 @@ public class MyAutofillService extends AutofillService {
private static boolean sEnabled;
/**
+ * Returns the TestWatcher that was used for the testing.
+ */
+ @NonNull
+ public static AutofillTestWatcher getTestWatcher() {
+ return new AutofillTestWatcher();
+ }
+
+ /**
* Resets the static state associated with the service.
*/
static void resetStaticState() {
@@ -93,6 +99,11 @@ public class MyAutofillService extends AutofillService {
}
@Override
+ public void onConnected() {
+ AutofillTestWatcher.ServiceWatcher.onConnected();
+ }
+
+ @Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback) {
try {
@@ -120,20 +131,27 @@ public class MyAutofillService extends AutofillService {
boolean hasData = false;
if (response.mUsername != null) {
hasData = true;
- dataset.setValue(response.mUsername.first,
- AutofillValue.forText(response.mUsername.second));
+ AutofillId autofillId = getAutofillIdByResourceId(request, response.mUsername.first);
+ AutofillValue value = AutofillValue.forText(response.mUsername.second);
+ dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
}
if (response.mPassword != null) {
hasData = true;
- dataset.setValue(response.mPassword.first,
- AutofillValue.forText(response.mPassword.second));
+ AutofillId autofillId = getAutofillIdByResourceId(request, response.mPassword.first);
+ AutofillValue value = AutofillValue.forText(response.mPassword.second);
+ dataset.setValue(autofillId, value, newDatasetPresentation("dataset"));
}
if (hasData) {
FillResponse.Builder fillResponse = new FillResponse.Builder();
if (response.mIgnoredIds != null) {
- fillResponse.setIgnoredIds(response.mIgnoredIds);
+ int length = response.mIgnoredIds.length;
+ AutofillId[] requiredIds = new AutofillId[length];
+ for (int i = 0; i < length; i++) {
+ String resourceId = response.mIgnoredIds[i];
+ requiredIds[i] = getAutofillIdByResourceId(request, resourceId);
+ }
+ fillResponse.setIgnoredIds(requiredIds);
}
-
callback.onSuccess(fillResponse.addDataset(dataset.build()).build());
} else {
callback.onSuccess(null);
@@ -143,6 +161,16 @@ public class MyAutofillService extends AutofillService {
}
}
+ private AutofillId getAutofillIdByResourceId(FillRequest request, String resourceId)
+ throws Exception {
+ ViewNode node = AutofillTestHelper.findNodeByResourceId(request.getFillContexts(),
+ resourceId);
+ if (node == null) {
+ throw new AssertionError("No node with resource id " + resourceId);
+ }
+ return node.getAutofillId();
+ }
+
@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
// No current test should have triggered it...
@@ -151,9 +179,9 @@ public class MyAutofillService extends AutofillService {
}
static final class CannedResponse {
- private final Pair<AutofillId, String> mUsername;
- private final Pair<AutofillId, String> mPassword;
- private final AutofillId[] mIgnoredIds;
+ private final Pair<String, String> mUsername;
+ private final Pair<String, String> mPassword;
+ private final String[] mIgnoredIds;
private CannedResponse(@NonNull Builder builder) {
mUsername = builder.mUsername;
@@ -162,24 +190,24 @@ public class MyAutofillService extends AutofillService {
}
static class Builder {
- private Pair<AutofillId, String> mUsername;
- private Pair<AutofillId, String> mPassword;
- private AutofillId[] mIgnoredIds;
+ private Pair<String, String> mUsername;
+ private Pair<String, String> mPassword;
+ private String[] mIgnoredIds;
@NonNull
- Builder setUsername(@NonNull AutofillId id, @NonNull String value) {
+ Builder setUsername(@NonNull String id, @NonNull String value) {
mUsername = new Pair<>(id, value);
return this;
}
@NonNull
- Builder setPassword(@NonNull AutofillId id, @NonNull String value) {
+ Builder setPassword(@NonNull String id, @NonNull String value) {
mPassword = new Pair<>(id, value);
return this;
}
@NonNull
- Builder setIgnored(AutofillId... ids) {
+ Builder setIgnored(String... ids) {
mIgnoredIds = ids;
return this;
}
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index 92dbc263cb34..c5419637dec8 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -1,3 +1,19 @@
+//
+// 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.
+//
+
android_test {
name: "CorePerfTests",
@@ -23,17 +39,14 @@ android_test {
libs: ["android.test.base"],
+ java_resources: [ ":GoogleFontDancingScript", ],
+
data: [":perfetto_artifacts"],
platform_apis: true,
jni_libs: ["libperftestscore_jni"],
- // Use google-fonts/dancing-script for the performance metrics
- // ANDROIDMK TRANSLATION ERROR: Only $(LOCAL_PATH)/.. values are allowed
- // LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
-
test_suites: ["device-tests"],
certificate: "platform",
-
}
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 833ae63e1125..e0c11cf0e3d5 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -20,7 +20,17 @@
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
- <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />
+
+ <service
+ android:name="android.os.SomeService"
+ android:exported="false"
+ android:process=":some_service" />
+
+ <provider
+ android:name="android.os.SomeProvider"
+ android:authorities="android.os.SomeProvider"
+ android:exported="false"
+ android:process=":some_provider" />
<service
android:name="android.view.autofill.MyAutofillService"
diff --git a/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java
new file mode 100644
index 000000000000..77654df1273c
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/CrossProcessCursorPerfTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.database;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class CrossProcessCursorPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ /**
+ * Measure transporting a small {@link Cursor}, roughly 1KB in size.
+ */
+ @Test
+ public void timeSmall() throws Exception {
+ time(1);
+ }
+
+ /**
+ * Measure transporting a small {@link Cursor}, roughly 54KB in size.
+ */
+ @Test
+ public void timeMedium() throws Exception {
+ time(100);
+ }
+
+ /**
+ * Measure transporting a small {@link Cursor}, roughly 5.4MB in size.
+ */
+ @Test
+ public void timeLarge() throws Exception {
+ time(10_000);
+ }
+
+ private static final Uri TEST_URI = Uri.parse("content://android.os.SomeProvider/");
+
+ private void time(int count) throws Exception {
+ try (ContentProviderClient client = InstrumentationRegistry.getTargetContext()
+ .getContentResolver().acquireContentProviderClient(TEST_URI)) {
+ // Configure remote side once with data size to return
+ final ContentValues values = new ContentValues();
+ values.put(Intent.EXTRA_INDEX, count);
+ client.update(TEST_URI, values, null);
+
+ // Repeatedly query that data until we reach convergence
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ try (Cursor c = client.query(TEST_URI, null, null, null)) {
+ // Actually walk the returned values to ensure we pull all
+ // data from the remote side
+ while (c.moveToNext()) {
+ assertEquals(c.getPosition(), c.getInt(0));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
index 884745699789..e83c64c37678 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceCreatePerfTest.java
@@ -27,6 +27,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.Preconditions;
import com.android.perftests.core.R;
import org.junit.Rule;
@@ -73,10 +74,31 @@ public class TypefaceCreatePerfTest {
final AssetManager am = context.getAssets();
while (state.keepRunning()) {
- Typeface face = Typeface.createFromAsset(am, TEST_FONT_NAME);
+ Typeface face = createFromNonAsset(am, TEST_FONT_NAME);
}
}
+ /**
+ * {@link AssetManager#openNonAsset(String)} variant of
+ * {@link Typeface#createFromAsset(AssetManager, String)}.
+ */
+ private static Typeface createFromNonAsset(AssetManager mgr, String path) {
+ Preconditions.checkNotNull(path); // for backward compatibility
+ Preconditions.checkNotNull(mgr);
+
+ Typeface typeface = new Typeface.Builder(mgr, path).build();
+ if (typeface != null) return typeface;
+ // check if the file exists, and throw an exception for backward compatibility
+ //noinspection EmptyTryBlock
+ try (InputStream inputStream = mgr.openNonAsset(path)) {
+ // Purposely empty
+ } catch (IOException e) {
+ throw new RuntimeException("Font asset not found " + path);
+ }
+
+ return Typeface.DEFAULT;
+ }
+
@Test
public void testCreate_fromFile() {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
@@ -90,7 +112,7 @@ public class TypefaceCreatePerfTest {
throw new RuntimeException(e);
}
- try (InputStream in = am.open(TEST_FONT_NAME);
+ try (InputStream in = am.openNonAsset(TEST_FONT_NAME);
OutputStream out = new FileOutputStream(outFile)) {
byte[] buf = new byte[1024];
int n = 0;
diff --git a/apct-tests/perftests/core/src/android/os/SomeProvider.java b/apct-tests/perftests/core/src/android/os/SomeProvider.java
new file mode 100644
index 000000000000..f5e247ea6e93
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SomeProvider.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+import java.util.Arrays;
+
+public class SomeProvider extends ContentProvider {
+ private Cursor mCursor;
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return mCursor;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ final char[] valueRaw = new char[512];
+ Arrays.fill(valueRaw, '!');
+ final String value = new String(valueRaw);
+
+ final int count = values.getAsInteger(Intent.EXTRA_INDEX);
+ final MatrixCursor cursor = new MatrixCursor(new String[] { "_id", "value" });
+ for (int i = 0; i < count; i++) {
+ MatrixCursor.RowBuilder row = cursor.newRow();
+ row.add(0, i);
+ row.add(1, value);
+ }
+ mCursor = cursor;
+ return 1;
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java b/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
index bb6b691226d6..66b2b0ecb21d 100644
--- a/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
+++ b/apct-tests/perftests/core/src/android/text/CanvasDrawTextTest.java
@@ -53,19 +53,19 @@ public class CanvasDrawTextTest {
final String text = mTextUtil.nextRandomParagraph(
WORD_LENGTH, 4 * 1024 * 1024 /* 4mb text */).toString();
final RenderNode node = RenderNode.create("benchmark", null);
- final RenderNode child = RenderNode.create("child", null);
- child.setLeftTopRightBottom(50, 50, 100, 100);
-
- RecordingCanvas canvas = node.start(100, 100);
- node.end(canvas);
- canvas = child.start(50, 50);
- child.end(canvas);
final Random r = new Random(0);
-
while (state.keepRunning()) {
+ state.pauseTiming();
+ RecordingCanvas canvas = node.beginRecording();
int start = r.nextInt(text.length() - 100);
+ state.resumeTiming();
+
canvas.drawText(text, start, start + 100, 0, 0, PAINT);
+
+ state.pauseTiming();
+ node.endRecording();
+ state.resumeTiming();
}
}
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
index efcabd817109..833cc0ff37a0 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java
@@ -226,7 +226,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase
mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
- atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
+ atm.startRecentsActivity(sRecentsIntent, 0 /* eventTime */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index a701f8631969..ecd149989ce6 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -139,7 +139,6 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase
final IntSupplier mViewVisibility;
- int mSeq;
int mFrameNumber;
int mFlags;
@@ -156,7 +155,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase
void runBenchmark(BenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
- session.relayout(mWindow, mSeq, mParams, mWidth, mHeight,
+ session.relayout(mWindow, mParams, mWidth, mHeight,
mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
mOutSurfaceSize, mOutBlastSurfaceControl);
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index c52b1300aedc..b11d74646d67 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -107,7 +107,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
final InputChannel inputChannel = new InputChannel();
long startTime = SystemClock.elapsedRealtimeNanos();
- session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE,
+ session.addToDisplay(this, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets,
mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
index d955289e5f49..a9d57167f6d2 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java
@@ -115,7 +115,7 @@ public class WmPerfRunListener extends RunListener {
runWithShellPermissionIdentity(() -> {
final ActivityTaskManager atm = mContext.getSystemService(ActivityTaskManager.class);
atm.removeAllVisibleRecentTasks();
- atm.removeStacksWithActivityTypes(new int[] { ACTIVITY_TYPE_STANDARD,
+ atm.removeRootTasksWithActivityTypes(new int[] { ACTIVITY_TYPE_STANDARD,
ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_UNDEFINED });
});
PhoneWindow.sendCloseSystemWindows(mContext, "WmPerfTests");
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 321f471c3c8b..e10fb074126f 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -21,60 +21,17 @@ filegroup {
path: "java",
}
-java_library {
+java_sdk_library {
name: "framework-appsearch",
- installable: true,
+ srcs: [ ":framework-appsearch-sources" ],
sdk_version: "core_platform", // TODO(b/146218515) should be module_current
- srcs: [":framework-appsearch-sources"],
- hostdex: true, // for hiddenapi check
- libs: [
- "framework-minus-apex", // TODO(b/146218515) should be removed
- ],
+ impl_only_libs: ["framework-minus-apex"], // TODO(b/146218515) should be removed
static_libs: ["icing-java-proto-lite"],
- visibility: [
- // TODO(b/146218515) remove this when framework is built with the stub of appsearch
- "//frameworks/base",
- "//frameworks/base/apex/appsearch:__subpackages__",
- ],
- jarjar_rules: "jarjar-rules.txt",
+ defaults: ["framework-module-defaults"],
permitted_packages: ["android.app.appsearch"],
- apex_available: ["com.android.appsearch"],
-}
-
-metalava_appsearch_docs_args =
- "--hide-package com.android.server " +
- "--error UnhiddenSystemApi " +
- "--hide RequiresPermission " +
- "--hide MissingPermission " +
- "--hide BroadcastBehavior " +
- "--hide HiddenSuperclass " +
- "--hide DeprecationMismatch " +
- "--hide UnavailableSymbol " +
- "--hide SdkConstant " +
- "--hide HiddenTypeParameter " +
- "--hide Todo --hide Typo " +
- "--hide HiddenTypedefConstant " +
- "--show-annotation android.annotation.SystemApi "
-
-droidstubs {
- name: "framework-appsearch-stubs-srcs",
- srcs: [":framework-appsearch-sources"],
- libs: ["framework-annotations-lib"],
- aidl: {
- include_dirs: ["frameworks/base/core/java"],
- },
- args: metalava_appsearch_docs_args,
- sdk_version: "module_current",
-}
-
-java_library {
- name: "framework-appsearch-stubs",
- srcs: [":framework-appsearch-stubs-srcs"],
+ jarjar_rules: "jarjar-rules.txt",
aidl: {
- export_include_dirs: [
- "java",
- ],
+ include_dirs: ["frameworks/base/core/java"], // TODO(b/146218515) should be removed
},
- sdk_version: "module_current",
- installable: false,
+ apex_available: ["com.android.appsearch"],
}
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
new file mode 100644
index 000000000000..d802177e249b
--- /dev/null
+++ b/apex/appsearch/framework/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/appsearch/framework/api/module-lib-current.txt b/apex/appsearch/framework/api/module-lib-current.txt
new file mode 100644
index 000000000000..d802177e249b
--- /dev/null
+++ b/apex/appsearch/framework/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/appsearch/framework/api/module-lib-removed.txt b/apex/appsearch/framework/api/module-lib-removed.txt
new file mode 100644
index 000000000000..d802177e249b
--- /dev/null
+++ b/apex/appsearch/framework/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/appsearch/framework/api/removed.txt b/apex/appsearch/framework/api/removed.txt
new file mode 100644
index 000000000000..d802177e249b
--- /dev/null
+++ b/apex/appsearch/framework/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/appsearch/framework/api/system-current.txt b/apex/appsearch/framework/api/system-current.txt
new file mode 100644
index 000000000000..4a6194e2915d
--- /dev/null
+++ b/apex/appsearch/framework/api/system-current.txt
@@ -0,0 +1,9 @@
+// Signature format: 2.0
+package android.app.appsearch {
+
+ public class AppSearchManagerFrameworkInitializer {
+ method public static void initialize();
+ }
+
+}
+
diff --git a/apex/appsearch/framework/api/system-removed.txt b/apex/appsearch/framework/api/system-removed.txt
new file mode 100644
index 000000000000..d802177e249b
--- /dev/null
+++ b/apex/appsearch/framework/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java
index 9afa19475bef..7d2b64e5d882 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchDocument.java
@@ -47,6 +47,10 @@ import java.util.Objects;
public class AppSearchDocument {
private static final String TAG = "AppSearchDocument";
+ /** The default empty namespace.*/
+ // TODO(adorokhine): Allow namespace to be specified in the document.
+ public static final String DEFAULT_NAMESPACE = "";
+
/**
* The maximum number of elements in a repeatable field. Will reject the request if exceed
* this limit.
@@ -450,7 +454,7 @@ public class AppSearchDocument {
*/
public Builder(@NonNull String uri, @NonNull String schemaType) {
mBuilderTypeInstance = (BuilderType) this;
- mProtoBuilder.setUri(uri).setSchema(schemaType);
+ mProtoBuilder.setUri(uri).setSchema(schemaType).setNamespace(DEFAULT_NAMESPACE);
// Set current timestamp for creation timestamp by default.
setCreationTimestampMillis(System.currentTimeMillis());
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 64264c03f79e..ad51a5c4158e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -18,14 +18,13 @@ package android.app.appsearch;
import android.annotation.NonNull;
import android.annotation.SystemService;
import android.content.Context;
+import android.os.Bundle;
import android.os.RemoteException;
import com.android.internal.infra.AndroidFuture;
import com.google.android.icing.proto.DocumentProto;
-import com.google.android.icing.proto.SchemaProto;
import com.google.android.icing.proto.SearchResultProto;
-import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.StatusProto;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -133,19 +132,15 @@ public class AppSearchManager {
@NonNull
public AppSearchResult<Void> setSchema(
@NonNull List<AppSearchSchema> schemas, boolean forceOverride) {
- // Prepare the merged schema for transmission.
- SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
- for (AppSearchSchema schema : schemas) {
- schemaProtoBuilder.addTypes(schema.getProto());
- }
-
- // Serialize and send the schema.
// TODO: This should use com.android.internal.infra.RemoteStream or another mechanism to
// avoid binder limits.
- byte[] schemaBytes = schemaProtoBuilder.build().toByteArray();
+ List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
+ for (AppSearchSchema schema : schemas) {
+ schemaBundles.add(schema.getBundle());
+ }
AndroidFuture<AppSearchResult> future = new AndroidFuture<>();
try {
- mService.setSchema(schemaBytes, forceOverride, future);
+ mService.setSchema(schemaBundles, forceOverride, future);
} catch (RemoteException e) {
future.completeExceptionally(e);
}
@@ -298,13 +293,7 @@ public class AppSearchManager {
// them in one big list.
AndroidFuture<AppSearchResult> searchResultFuture = new AndroidFuture<>();
try {
- SearchSpecProto searchSpecProto = searchSpec.getSearchSpecProto();
- searchSpecProto = searchSpecProto.toBuilder().setQuery(queryExpression).build();
- mService.query(
- searchSpecProto.toByteArray(),
- searchSpec.getResultSpecProto().toByteArray(),
- searchSpec.getScoringSpecProto().toByteArray(),
- searchResultFuture);
+ mService.query(queryExpression, searchSpec.getBundle(), searchResultFuture);
} catch (RemoteException e) {
searchResultFuture.completeExceptionally(e);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
index 4b0b41b1a282..90e4df68f734 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 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.
@@ -16,18 +16,18 @@
package android.app.appsearch;
+import android.os.Bundle;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.util.ArraySet;
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.android.icing.proto.PropertyConfigProto;
-import com.google.android.icing.proto.SchemaTypeConfigProto;
-import com.google.android.icing.proto.TermMatchType;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+import android.util.ArraySet;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Set;
/**
@@ -36,45 +36,64 @@ import java.util.Set;
* <p>For example, an e-mail message or a music recording could be a schema type.
*
* <p>The schema consists of type information, properties, and config (like tokenization type).
- *
* @hide
*/
public final class AppSearchSchema {
- private final SchemaTypeConfigProto mProto;
+ /** @hide */
+
+ public static final String SCHEMA_TYPE_FIELD = "schemaType";
+
+ /** @hide */
+
+ public static final String PROPERTIES_FIELD = "properties";
- private AppSearchSchema(SchemaTypeConfigProto proto) {
- mProto = proto;
+ private final Bundle mBundle;
+
+ /** @hide */
+
+ public AppSearchSchema(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle);
+ mBundle = bundle;
}
/**
- * Returns the {@link SchemaTypeConfigProto} populated by this builder.
+ * Returns the {@link Bundle} populated by this builder.
* @hide
*/
+
@NonNull
- @VisibleForTesting
- public SchemaTypeConfigProto getProto() {
- return mProto;
+ public Bundle getBundle() {
+ return mBundle;
}
@Override
public String toString() {
- return mProto.toString();
+ return mBundle.toString();
}
/** Builder for {@link AppSearchSchema objects}. */
public static final class Builder {
- private final SchemaTypeConfigProto.Builder mProtoBuilder =
- SchemaTypeConfigProto.newBuilder();
+ private final String mTypeName;
+ private final ArrayList<Bundle> mProperties = new ArrayList<>();
+ private final Set<String> mPropertyNames = new ArraySet<>();
+ private boolean mBuilt = false;
/** Creates a new {@link AppSearchSchema.Builder}. */
public Builder(@NonNull String typeName) {
- mProtoBuilder.setSchemaType(typeName);
+ Preconditions.checkNotNull(typeName);
+ mTypeName = typeName;
}
/** Adds a property to the given type. */
@NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
- mProtoBuilder.addProperties(propertyConfig.mProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(propertyConfig);
+ if (!mPropertyNames.add(propertyConfig.mName)) {
+ throw new IllegalSchemaException(
+ "Property defined more than once: " + propertyConfig.mName);
+ }
+ mProperties.add(propertyConfig.mBundle);
return this;
}
@@ -85,15 +104,12 @@ public final class AppSearchSchema {
*/
@NonNull
public AppSearchSchema build() {
- Set<String> propertyNames = new ArraySet<>();
- for (PropertyConfigProto propertyConfigProto : mProtoBuilder.getPropertiesList()) {
- if (!propertyNames.add(propertyConfigProto.getPropertyName())) {
- throw new IllegalSchemaException(
- "Property defined more than once: "
- + propertyConfigProto.getPropertyName());
- }
- }
- return new AppSearchSchema(mProtoBuilder.build());
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Bundle bundle = new Bundle();
+ bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mTypeName);
+ bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mProperties);
+ mBuilt = true;
+ return new AppSearchSchema(bundle);
}
}
@@ -104,10 +120,37 @@ public final class AppSearchSchema {
* a property.
*/
public static final class PropertyConfig {
- /** Physical data-types of the contents of the property. */
+ /** @hide */
+
+ public static final String NAME_FIELD = "name";
+
+ /** @hide */
+
+ public static final String DATA_TYPE_FIELD = "dataType";
+
+ /** @hide */
+
+ public static final String SCHEMA_TYPE_FIELD = "schemaType";
+
+ /** @hide */
+
+ public static final String CARDINALITY_FIELD = "cardinality";
+
+ /** @hide */
+
+ public static final String INDEXING_TYPE_FIELD = "indexingType";
+
+ /** @hide */
+
+ public static final String TOKENIZER_TYPE_FIELD = "tokenizerType";
+
+ /**
+ * Physical data-types of the contents of the property.
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
- @IntDef(prefix = {"DATA_TYPE_"}, value = {
+ @IntDef(value = {
DATA_TYPE_STRING,
DATA_TYPE_INT64,
DATA_TYPE_DOUBLE,
@@ -133,10 +176,13 @@ public final class AppSearchSchema {
*/
public static final int DATA_TYPE_DOCUMENT = 6;
- /** The cardinality of the property (whether it is required, optional or repeated). */
+ /**
+ * The cardinality of the property (whether it is required, optional or repeated).
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
- @IntDef(prefix = {"CARDINALITY_"}, value = {
+ @IntDef(value = {
CARDINALITY_REPEATED,
CARDINALITY_OPTIONAL,
CARDINALITY_REQUIRED,
@@ -153,8 +199,11 @@ public final class AppSearchSchema {
/** Exactly one value [1]. */
public static final int CARDINALITY_REQUIRED = 3;
- /** Encapsulates the configurations on how AppSearch should query/index these terms. */
- @IntDef(prefix = {"INDEXING_TYPE_"}, value = {
+ /**
+ * Encapsulates the configurations on how AppSearch should query/index these terms.
+ * @hide
+ */
+ @IntDef(value = {
INDEXING_TYPE_NONE,
INDEXING_TYPE_EXACT_TERMS,
INDEXING_TYPE_PREFIXES,
@@ -188,10 +237,13 @@ public final class AppSearchSchema {
*/
public static final int INDEXING_TYPE_PREFIXES = 2;
- /** Configures how tokens should be extracted from this property. */
+ /**
+ * Configures how tokens should be extracted from this property.
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
- @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = {
+ @IntDef(value = {
TOKENIZER_TYPE_NONE,
TOKENIZER_TYPE_PLAIN,
})
@@ -207,15 +259,17 @@ public final class AppSearchSchema {
/** Tokenization for plain text. */
public static final int TOKENIZER_TYPE_PLAIN = 1;
- private final PropertyConfigProto mProto;
+ final String mName;
+ final Bundle mBundle;
- private PropertyConfig(PropertyConfigProto proto) {
- mProto = proto;
+ PropertyConfig(@NonNull String name, @NonNull Bundle bundle) {
+ mName = Preconditions.checkNotNull(name);
+ mBundle = Preconditions.checkNotNull(bundle);
}
@Override
public String toString() {
- return mProto.toString();
+ return mBundle.toString();
}
/**
@@ -232,15 +286,14 @@ public final class AppSearchSchema {
* is also required.
*/
public static final class Builder {
- private final PropertyConfigProto.Builder mPropertyConfigProto =
- PropertyConfigProto.newBuilder();
- private final com.google.android.icing.proto.IndexingConfig.Builder
- mIndexingConfigProto =
- com.google.android.icing.proto.IndexingConfig.newBuilder();
+ private final String mName;
+ private final Bundle mBundle = new Bundle();
+ private boolean mBuilt = false;
/** Creates a new {@link PropertyConfig.Builder}. */
public Builder(@NonNull String propertyName) {
- mPropertyConfigProto.setPropertyName(propertyName);
+ mName = Preconditions.checkNotNull(propertyName);
+ mBundle.putString(NAME_FIELD, propertyName);
}
/**
@@ -250,24 +303,24 @@ public final class AppSearchSchema {
*/
@NonNull
public PropertyConfig.Builder setDataType(@DataType int dataType) {
- PropertyConfigProto.DataType.Code dataTypeProto =
- PropertyConfigProto.DataType.Code.forNumber(dataType);
- if (dataTypeProto == null) {
- throw new IllegalArgumentException("Invalid dataType: " + dataType);
- }
- mPropertyConfigProto.setDataType(dataTypeProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(
+ dataType, DATA_TYPE_STRING, DATA_TYPE_DOCUMENT, "dataType");
+ mBundle.putInt(DATA_TYPE_FIELD, dataType);
return this;
}
/**
* The logical schema-type of the contents of this property.
*
- * <p>Only required when {@link #setDataType(int)} is set to
+ * <p>Only required when {@link #setDataType} is set to
* {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
*/
@NonNull
public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
- mPropertyConfigProto.setSchemaType(schemaType);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkNotNull(schemaType);
+ mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
return this;
}
@@ -278,12 +331,10 @@ public final class AppSearchSchema {
*/
@NonNull
public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
- PropertyConfigProto.Cardinality.Code cardinalityProto =
- PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
- if (cardinalityProto == null) {
- throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
- }
- mPropertyConfigProto.setCardinality(cardinalityProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(
+ cardinality, CARDINALITY_REPEATED, CARDINALITY_REQUIRED, "cardinality");
+ mBundle.putInt(CARDINALITY_FIELD, cardinality);
return this;
}
@@ -292,35 +343,20 @@ public final class AppSearchSchema {
*/
@NonNull
public PropertyConfig.Builder setIndexingType(@IndexingType int indexingType) {
- TermMatchType.Code termMatchTypeProto;
- switch (indexingType) {
- case INDEXING_TYPE_NONE:
- termMatchTypeProto = TermMatchType.Code.UNKNOWN;
- break;
- case INDEXING_TYPE_EXACT_TERMS:
- termMatchTypeProto = TermMatchType.Code.EXACT_ONLY;
- break;
- case INDEXING_TYPE_PREFIXES:
- termMatchTypeProto = TermMatchType.Code.PREFIX;
- break;
- default:
- throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
- }
- mIndexingConfigProto.setTermMatchType(termMatchTypeProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(
+ indexingType, INDEXING_TYPE_NONE, INDEXING_TYPE_PREFIXES, "indexingType");
+ mBundle.putInt(INDEXING_TYPE_FIELD, indexingType);
return this;
}
/** Configures how this property should be tokenized (split into words). */
@NonNull
public PropertyConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
- com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
- tokenizerTypeProto =
- com.google.android.icing.proto.IndexingConfig
- .TokenizerType.Code.forNumber(tokenizerType);
- if (tokenizerTypeProto == null) {
- throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
- }
- mIndexingConfigProto.setTokenizerType(tokenizerTypeProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(
+ tokenizerType, TOKENIZER_TYPE_NONE, TOKENIZER_TYPE_PLAIN, "tokenizerType");
+ mBundle.putInt(TOKENIZER_TYPE_FIELD, tokenizerType);
return this;
}
@@ -334,25 +370,23 @@ public final class AppSearchSchema {
*/
@NonNull
public PropertyConfig build() {
- mPropertyConfigProto.setIndexingConfig(mIndexingConfigProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
// TODO(b/147692920): Send the schema to Icing Lib for official validation, instead
// of partially reimplementing some of the validation Icing does here.
- if (mPropertyConfigProto.getDataType()
- == PropertyConfigProto.DataType.Code.UNKNOWN) {
+ if (!mBundle.containsKey(DATA_TYPE_FIELD)) {
throw new IllegalSchemaException("Missing field: dataType");
}
- if (mPropertyConfigProto.getSchemaType().isEmpty()
- && mPropertyConfigProto.getDataType()
- == PropertyConfigProto.DataType.Code.DOCUMENT) {
+ if (mBundle.getString(SCHEMA_TYPE_FIELD, "").isEmpty()
+ && mBundle.getInt(DATA_TYPE_FIELD) == DATA_TYPE_DOCUMENT) {
throw new IllegalSchemaException(
"Missing field: schemaType (required for configs with "
+ "dataType = DOCUMENT)");
}
- if (mPropertyConfigProto.getCardinality()
- == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
+ if (!mBundle.containsKey(CARDINALITY_FIELD)) {
throw new IllegalSchemaException("Missing field: cardinality");
}
- return new PropertyConfig(mPropertyConfigProto.build());
+ mBuilt = true;
+ return new PropertyConfig(mName, mBundle);
}
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index 68de4f068470..c710a29919e3 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -15,6 +15,8 @@
*/
package android.app.appsearch;
+import android.os.Bundle;
+
import com.android.internal.infra.AndroidFuture;
parcelable AppSearchResult;
@@ -25,14 +27,16 @@ interface IAppSearchManager {
/**
* Sets the schema.
*
- * @param schemaBytes Serialized SchemaProto.
+ * @param schemaBundles List of AppSearchSchema bundles.
* @param forceOverride Whether to apply the new schema even if it is incompatible. All
* incompatible documents will be deleted.
* @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link Void}&gt&gt;.
* The results of the call.
*/
void setSchema(
- in byte[] schemaBytes, boolean forceOverride, in AndroidFuture<AppSearchResult> callback);
+ in List<Bundle> schemaBundles,
+ boolean forceOverride,
+ in AndroidFuture<AppSearchResult> callback);
/**
* Inserts documents into the index.
@@ -63,14 +67,14 @@ interface IAppSearchManager {
/**
* Searches a document based on a given specifications.
*
- * @param searchSpecBytes Serialized SearchSpecProto.
- * @param resultSpecBytes Serialized SearchResultsProto.
- * @param scoringSpecBytes Serialized ScoringSpecProto.
+ * @param queryExpression String to search for
+ * @param searchSpecBundle SearchSpec bundle
* @param callback {@link AndroidFuture}&lt;{@link AppSearchResult}&lt;{@link byte[]}&gt;&gt;
* Will be completed with a serialized {@link SearchResultsProto}.
*/
void query(
- in byte[] searchSpecBytes, in byte[] resultSpecBytes, in byte[] scoringSpecBytes,
+ in String queryExpression,
+ in Bundle searchSpecBundle,
in AndroidFuture<AppSearchResult> callback);
/**
diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
index 6e644ffb8b27..817c3ef5e028 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/SearchSpec.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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.
@@ -16,18 +16,17 @@
package android.app.appsearch;
+import android.os.Bundle;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
-import com.google.android.icing.proto.ResultSpecProto;
-import com.google.android.icing.proto.ScoringSpecProto;
-import com.google.android.icing.proto.SearchSpecProto;
-import com.google.android.icing.proto.TermMatchType;
+import android.app.appsearch.exceptions.IllegalSearchSpecException;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
/**
* This class represents the specification logic for AppSearch. It can be used to set the type of
* search, like prefix or exact only or apply filters to search for a specific schema type only etc.
@@ -35,67 +34,100 @@ import java.lang.annotation.RetentionPolicy;
*/
// TODO(sidchhabra) : AddResultSpec fields for Snippets etc.
public final class SearchSpec {
+ /** @hide */
+
+ public static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
- private final SearchSpecProto mSearchSpecProto;
- private final ResultSpecProto mResultSpecProto;
- private final ScoringSpecProto mScoringSpecProto;
+ /** @hide */
+
+ public static final String SCHEMA_TYPES_FIELD = "schemaType";
- private SearchSpec(@NonNull SearchSpecProto searchSpecProto,
- @NonNull ResultSpecProto resultSpecProto, @NonNull ScoringSpecProto scoringSpecProto) {
- mSearchSpecProto = searchSpecProto;
- mResultSpecProto = resultSpecProto;
- mScoringSpecProto = scoringSpecProto;
- }
+ /** @hide */
+
+ public static final String NAMESPACE_FIELD = "namespace";
- /** Creates a new {@link SearchSpec.Builder}. */
- @NonNull
- public static SearchSpec.Builder newBuilder() {
- return new SearchSpec.Builder();
- }
+ /** @hide */
+
+ public static final String NUM_PER_PAGE_FIELD = "numPerPage";
/** @hide */
- @NonNull
- SearchSpecProto getSearchSpecProto() {
- return mSearchSpecProto;
- }
+
+ public static final String RANKING_STRATEGY_FIELD = "rankingStrategy";
/** @hide */
- @NonNull
- ResultSpecProto getResultSpecProto() {
- return mResultSpecProto;
- }
+
+ public static final String ORDER_FIELD = "order";
+
+ /** @hide */
+
+ public static final String SNIPPET_COUNT_FIELD = "snippetCount";
+
+ /** @hide */
+
+ public static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty";
+
+ /** @hide */
+
+ public static final String MAX_SNIPPET_FIELD = "maxSnippet";
+
+ /** @hide */
+
+ public static final int DEFAULT_NUM_PER_PAGE = 10;
+
+ private static final int MAX_NUM_PER_PAGE = 10_000;
+ private static final int MAX_SNIPPET_COUNT = 10_000;
+ private static final int MAX_SNIPPET_PER_PROPERTY_COUNT = 10_000;
+ private static final int MAX_SNIPPET_SIZE_LIMIT = 10_000;
+
+ private final Bundle mBundle;
/** @hide */
+
+ public SearchSpec(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle);
+ mBundle = bundle;
+ }
+
+ /**
+ * Returns the {@link Bundle} populated by this builder.
+ * @hide
+ */
@NonNull
- ScoringSpecProto getScoringSpecProto() {
- return mScoringSpecProto;
+ public Bundle getBundle() {
+ return mBundle;
}
- /** Term Match Type for the query. */
+ /**
+ * Term Match Type for the query.
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link com.google.android.icing.proto.SearchSpecProto.termMatchType}
- @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
- TERM_MATCH_TYPE_EXACT_ONLY,
- TERM_MATCH_TYPE_PREFIX
+ @IntDef(value = {
+ TERM_MATCH_EXACT_ONLY,
+ TERM_MATCH_PREFIX
})
@Retention(RetentionPolicy.SOURCE)
- public @interface TermMatchTypeCode {}
+ public @interface TermMatchCode {}
/**
* Query terms will only match exact tokens in the index.
* <p>Ex. A query term "foo" will only match indexed token "foo", and not "foot" or "football".
*/
- public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
+ public static final int TERM_MATCH_EXACT_ONLY = 1;
/**
* Query terms will match indexed tokens when the query term is a prefix of the token.
* <p>Ex. A query term "foo" will match indexed tokens like "foo", "foot", and "football".
*/
- public static final int TERM_MATCH_TYPE_PREFIX = 2;
+ public static final int TERM_MATCH_PREFIX = 2;
- /** Ranking Strategy for query result.*/
+ /**
+ * Ranking Strategy for query result.
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.RankingStrategy.Code }
- @IntDef(prefix = {"RANKING_STRATEGY_"}, value = {
+ @IntDef(value = {
RANKING_STRATEGY_NONE,
RANKING_STRATEGY_DOCUMENT_SCORE,
RANKING_STRATEGY_CREATION_TIMESTAMP
@@ -110,10 +142,13 @@ public final class SearchSpec {
/** Ranked by document creation timestamps. */
public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2;
- /** Order for query result.*/
+ /**
+ * Order for query result.
+ * @hide
+ */
// NOTE: The integer values of these constants must match the proto enum constants in
// {@link ScoringSpecProto.Order.Code }
- @IntDef(prefix = {"ORDER_"}, value = {
+ @IntDef(value = {
ORDER_DESCENDING,
ORDER_ASCENDING
})
@@ -128,27 +163,24 @@ public final class SearchSpec {
/** Builder for {@link SearchSpec objects}. */
public static final class Builder {
- private final SearchSpecProto.Builder mSearchSpecBuilder = SearchSpecProto.newBuilder();
- private final ResultSpecProto.Builder mResultSpecBuilder = ResultSpecProto.newBuilder();
- private final ScoringSpecProto.Builder mScoringSpecBuilder = ScoringSpecProto.newBuilder();
- private final ResultSpecProto.SnippetSpecProto.Builder mSnippetSpecBuilder =
- ResultSpecProto.SnippetSpecProto.newBuilder();
+ private final Bundle mBundle;
+ private boolean mBuilt = false;
- private Builder() {
+ /** Creates a new {@link SearchSpec.Builder}. */
+ public Builder() {
+ mBundle = new Bundle();
+ mBundle.putInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
}
/**
- * Indicates how the query terms should match {@link TermMatchTypeCode} in the index.
+ * Indicates how the query terms should match {@code TermMatchCode} in the index.
*/
@NonNull
- public Builder setTermMatchType(@TermMatchTypeCode int termMatchTypeCode) {
- TermMatchType.Code termMatchTypeCodeProto =
- TermMatchType.Code.forNumber(termMatchTypeCode);
- if (termMatchTypeCodeProto == null) {
- throw new IllegalArgumentException("Invalid term match type: "
- + termMatchTypeCode);
- }
- mSearchSpecBuilder.setTermMatchType(termMatchTypeCodeProto);
+ public Builder setTermMatch(@TermMatchCode int termMatchTypeCode) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(termMatchTypeCode, TERM_MATCH_EXACT_ONLY,
+ TERM_MATCH_PREFIX, "Term match type");
+ mBundle.putInt(TERM_MATCH_TYPE_FIELD, termMatchTypeCode);
return this;
}
@@ -159,31 +191,44 @@ public final class SearchSpec {
*/
@NonNull
public Builder setSchemaTypes(@NonNull String... schemaTypes) {
- for (String schemaType : schemaTypes) {
- mSearchSpecBuilder.addSchemaTypeFilters(schemaType);
- }
+ Preconditions.checkNotNull(schemaTypes);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putStringArray(SCHEMA_TYPES_FIELD, schemaTypes);
return this;
}
- /** Sets the maximum number of results to retrieve from the query */
+ /**
+ * Adds a namespace filter to {@link SearchSpec} Entry. Only search for documents that
+ * have the specified namespaces.
+ * <p>If unset, the query will search over all namespaces.
+ */
@NonNull
- public SearchSpec.Builder setNumToRetrieve(int numToRetrieve) {
- // Just retrieve everything in one page.
- // TODO(b/152359656): Realign these two apis properly.
- mResultSpecBuilder.setNumPerPage(numToRetrieve);
+ public Builder setNamespaces(@NonNull String... namespaces) {
+ Preconditions.checkNotNull(namespaces);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ mBundle.putStringArray(NAMESPACE_FIELD, namespaces);
+ return this;
+ }
+
+ /**
+ * Sets the number of results per page in the returned object.
+ * <p> The default number of results per page is 10. And should be set in range [0, 10k].
+ */
+ @NonNull
+ public SearchSpec.Builder setNumPerPage(int numPerPage) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(numPerPage, 0, MAX_NUM_PER_PAGE, "NumPerPage");
+ mBundle.putInt(NUM_PER_PAGE_FIELD, numPerPage);
return this;
}
/** Sets ranking strategy for AppSearch results.*/
@NonNull
public Builder setRankingStrategy(@RankingStrategyCode int rankingStrategy) {
- ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
- ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategy);
- if (rankingStrategyCodeProto == null) {
- throw new IllegalArgumentException("Invalid result ranking strategy: "
- + rankingStrategyCodeProto);
- }
- mScoringSpecBuilder.setRankBy(rankingStrategyCodeProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(rankingStrategy, RANKING_STRATEGY_NONE,
+ RANKING_STRATEGY_CREATION_TIMESTAMP, "Result ranking strategy");
+ mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy);
return this;
}
@@ -194,37 +239,41 @@ public final class SearchSpec {
*/
@NonNull
public Builder setOrder(@OrderCode int order) {
- ScoringSpecProto.Order.Code orderCodeProto =
- ScoringSpecProto.Order.Code.forNumber(order);
- if (orderCodeProto == null) {
- throw new IllegalArgumentException("Invalid result ranking order: "
- + orderCodeProto);
- }
- mScoringSpecBuilder.setOrderBy(orderCodeProto);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(order, ORDER_DESCENDING, ORDER_ASCENDING,
+ "Result ranking order");
+ mBundle.putInt(ORDER_FIELD, order);
return this;
}
/**
- * Only the first {@code numToSnippet} documents based on the ranking strategy
+ * Only the first {@code snippetCount} documents based on the ranking strategy
* will have snippet information provided.
* <p>If set to 0 (default), snippeting is disabled and
- * {@link SearchResults.Result#getMatchInfo} will return {@code null} for that result.
+ * {@link SearchResults.Result#getMatches} will return {@code null} for that result.
+ * <p>The value should be set in range[0, 10k].
*/
@NonNull
- public SearchSpec.Builder setNumToSnippet(int numToSnippet) {
- mSnippetSpecBuilder.setNumToSnippet(numToSnippet);
+ public SearchSpec.Builder setSnippetCount(int snippetCount) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(snippetCount, 0, MAX_SNIPPET_COUNT, "snippetCount");
+ mBundle.putInt(SNIPPET_COUNT_FIELD, snippetCount);
return this;
}
/**
- * Only the first {@code numMatchesPerProperty} matches for a every property of
- * {@link AppSearchDocument} will contain snippet information.
- * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatchInfo}
+ * Only the first {@code matchesCountPerProperty} matches for a every property of
+ * {@link GenericDocument} will contain snippet information.
+ * <p>If set to 0, snippeting is disabled and {@link SearchResults.Result#getMatches}
* will return {@code null} for that result.
+ * <p>The value should be set in range[0, 10k].
*/
@NonNull
- public SearchSpec.Builder setNumMatchesPerProperty(int numMatchesPerProperty) {
- mSnippetSpecBuilder.setNumMatchesPerProperty(numMatchesPerProperty);
+ public SearchSpec.Builder setSnippetCountPerProperty(int snippetCountPerProperty) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(snippetCountPerProperty,
+ 0, MAX_SNIPPET_PER_PROPERTY_COUNT, "snippetCountPerProperty");
+ mBundle.putInt(SNIPPET_COUNT_PER_PROPERTY_FIELD, snippetCountPerProperty);
return this;
}
@@ -237,10 +286,14 @@ public final class SearchSpec {
* be returned. If matches enabled is also set to false, then snippeting is disabled.
* <p>Ex. {@code maxSnippetSize} = 16. "foo bar baz bat rat" with a query of "baz" will
* return a window of "bar baz bat" which is only 11 bytes long.
+ * <p>The value should be in range[0, 10k].
*/
@NonNull
public SearchSpec.Builder setMaxSnippetSize(int maxSnippetSize) {
- mSnippetSpecBuilder.setMaxWindowBytes(maxSnippetSize);
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ Preconditions.checkArgumentInRange(
+ maxSnippetSize, 0, MAX_SNIPPET_SIZE_LIMIT, "maxSnippetSize");
+ mBundle.putInt(MAX_SNIPPET_FIELD, maxSnippetSize);
return this;
}
@@ -251,12 +304,12 @@ public final class SearchSpec {
*/
@NonNull
public SearchSpec build() {
- if (mSearchSpecBuilder.getTermMatchType() == TermMatchType.Code.UNKNOWN) {
+ Preconditions.checkState(!mBuilt, "Builder has already been used");
+ if (!mBundle.containsKey(TERM_MATCH_TYPE_FIELD)) {
throw new IllegalSearchSpecException("Missing termMatchType field.");
}
- mResultSpecBuilder.setSnippetSpec(mSnippetSpecBuilder);
- return new SearchSpec(mSearchSpecBuilder.build(), mResultSpecBuilder.build(),
- mScoringSpecBuilder.build());
+ mBuilt = true;
+ return new SearchSpec(mBundle);
}
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
index 9b705ceb80de..00f6e75afe54 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/AppSearchException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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.
@@ -14,31 +14,38 @@
* limitations under the License.
*/
-package com.android.server.appsearch;
+package android.app.appsearch.exceptions;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.appsearch.AppSearchResult;
/**
- * An exception thrown by {@link com.android.server.appsearch.AppSearchManagerService} or a
- * subcomponent.
+ * An exception thrown by {@code android.app.appsearch.AppSearchManager} or a subcomponent.
*
- * <p>These exceptions can be converted into a failed {@link android.app.appsearch.AppSearchResult}
+ * <p>These exceptions can be converted into a failed {@link AppSearchResult}
* for propagating to the client.
+ * @hide
*/
+//TODO(b/157082794): Linkify to AppSearchManager once that API is public
public class AppSearchException extends Exception {
private final @AppSearchResult.ResultCode int mResultCode;
- /** Initializes an {@link com.android.server.appsearch.AppSearchException} with no message. */
+ /**
+ * Initializes an {@link AppSearchException} with no message.
+ * @hide
+ */
public AppSearchException(@AppSearchResult.ResultCode int resultCode) {
this(resultCode, /*message=*/ null);
}
+ /** @hide */
public AppSearchException(
@AppSearchResult.ResultCode int resultCode, @Nullable String message) {
this(resultCode, message, /*cause=*/ null);
}
+ /** @hide */
public AppSearchException(
@AppSearchResult.ResultCode int resultCode,
@Nullable String message,
@@ -48,9 +55,9 @@ public class AppSearchException extends Exception {
}
/**
- * Converts this {@link java.lang.Exception} into a failed
- * {@link android.app.appsearch.AppSearchResult}
+ * Converts this {@link java.lang.Exception} into a failed {@link AppSearchResult}
*/
+ @NonNull
public <T> AppSearchResult<T> toAppSearchResult() {
return AppSearchResult.newFailedResult(mResultCode, getMessage());
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
index f9e528cd2951..6dd86f5e6de1 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSchemaException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 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.
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-package android.app.appsearch;
+package android.app.appsearch.exceptions;
import android.annotation.NonNull;
+
/**
* Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
* as unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
+
public class IllegalSchemaException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSchemaException}.
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
index 0d029f029ee5..3ef887f09eab 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IllegalSearchSpecException.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/exceptions/IllegalSearchSpecException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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.
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-package android.app.appsearch;
+package android.app.appsearch.exceptions;
import android.annotation.NonNull;
+
/**
- * Indicates that a {@link android.app.appsearch.SearchResults} has logical inconsistencies such
+ * Indicates that a {@link android.app.appsearch.SearchResult} has logical inconsistencies such
* as unpopulated mandatory fields or illegal combinations of parameters.
*
* @hide
*/
+
public class IllegalSearchSpecException extends IllegalArgumentException {
/**
* Constructs a new {@link IllegalSearchSpecException}.
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index c125f5686f0b..fc1d707edd86 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -20,7 +20,13 @@ java_library {
"framework-appsearch",
"services.core",
],
- static_libs: ["icing-java-proto-lite"],
+ static_libs: [
+ "icing-java-proto-lite",
+ "libicing-java",
+ ],
+ required: [
+ "libicing",
+ ],
jarjar_rules: "jarjar-rules.txt",
apex_available: ["com.android.appsearch"],
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 16948b257392..4bc0c39bb6c9 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -17,25 +17,29 @@ package com.android.server.appsearch;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchDocument;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.IAppSearchManager;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.UserHandle;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
-import com.android.server.appsearch.impl.AppSearchImpl;
-import com.android.server.appsearch.impl.ImplInstanceManager;
+import com.android.server.appsearch.external.localbackend.AppSearchImpl;
+import com.android.server.appsearch.external.localbackend.converter.SchemaToProtoConverter;
+import com.android.server.appsearch.external.localbackend.converter.SearchSpecToProtoConverter;
import com.google.android.icing.proto.DocumentProto;
-import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.SchemaProto;
-import com.google.android.icing.proto.ScoringSpecProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchSpecProto;
-import com.google.android.icing.proto.StatusProto;
import java.io.IOException;
import java.util.List;
@@ -44,6 +48,7 @@ import java.util.List;
* TODO(b/142567528): add comments when implement this class
*/
public class AppSearchManagerService extends SystemService {
+ private static final String TAG = "AppSearchManagerService";
public AppSearchManagerService(Context context) {
super(context);
@@ -57,18 +62,24 @@ public class AppSearchManagerService extends SystemService {
private class Stub extends IAppSearchManager.Stub {
@Override
public void setSchema(
- @NonNull byte[] schemaBytes,
+ @NonNull List<Bundle> schemaBundles,
boolean forceOverride,
@NonNull AndroidFuture<AppSearchResult> callback) {
- Preconditions.checkNotNull(schemaBytes);
+ Preconditions.checkNotNull(schemaBundles);
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
- SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
+ SchemaProto.Builder schemaProtoBuilder = SchemaProto.newBuilder();
+ for (int i = 0; i < schemaBundles.size(); i++) {
+ AppSearchSchema schema = new AppSearchSchema(schemaBundles.get(i));
+ SchemaTypeConfigProto schemaTypeProto = SchemaToProtoConverter.convert(schema);
+ schemaProtoBuilder.addTypes(schemaTypeProto);
+ }
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- impl.setSchema(callingUid, schema, forceOverride);
+ String databaseName = makeDatabaseName(callingUid);
+ impl.setSchema(databaseName, schemaProtoBuilder.build(), forceOverride);
callback.complete(AppSearchResult.newSuccessfulResult(/*value=*/ null));
} catch (Throwable t) {
callback.complete(throwableToFailedResult(t));
@@ -85,16 +96,17 @@ public class AppSearchManagerService extends SystemService {
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ String databaseName = makeDatabaseName(callingUid);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
for (int i = 0; i < documentsBytes.size(); i++) {
byte[] documentBytes = (byte[]) documentsBytes.get(i);
DocumentProto document = DocumentProto.parseFrom(documentBytes);
try {
- impl.putDocument(callingUid, document);
+ impl.putDocument(databaseName, document);
resultBuilder.setSuccess(document.getUri(), /*value=*/ null);
} catch (Throwable t) {
resultBuilder.setResult(document.getUri(), throwableToFailedResult(t));
@@ -115,15 +127,17 @@ public class AppSearchManagerService extends SystemService {
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ String databaseName = makeDatabaseName(callingUid);
AppSearchBatchResult.Builder<String, byte[]> resultBuilder =
new AppSearchBatchResult.Builder<>();
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
- DocumentProto document = impl.getDocument(callingUid, uri);
+ DocumentProto document = impl.getDocument(
+ databaseName, AppSearchDocument.DEFAULT_NAMESPACE, uri);
if (document == null) {
resultBuilder.setFailure(
uri, AppSearchResult.RESULT_NOT_FOUND, /*errorMessage=*/ null);
@@ -145,35 +159,31 @@ public class AppSearchManagerService extends SystemService {
// TODO(sidchhabra): Do this in a threadpool.
@Override
public void query(
- @NonNull byte[] searchSpecBytes,
- @NonNull byte[] resultSpecBytes,
- @NonNull byte[] scoringSpecBytes,
+ @NonNull String queryExpression,
+ @NonNull Bundle searchSpecBundle,
@NonNull AndroidFuture<AppSearchResult> callback) {
- Preconditions.checkNotNull(searchSpecBytes);
- Preconditions.checkNotNull(resultSpecBytes);
- Preconditions.checkNotNull(scoringSpecBytes);
+ Preconditions.checkNotNull(queryExpression);
+ Preconditions.checkNotNull(searchSpecBundle);
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
- SearchSpecProto searchSpecProto = SearchSpecProto.parseFrom(searchSpecBytes);
- ResultSpecProto resultSpecProto = ResultSpecProto.parseFrom(resultSpecBytes);
- ScoringSpecProto scoringSpecProto = ScoringSpecProto.parseFrom(scoringSpecBytes);
+ SearchSpec searchSpec = new SearchSpec(searchSpecBundle);
+ SearchSpecProto searchSpecProto =
+ SearchSpecToProtoConverter.toSearchSpecProto(searchSpec);
+ searchSpecProto = searchSpecProto.toBuilder()
+ .setQuery(queryExpression).build();
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- SearchResultProto searchResultProto =
- impl.query(callingUid, searchSpecProto, resultSpecProto, scoringSpecProto);
- // TODO(sidchhabra): Translate SearchResultProto errors into error codes. This might
- // better be done in AppSearchImpl by throwing an AppSearchException.
- if (searchResultProto.getStatus().getCode() != StatusProto.Code.OK) {
- callback.complete(
- AppSearchResult.newFailedResult(
- AppSearchResult.RESULT_INTERNAL_ERROR,
- searchResultProto.getStatus().getMessage()));
- } else {
- callback.complete(
- AppSearchResult.newSuccessfulResult(searchResultProto.toByteArray()));
- }
+ String databaseName = makeDatabaseName(callingUid);
+ // TODO(adorokhine): handle pagination
+ SearchResultProto searchResultProto = impl.query(
+ databaseName,
+ searchSpecProto,
+ SearchSpecToProtoConverter.toResultSpecProto(searchSpec),
+ SearchSpecToProtoConverter.toScoringSpecProto(searchSpec));
+ callback.complete(
+ AppSearchResult.newSuccessfulResult(searchResultProto.toByteArray()));
} catch (Throwable t) {
callback.complete(throwableToFailedResult(t));
} finally {
@@ -187,20 +197,17 @@ public class AppSearchManagerService extends SystemService {
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ String databaseName = makeDatabaseName(callingUid);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
for (int i = 0; i < uris.size(); i++) {
String uri = uris.get(i);
try {
- if (!impl.delete(callingUid, uri)) {
- resultBuilder.setFailure(
- uri, AppSearchResult.RESULT_NOT_FOUND, /*errorMessage=*/ null);
- } else {
- resultBuilder.setSuccess(uri, /*value= */null);
- }
+ impl.remove(databaseName, AppSearchDocument.DEFAULT_NAMESPACE, uri);
+ resultBuilder.setSuccess(uri, /*value= */null);
} catch (Throwable t) {
resultBuilder.setResult(uri, throwableToFailedResult(t));
}
@@ -220,22 +227,17 @@ public class AppSearchManagerService extends SystemService {
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ String databaseName = makeDatabaseName(callingUid);
AppSearchBatchResult.Builder<String, Void> resultBuilder =
new AppSearchBatchResult.Builder<>();
for (int i = 0; i < schemaTypes.size(); i++) {
String schemaType = schemaTypes.get(i);
try {
- if (!impl.deleteByType(callingUid, schemaType)) {
- resultBuilder.setFailure(
- schemaType,
- AppSearchResult.RESULT_NOT_FOUND,
- /*errorMessage=*/ null);
- } else {
- resultBuilder.setSuccess(schemaType, /*value=*/ null);
- }
+ impl.removeByType(databaseName, schemaType);
+ resultBuilder.setSuccess(schemaType, /*value=*/ null);
} catch (Throwable t) {
resultBuilder.setResult(schemaType, throwableToFailedResult(t));
}
@@ -253,10 +255,11 @@ public class AppSearchManagerService extends SystemService {
Preconditions.checkNotNull(callback);
int callingUid = Binder.getCallingUidOrThrow();
int callingUserId = UserHandle.getUserId(callingUid);
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
- impl.deleteAll(callingUid);
+ String databaseName = makeDatabaseName(callingUid);
+ impl.removeAll(databaseName);
callback.complete(AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
callback.complete(throwableToFailedResult(t));
@@ -265,6 +268,25 @@ public class AppSearchManagerService extends SystemService {
}
}
+ /**
+ * Returns a unique database name for the given uid.
+ *
+ * <p>The current implementation returns the package name of the app with this uid in a
+ * format like {@code com.example.package} or {@code com.example.sharedname:5678}.
+ */
+ @NonNull
+ private String makeDatabaseName(int callingUid) {
+ // For regular apps, this call will return the package name. If callingUid is an
+ // android:sharedUserId, this value may be another type of name and have a :uid suffix.
+ String callingUidName = getContext().getPackageManager().getNameForUid(callingUid);
+ if (callingUidName == null) {
+ // Not sure how this is possible --- maybe app was uninstalled?
+ throw new IllegalStateException(
+ "Failed to look up package name for uid " + callingUid);
+ }
+ return callingUidName;
+ }
+
private <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
@NonNull Throwable t) {
if (t instanceof AppSearchException) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index 395e30e89dc0..c1e6b0fd205f 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -14,21 +14,32 @@
* limitations under the License.
*/
-package com.android.server.appsearch.impl;
+package com.android.server.appsearch;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
+import android.os.Environment;
+import android.os.storage.StorageManager;
import android.util.SparseArray;
+import com.android.server.appsearch.external.localbackend.AppSearchImpl;
+
+import java.io.File;
+
/**
* Manages the lifecycle of instances of {@link AppSearchImpl}.
*
* <p>These instances are managed per unique device-user.
*/
public final class ImplInstanceManager {
+ private static final String APP_SEARCH_DIR = "appSearch";
+
private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+ private ImplInstanceManager() {}
+
/**
* Gets an instance of AppSearchImpl for the given user.
*
@@ -40,17 +51,33 @@ public final class ImplInstanceManager {
* @return An initialized {@link AppSearchImpl} for this user
*/
@NonNull
- public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) {
+ public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId)
+ throws AppSearchException {
AppSearchImpl instance = sInstances.get(userId);
if (instance == null) {
synchronized (ImplInstanceManager.class) {
instance = sInstances.get(userId);
if (instance == null) {
- instance = new AppSearchImpl(context, userId);
+ instance = createImpl(context, userId);
sInstances.put(userId, instance);
}
}
}
return instance;
}
+
+ private static AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
+ throws AppSearchException {
+ File appSearchDir = getAppSearchDir(context, userId);
+ AppSearchImpl appSearchImpl = new AppSearchImpl(appSearchDir);
+ appSearchImpl.initialize();
+ return appSearchImpl;
+ }
+
+ private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
+ // See com.android.internal.app.ChooserActivity::getPinnedSharedPrefs
+ File userCeDir = Environment.getDataUserCePackageDirectory(
+ StorageManager.UUID_PRIVATE_INTERNAL, userId, context.getPackageName());
+ return new File(userCeDir, APP_SEARCH_DIR);
+ }
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java
new file mode 100644
index 000000000000..462f45869b34
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/AppSearchImpl.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright 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.appsearch.external.localbackend;
+
+import android.util.Log;
+
+import android.annotation.AnyThread;
+import com.android.internal.annotations.GuardedBy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import android.annotation.WorkerThread;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.exceptions.AppSearchException;
+
+import com.google.android.icing.IcingSearchEngine;
+import com.google.android.icing.proto.DeleteByNamespaceResultProto;
+import com.google.android.icing.proto.DeleteBySchemaTypeResultProto;
+import com.google.android.icing.proto.DeleteResultProto;
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.GetAllNamespacesResultProto;
+import com.google.android.icing.proto.GetOptimizeInfoResultProto;
+import com.google.android.icing.proto.GetResultProto;
+import com.google.android.icing.proto.GetSchemaResultProto;
+import com.google.android.icing.proto.IcingSearchEngineOptions;
+import com.google.android.icing.proto.InitializeResultProto;
+import com.google.android.icing.proto.OptimizeResultProto;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.PutResultProto;
+import com.google.android.icing.proto.ResetResultProto;
+import com.google.android.icing.proto.ResultSpecProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.ScoringSpecProto;
+import com.google.android.icing.proto.SearchResultProto;
+import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.proto.SetSchemaResultProto;
+import com.google.android.icing.proto.StatusProto;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Manages interaction with the native IcingSearchEngine and other components to implement AppSearch
+ * functionality.
+ *
+ * <p>Callers should call {@link #initialize} before using the AppSearchImpl instance. Never create
+ * two instances using the same folder.
+ *
+ * <p>A single instance of {@link AppSearchImpl} can support all databases. Schemas and documents
+ * are physically saved together in {@link IcingSearchEngine}, but logically isolated:
+ * <ul>
+ * <li>Rewrite SchemaType in SchemaProto by adding database name prefix and save into
+ * SchemaTypes set in {@link #setSchema}.
+ * <li>Rewrite namespace and SchemaType in DocumentProto by adding database name prefix and
+ * save to namespaces set in {@link #putDocument}.
+ * <li>Remove database name prefix when retrieve documents in {@link #getDocument} and
+ * {@link #query}.
+ * <li>Rewrite filters in {@link SearchSpecProto} to have all namespaces and schema types of
+ * the queried database when user using empty filters in {@link #query}.
+ * </ul>
+ *
+ * <p>Methods in this class belong to two groups, the query group and the mutate group.
+ * <ul>
+ * <li>All methods are going to modify global parameters and data in Icing are executed under
+ * WRITE lock to keep thread safety.
+ * <li>All methods are going to access global parameters or query data from Icing are executed
+ * under READ lock to improve query performance.
+ * </ul>
+ *
+ * <p>This class is thread safe.
+ * @hide
+ */
+
+@WorkerThread
+public final class AppSearchImpl {
+ private static final String TAG = "AppSearchImpl";
+
+ @VisibleForTesting
+ static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
+ @VisibleForTesting
+ static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
+ @VisibleForTesting
+ static final int CHECK_OPTIMIZE_INTERVAL = 100;
+
+ private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
+ private final CountDownLatch mInitCompleteLatch = new CountDownLatch(1);
+ private final File mIcingDir;
+ private IcingSearchEngine mIcingSearchEngine;
+
+ // The map contains schemaTypes and namespaces for all database. All values in the map have
+ // been already added database name prefix.
+ private final Map<String, Set<String>> mSchemaMap = new HashMap<>();
+ private final Map<String, Set<String>> mNamespaceMap = new HashMap<>();
+
+ /**
+ * The counter to check when to call {@link #checkForOptimize(boolean)}. The interval is
+ * {@link #CHECK_OPTIMIZE_INTERVAL}.
+ */
+ private int mOptimizeIntervalCount = 0;
+
+ /** Creates an instance of {@link AppSearchImpl} which writes data to the given folder. */
+ @AnyThread
+ public AppSearchImpl(@NonNull File icingDir) {
+ mIcingDir = icingDir;
+ }
+
+ /**
+ * Initializes the underlying IcingSearchEngine.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @throws AppSearchException on IcingSearchEngine error.
+ */
+ public void initialize() throws AppSearchException {
+ if (isInitialized()) {
+ return;
+ }
+ boolean isReset = false;
+ mReadWriteLock.writeLock().lock();
+ try {
+ // We synchronize here because we don't want to call IcingSearchEngine.initialize() more
+ // than once. It's unnecessary and can be a costly operation.
+ if (isInitialized()) {
+ return;
+ }
+ IcingSearchEngineOptions options = IcingSearchEngineOptions.newBuilder()
+ .setBaseDir(mIcingDir.getAbsolutePath()).build();
+ mIcingSearchEngine = new IcingSearchEngine(options);
+
+ InitializeResultProto initializeResultProto = mIcingSearchEngine.initialize();
+ SchemaProto schemaProto = null;
+ GetAllNamespacesResultProto getAllNamespacesResultProto = null;
+ try {
+ checkSuccess(initializeResultProto.getStatus());
+ schemaProto = getSchemaProto();
+ getAllNamespacesResultProto = mIcingSearchEngine.getAllNamespaces();
+ checkSuccess(getAllNamespacesResultProto.getStatus());
+ } catch (AppSearchException e) {
+ // Some error. Reset and see if it fixes it.
+ reset();
+ isReset = true;
+ }
+ for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
+ String qualifiedSchemaType = schema.getSchemaType();
+ addToMap(mSchemaMap, getDatabaseName(qualifiedSchemaType), qualifiedSchemaType);
+ }
+ for (String qualifiedNamespace : getAllNamespacesResultProto.getNamespacesList()) {
+ addToMap(mNamespaceMap, getDatabaseName(qualifiedNamespace), qualifiedNamespace);
+ }
+ mInitCompleteLatch.countDown();
+ if (!isReset) {
+ checkForOptimize(/* force= */ true);
+ }
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
+ /** Checks if the internal state of {@link AppSearchImpl} has been initialized. */
+ @AnyThread
+ public boolean isInitialized() {
+ return mInitCompleteLatch.getCount() == 0;
+ }
+
+ /**
+ * Updates the AppSearch schema for this app.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The name of the database where this schema lives.
+ * @param origSchema The schema to set for this app.
+ * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
+ * which do not comply with the new schema will be deleted.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void setSchema(@NonNull String databaseName, @NonNull SchemaProto origSchema,
+ boolean forceOverride) throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ SchemaProto schemaProto = getSchemaProto();
+
+ SchemaProto.Builder existingSchemaBuilder = schemaProto.toBuilder();
+
+ // Combine the existing schema (which may have types from other databases) with this
+ // database's new schema. Modifies the existingSchemaBuilder.
+ Set<String> newTypeNames = rewriteSchema(databaseName, existingSchemaBuilder, origSchema);
+
+ SetSchemaResultProto setSchemaResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ setSchemaResultProto = mIcingSearchEngine.setSchema(existingSchemaBuilder.build(),
+ forceOverride);
+ checkSuccess(setSchemaResultProto.getStatus());
+ mSchemaMap.put(databaseName, newTypeNames);
+ if (setSchemaResultProto.getDeletedSchemaTypesCount() > 0
+ || (setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0
+ && forceOverride)) {
+ // Any existing schemas which is not in origSchema will be deleted, and all
+ // documents of these types were also deleted. And so well if we force override
+ // incompatible schemas.
+ checkForOptimize(/* force= */true);
+ }
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Adds a document to the AppSearch index.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The databaseName this document resides in.
+ * @param document The document to index.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void putDocument(@NonNull String databaseName, @NonNull DocumentProto document)
+ throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ DocumentProto.Builder documentBuilder = document.toBuilder();
+ rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ true);
+
+ PutResultProto putResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ putResultProto = mIcingSearchEngine.put(documentBuilder.build());
+ addToMap(mNamespaceMap, databaseName, documentBuilder.getNamespace());
+ // The existing documents with same URI will be deleted, so there maybe some resources
+ // could be released after optimize().
+ checkForOptimize(/* force= */false);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ checkSuccess(putResultProto.getStatus());
+ }
+
+ /**
+ * Retrieves a document from the AppSearch index by URI.
+ *
+ * <p>This method belongs to query group.
+ *
+ * @param databaseName The databaseName this document resides in.
+ * @param namespace The namespace this document resides in.
+ * @param uri The URI of the document to get.
+ * @return The Document contents, or {@code null} if no such URI exists in the system.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ @Nullable
+ public DocumentProto getDocument(@NonNull String databaseName, @NonNull String namespace,
+ @NonNull String uri) throws AppSearchException, InterruptedException {
+ awaitInitialized();
+ GetResultProto getResultProto;
+ mReadWriteLock.readLock().lock();
+ try {
+ getResultProto = mIcingSearchEngine.get(
+ getDatabasePrefix(databaseName) + namespace, uri);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
+ checkSuccess(getResultProto.getStatus());
+
+ DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
+ rewriteDocumentTypes(getDatabasePrefix(databaseName), documentBuilder, /*add=*/ false);
+ return documentBuilder.build();
+ }
+
+ /**
+ * Executes a query against the AppSearch index and returns results.
+ *
+ * <p>This method belongs to query group.
+ *
+ * @param databaseName The databaseName this query for.
+ * @param searchSpec Defines what and how to search
+ * @param resultSpec Defines what results to show
+ * @param scoringSpec Defines how to order results
+ * @return The results of performing this search The proto might have no {@code results} if no
+ * documents matched the query.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ @NonNull
+ public SearchResultProto query(
+ @NonNull String databaseName,
+ @NonNull SearchSpecProto searchSpec,
+ @NonNull ResultSpecProto resultSpec,
+ @NonNull ScoringSpecProto scoringSpec) throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ SearchSpecProto.Builder searchSpecBuilder = searchSpec.toBuilder();
+ SearchResultProto searchResultProto;
+ mReadWriteLock.readLock().lock();
+ try {
+ // Only rewrite SearchSpec for non empty database.
+ // rewriteSearchSpecForNonEmptyDatabase will return false for empty database, we
+ // should just return an empty SearchResult and skip sending request to Icing.
+ if (!rewriteSearchSpecForNonEmptyDatabase(databaseName, searchSpecBuilder)) {
+ return SearchResultProto.newBuilder()
+ .setStatus(StatusProto.newBuilder()
+ .setCode(StatusProto.Code.OK)
+ .build())
+ .build();
+ }
+ searchResultProto = mIcingSearchEngine.search(
+ searchSpecBuilder.build(), scoringSpec, resultSpec);
+ } finally {
+ mReadWriteLock.readLock().unlock();
+ }
+ checkSuccess(searchResultProto.getStatus());
+ if (searchResultProto.getResultsCount() == 0) {
+ return searchResultProto;
+ }
+ return rewriteSearchResultProto(databaseName, searchResultProto);
+ }
+
+ /**
+ * Fetches the next page of results of a previously executed query. Results can be empty if
+ * next-page token is invalid or all pages have been returned.
+ *
+ * @param databaseName The databaseName of the previously executed query.
+ * @param nextPageToken The token of pre-loaded results of previously executed query.
+ * @return The next page of results of previously executed query.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ @NonNull
+ public SearchResultProto getNextPage(@NonNull String databaseName, long nextPageToken)
+ throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ SearchResultProto searchResultProto = mIcingSearchEngine.getNextPage(nextPageToken);
+ checkSuccess(searchResultProto.getStatus());
+ if (searchResultProto.getResultsCount() == 0) {
+ return searchResultProto;
+ }
+ return rewriteSearchResultProto(databaseName, searchResultProto);
+ }
+
+ /**
+ * Invalidates the next-page token so that no more results of the related query can be returned.
+ * @param nextPageToken The token of pre-loaded results of previously executed query to be
+ * Invalidated.
+ */
+ public void invalidateNextPageToken(long nextPageToken) throws InterruptedException {
+ awaitInitialized();
+ mIcingSearchEngine.invalidateNextPageToken(nextPageToken);
+ }
+
+ /**
+ * Removes the given document by URI.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The databaseName the document is in.
+ * @param namespace Namespace of the document to remove.
+ * @param uri URI of the document to remove.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void remove(@NonNull String databaseName, @NonNull String namespace,
+ @NonNull String uri) throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
+ DeleteResultProto deleteResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ deleteResultProto = mIcingSearchEngine.delete(qualifiedNamespace, uri);
+ checkForOptimize(/* force= */false);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ checkSuccess(deleteResultProto.getStatus());
+ }
+
+ /**
+ * Removes all documents having the given {@code schemaType} in given database.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The databaseName that contains documents of schemaType.
+ * @param schemaType The schemaType of documents to remove.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void removeByType(@NonNull String databaseName, @NonNull String schemaType)
+ throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ String qualifiedType = getDatabasePrefix(databaseName) + schemaType;
+ DeleteBySchemaTypeResultProto deleteBySchemaTypeResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
+ if (existingSchemaTypes == null || !existingSchemaTypes.contains(qualifiedType)) {
+ return;
+ }
+ deleteBySchemaTypeResultProto = mIcingSearchEngine.deleteBySchemaType(qualifiedType);
+ checkForOptimize(/* force= */true);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ checkSuccess(deleteBySchemaTypeResultProto.getStatus());
+ }
+
+ /**
+ * Removes all documents having the given {@code namespace} in given database.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The databaseName that contains documents of namespace.
+ * @param namespace The namespace of documents to remove.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void removeByNamespace(@NonNull String databaseName, @NonNull String namespace)
+ throws AppSearchException, InterruptedException {
+ awaitInitialized();
+
+ String qualifiedNamespace = getDatabasePrefix(databaseName) + namespace;
+ DeleteByNamespaceResultProto deleteByNamespaceResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
+ if (existingNamespaces == null || !existingNamespaces.contains(qualifiedNamespace)) {
+ return;
+ }
+ deleteByNamespaceResultProto = mIcingSearchEngine.deleteByNamespace(qualifiedNamespace);
+ checkForOptimize(/* force= */true);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ checkSuccess(deleteByNamespaceResultProto.getStatus());
+ }
+
+ /**
+ * Clears the given database by removing all documents and types.
+ *
+ * <p>The schemas will remain. To clear everything including schemas, please call
+ * {@link #setSchema} with an empty schema and {@code forceOverride} set to true.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @param databaseName The databaseName to remove all documents from.
+ * @throws AppSearchException on IcingSearchEngine error.
+ * @throws InterruptedException if the current thread was interrupted during execution.
+ */
+ public void removeAll(@NonNull String databaseName)
+ throws AppSearchException, InterruptedException {
+ awaitInitialized();
+ mReadWriteLock.writeLock().lock();
+ try {
+ Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
+ if (existingNamespaces == null) {
+ return;
+ }
+ for (String namespace : existingNamespaces) {
+ DeleteByNamespaceResultProto deleteByNamespaceResultProto =
+ mIcingSearchEngine.deleteByNamespace(namespace);
+ // There's no way for AppSearch to know that all documents in a particular
+ // namespace have been deleted, but if you try to delete an empty namespace, Icing
+ // returns NOT_FOUND. Just ignore that code.
+ checkCodeOneOf(
+ deleteByNamespaceResultProto.getStatus(),
+ StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
+ }
+ mNamespaceMap.remove(databaseName);
+ checkForOptimize(/* force= */true);
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Clears documents and schema across all databaseNames.
+ *
+ * <p>This method belongs to mutate group.
+ *
+ * @throws AppSearchException on IcingSearchEngine error.
+ */
+ @VisibleForTesting
+ public void reset() throws AppSearchException {
+ ResetResultProto resetResultProto;
+ mReadWriteLock.writeLock().lock();
+ try {
+ resetResultProto = mIcingSearchEngine.reset();
+ mOptimizeIntervalCount = 0;
+ mSchemaMap.clear();
+ mNamespaceMap.clear();
+ } finally {
+ mReadWriteLock.writeLock().unlock();
+ }
+ checkSuccess(resetResultProto.getStatus());
+ }
+
+ /**
+ * Rewrites all types mentioned in the given {@code newSchema} to prepend {@code prefix}.
+ * Rewritten types will be added to the {@code existingSchema}.
+ *
+ * @param databaseName The name of the database where this schema lives.
+ * @param existingSchema A schema that may contain existing types from across all database
+ * instances. Will be mutated to contain the properly rewritten schema
+ * types from {@code newSchema}.
+ * @param newSchema Schema with types to add to the {@code existingSchema}.
+ * @return a Set contains all remaining qualified schema type names in given database.
+ */
+ @VisibleForTesting
+ Set<String> rewriteSchema(@NonNull String databaseName,
+ @NonNull SchemaProto.Builder existingSchema,
+ @NonNull SchemaProto newSchema) throws AppSearchException {
+ String prefix = getDatabasePrefix(databaseName);
+ HashMap<String, SchemaTypeConfigProto> newTypesToProto = new HashMap<>();
+ // Rewrite the schema type to include the typePrefix.
+ for (int typeIdx = 0; typeIdx < newSchema.getTypesCount(); typeIdx++) {
+ SchemaTypeConfigProto.Builder typeConfigBuilder =
+ newSchema.getTypes(typeIdx).toBuilder();
+
+ // Rewrite SchemaProto.types.schema_type
+ String newSchemaType = prefix + typeConfigBuilder.getSchemaType();
+ typeConfigBuilder.setSchemaType(newSchemaType);
+
+ // Rewrite SchemaProto.types.properties.schema_type
+ for (int propertyIdx = 0;
+ propertyIdx < typeConfigBuilder.getPropertiesCount();
+ propertyIdx++) {
+ PropertyConfigProto.Builder propertyConfigBuilder =
+ typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+ if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+ String newPropertySchemaType =
+ prefix + propertyConfigBuilder.getSchemaType();
+ propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+ typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+ }
+ }
+
+ newTypesToProto.put(newSchemaType, typeConfigBuilder.build());
+ }
+
+ Set<String> newSchemaTypesName = newTypesToProto.keySet();
+
+ // Combine the existing schema (which may have types from other databases) with this
+ // database's new schema. Modifies the existingSchemaBuilder.
+ // Check if we need to replace any old schema types with the new ones.
+ for (int i = 0; i < existingSchema.getTypesCount(); i++) {
+ String schemaType = existingSchema.getTypes(i).getSchemaType();
+ SchemaTypeConfigProto newProto = newTypesToProto.remove(schemaType);
+ if (newProto != null) {
+ // Replacement
+ existingSchema.setTypes(i, newProto);
+ } else if (databaseName.equals(getDatabaseName(schemaType))) {
+ // All types existing before but not in newSchema should be removed.
+ existingSchema.removeTypes(i);
+ --i;
+ }
+ }
+ // We've been removing existing types from newTypesToProto, so everything that remains is
+ // new.
+ existingSchema.addAllTypes(newTypesToProto.values());
+
+ return newSchemaTypesName;
+ }
+
+ /**
+ * Rewrites all types and namespaces mentioned anywhere in {@code documentBuilder} to prepend
+ * or remove {@code prefix}.
+ *
+ * @param prefix The prefix to add or remove
+ * @param documentBuilder The document to mutate
+ * @param add Whether to add prefix to the types and namespaces. If {@code false},
+ * prefix will be removed.
+ * @throws IllegalStateException If {@code add=false} and the document has a type or namespace
+ * that doesn't start with {@code prefix}.
+ */
+ @VisibleForTesting
+ void rewriteDocumentTypes(
+ @NonNull String prefix,
+ @NonNull DocumentProto.Builder documentBuilder,
+ boolean add) {
+ // Rewrite the type name to include/remove the prefix.
+ String newSchema;
+ if (add) {
+ newSchema = prefix + documentBuilder.getSchema();
+ } else {
+ newSchema = removePrefix(prefix, "schemaType", documentBuilder.getSchema());
+ }
+ documentBuilder.setSchema(newSchema);
+
+ // Rewrite the namespace to include/remove the prefix.
+ if (add) {
+ documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
+ } else {
+ documentBuilder.setNamespace(
+ removePrefix(prefix, "namespace", documentBuilder.getNamespace()));
+ }
+
+ // Recurse into derived documents
+ for (int propertyIdx = 0;
+ propertyIdx < documentBuilder.getPropertiesCount();
+ propertyIdx++) {
+ int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+ if (documentCount > 0) {
+ PropertyProto.Builder propertyBuilder =
+ documentBuilder.getProperties(propertyIdx).toBuilder();
+ for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+ DocumentProto.Builder derivedDocumentBuilder =
+ propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+ rewriteDocumentTypes(prefix, derivedDocumentBuilder, add);
+ propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+ }
+ documentBuilder.setProperties(propertyIdx, propertyBuilder);
+ }
+ }
+ }
+
+ /**
+ * Rewrites searchSpec by adding schemaTypeFilter and namespacesFilter
+ *
+ * <p>If user input empty filter lists, will look up {@link #mSchemaMap} and
+ * {@link #mNamespaceMap} and put all values belong to current database to narrow down Icing
+ * search area.
+ * <p>This method should be only called in query methods and get the READ lock to keep thread
+ * safety.
+ * @return false if the current database is brand new and contains nothing. We should just
+ * return an empty query result to user.
+ */
+ @VisibleForTesting
+ @GuardedBy("mReadWriteLock")
+ boolean rewriteSearchSpecForNonEmptyDatabase(@NonNull String databaseName,
+ @NonNull SearchSpecProto.Builder searchSpecBuilder) {
+ Set<String> existingSchemaTypes = mSchemaMap.get(databaseName);
+ Set<String> existingNamespaces = mNamespaceMap.get(databaseName);
+ if (existingSchemaTypes == null || existingSchemaTypes.isEmpty()
+ || existingNamespaces == null || existingNamespaces.isEmpty()) {
+ return false;
+ }
+ // Rewrite any existing schema types specified in the searchSpec, or add schema types to
+ // limit the search to this database instance.
+ if (searchSpecBuilder.getSchemaTypeFiltersCount() > 0) {
+ for (int i = 0; i < searchSpecBuilder.getSchemaTypeFiltersCount(); i++) {
+ String qualifiedType = getDatabasePrefix(databaseName)
+ + searchSpecBuilder.getSchemaTypeFilters(i);
+ if (existingSchemaTypes.contains(qualifiedType)) {
+ searchSpecBuilder.setSchemaTypeFilters(i, qualifiedType);
+ }
+ }
+ } else {
+ searchSpecBuilder.addAllSchemaTypeFilters(existingSchemaTypes);
+ }
+
+ // Rewrite any existing namespaces specified in the searchSpec, or add namespaces to
+ // limit the search to this database instance.
+ if (searchSpecBuilder.getNamespaceFiltersCount() > 0) {
+ for (int i = 0; i < searchSpecBuilder.getNamespaceFiltersCount(); i++) {
+ String qualifiedNamespace = getDatabasePrefix(databaseName)
+ + searchSpecBuilder.getNamespaceFilters(i);
+ searchSpecBuilder.setNamespaceFilters(i, qualifiedNamespace);
+ }
+ } else {
+ searchSpecBuilder.addAllNamespaceFilters(existingNamespaces);
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ SchemaProto getSchemaProto() throws AppSearchException {
+ GetSchemaResultProto schemaProto = mIcingSearchEngine.getSchema();
+ // TODO(b/161935693) check GetSchemaResultProto is success or not. Call reset() if it's not.
+ // TODO(b/161935693) only allow GetSchemaResultProto NOT_FOUND on first run
+ checkCodeOneOf(schemaProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
+ return schemaProto.getSchema();
+ }
+
+ @NonNull
+ private String getDatabasePrefix(@NonNull String databaseName) {
+ return databaseName + "/";
+ }
+
+ @NonNull
+ private String getDatabaseName(@NonNull String prefixedValue) throws AppSearchException {
+ int delimiterIndex = prefixedValue.indexOf('/');
+ if (delimiterIndex == -1) {
+ throw new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "The databaseName prefixed value doesn't contains a valid database name.");
+ }
+ return prefixedValue.substring(0, delimiterIndex);
+ }
+
+ @NonNull
+ private static String removePrefix(@NonNull String prefix, @NonNull String inputType,
+ @NonNull String input) {
+ if (!input.startsWith(prefix)) {
+ throw new IllegalStateException(
+ "Unexpected " + inputType + " \"" + input
+ + "\" does not start with \"" + prefix + "\"");
+ }
+ return input.substring(prefix.length());
+ }
+
+ @GuardedBy("mReadWriteLock")
+ private void addToMap(Map<String, Set<String>> map, String databaseName, String prefixedValue) {
+ Set<String> values = map.get(databaseName);
+ if (values == null) {
+ values = new HashSet<>();
+ map.put(databaseName, values);
+ }
+ values.add(prefixedValue);
+ }
+
+ /**
+ * Waits for the instance to become initialized.
+ *
+ * @throws InterruptedException if the current thread was interrupted during waiting.
+ */
+ private void awaitInitialized() throws InterruptedException {
+ mInitCompleteLatch.await();
+ }
+
+ /**
+ * Checks the given status code and throws an {@link AppSearchException} if code is an error.
+ *
+ * @throws AppSearchException on error codes.
+ */
+ private void checkSuccess(StatusProto statusProto) throws AppSearchException {
+ checkCodeOneOf(statusProto, StatusProto.Code.OK);
+ }
+
+ /**
+ * Checks the given status code is one of the provided codes, and throws an
+ * {@link AppSearchException} if it is not.
+ */
+ private void checkCodeOneOf(StatusProto statusProto, StatusProto.Code... codes)
+ throws AppSearchException {
+ for (int i = 0; i < codes.length; i++) {
+ if (codes[i] == statusProto.getCode()) {
+ // Everything's good
+ return;
+ }
+ }
+
+ if (statusProto.getCode() == StatusProto.Code.WARNING_DATA_LOSS) {
+ // TODO: May want to propagate WARNING_DATA_LOSS up to AppSearchManager so they can
+ // choose to log the error or potentially pass it on to clients.
+ Log.w(TAG, "Encountered WARNING_DATA_LOSS: " + statusProto.getMessage());
+ return;
+ }
+
+ throw statusProtoToAppSearchException(statusProto);
+ }
+
+ /**
+ * Checks whether {@link IcingSearchEngine#optimize()} should be called to release resources.
+ *
+ * <p>This method should be only called in mutate methods and get the WRITE lock to keep thread
+ * safety.
+ * <p>{@link IcingSearchEngine#optimize()} should be called only if
+ * {@link GetOptimizeInfoResultProto} shows there is enough resources could be released.
+ * <p>{@link IcingSearchEngine#getOptimizeInfo()} should be called once per
+ * {@link #CHECK_OPTIMIZE_INTERVAL} of remove executions.
+ *
+ * @param force whether we should directly call {@link IcingSearchEngine#getOptimizeInfo()}.
+ */
+ @GuardedBy("mReadWriteLock")
+ private void checkForOptimize(boolean force) throws AppSearchException {
+ ++mOptimizeIntervalCount;
+ if (force || mOptimizeIntervalCount >= CHECK_OPTIMIZE_INTERVAL) {
+ mOptimizeIntervalCount = 0;
+ GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResult();
+ checkSuccess(optimizeInfo.getStatus());
+ // Second threshold, decide when to call optimize().
+ if (optimizeInfo.getOptimizableDocs() >= OPTIMIZE_THRESHOLD_DOC_COUNT
+ || optimizeInfo.getEstimatedOptimizableBytes()
+ >= OPTIMIZE_THRESHOLD_BYTES) {
+ // TODO(b/155939114): call optimize in the same thread will slow down api calls
+ // significantly. Move this call to background.
+ OptimizeResultProto optimizeResultProto = mIcingSearchEngine.optimize();
+ checkSuccess(optimizeResultProto.getStatus());
+ }
+ // TODO(b/147699081): Return OptimizeResultProto & log lost data detail once we add
+ // a field to indicate lost_schema and lost_documents in OptimizeResultProto.
+ // go/icing-library-apis.
+ }
+ }
+
+ /** Remove the rewritten schema types from any result documents.*/
+ private SearchResultProto rewriteSearchResultProto(@NonNull String databaseName,
+ @NonNull SearchResultProto searchResultProto) {
+ SearchResultProto.Builder searchResultsBuilder = searchResultProto.toBuilder();
+ for (int i = 0; i < searchResultsBuilder.getResultsCount(); i++) {
+ if (searchResultProto.getResults(i).hasDocument()) {
+ SearchResultProto.ResultProto.Builder resultBuilder =
+ searchResultsBuilder.getResults(i).toBuilder();
+ DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
+ rewriteDocumentTypes(
+ getDatabasePrefix(databaseName), documentBuilder, /*add=*/false);
+ resultBuilder.setDocument(documentBuilder);
+ searchResultsBuilder.setResults(i, resultBuilder);
+ }
+ }
+ return searchResultsBuilder.build();
+ }
+
+ @VisibleForTesting
+ GetOptimizeInfoResultProto getOptimizeInfoResult() {
+ return mIcingSearchEngine.getOptimizeInfo();
+ }
+
+ /**
+ * Converts an erroneous status code to an AppSearchException. Callers should ensure that
+ * the status code is not OK or WARNING_DATA_LOSS.
+ *
+ * @param statusProto StatusProto with error code and message to translate into
+ * AppSearchException.
+ * @return AppSearchException with the parallel error code.
+ */
+ private AppSearchException statusProtoToAppSearchException(StatusProto statusProto) {
+ switch (statusProto.getCode()) {
+ case INVALID_ARGUMENT:
+ return new AppSearchException(AppSearchResult.RESULT_INVALID_ARGUMENT,
+ statusProto.getMessage());
+ case NOT_FOUND:
+ return new AppSearchException(AppSearchResult.RESULT_NOT_FOUND,
+ statusProto.getMessage());
+ case FAILED_PRECONDITION:
+ // Fallthrough
+ case ABORTED:
+ // Fallthrough
+ case INTERNAL:
+ return new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR,
+ statusProto.getMessage());
+ case OUT_OF_SPACE:
+ return new AppSearchException(AppSearchResult.RESULT_OUT_OF_SPACE,
+ statusProto.getMessage());
+ default:
+ // Some unknown/unsupported error
+ return new AppSearchException(AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "Unknown IcingSearchEngine status code: " + statusProto.getCode());
+ }
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverter.java
new file mode 100644
index 000000000000..ca0d2ee970cb
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 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.appsearch.external.localbackend.converter;
+
+import android.os.Bundle;
+
+import android.annotation.NonNull;
+
+import android.app.appsearch.AppSearchSchema;
+import com.android.internal.util.Preconditions;
+
+import com.google.android.icing.proto.IndexingConfig;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import java.util.ArrayList;
+
+/**
+ * Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
+ * @hide
+ */
+
+public final class SchemaToProtoConverter {
+ private SchemaToProtoConverter() {}
+
+ /**
+ * Converts an {@link android.app.appsearch.AppSearchSchema} into a
+ * {@link SchemaTypeConfigProto}.
+ */
+ @NonNull
+ public static SchemaTypeConfigProto convert(@NonNull AppSearchSchema schema) {
+ Preconditions.checkNotNull(schema);
+ Bundle bundle = schema.getBundle();
+ SchemaTypeConfigProto.Builder protoBuilder =
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType(bundle.getString(AppSearchSchema.SCHEMA_TYPE_FIELD, ""));
+ ArrayList<Bundle> properties =
+ bundle.getParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD);
+ if (properties != null) {
+ for (int i = 0; i < properties.size(); i++) {
+ PropertyConfigProto propertyProto = convertProperty(properties.get(i));
+ protoBuilder.addProperties(propertyProto);
+ }
+ }
+ return protoBuilder.build();
+ }
+
+ @NonNull
+ private static PropertyConfigProto convertProperty(@NonNull Bundle bundle) {
+ Preconditions.checkNotNull(bundle);
+ PropertyConfigProto.Builder propertyConfigProto = PropertyConfigProto.newBuilder()
+ .setPropertyName(bundle.getString(AppSearchSchema.PropertyConfig.NAME_FIELD, ""));
+ IndexingConfig.Builder indexingConfig = IndexingConfig.newBuilder();
+
+ // Set dataType
+ @AppSearchSchema.PropertyConfig.DataType int dataType =
+ bundle.getInt(AppSearchSchema.PropertyConfig.DATA_TYPE_FIELD);
+ PropertyConfigProto.DataType.Code dataTypeProto =
+ PropertyConfigProto.DataType.Code.forNumber(dataType);
+ if (dataTypeProto == null) {
+ throw new IllegalArgumentException("Invalid dataType: " + dataType);
+ }
+ propertyConfigProto.setDataType(dataTypeProto);
+
+ // Set schemaType
+ propertyConfigProto.setSchemaType(
+ bundle.getString(AppSearchSchema.PropertyConfig.SCHEMA_TYPE_FIELD, ""));
+
+ // Set cardinality
+ @AppSearchSchema.PropertyConfig.Cardinality int cardinality =
+ bundle.getInt(AppSearchSchema.PropertyConfig.CARDINALITY_FIELD);
+ PropertyConfigProto.Cardinality.Code cardinalityProto =
+ PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
+ if (cardinalityProto == null) {
+ throw new IllegalArgumentException("Invalid cardinality: " + dataType);
+ }
+ propertyConfigProto.setCardinality(cardinalityProto);
+
+ // Set indexingType
+ @AppSearchSchema.PropertyConfig.IndexingType int indexingType =
+ bundle.getInt(AppSearchSchema.PropertyConfig.INDEXING_TYPE_FIELD);
+ TermMatchType.Code termMatchTypeProto;
+ switch (indexingType) {
+ case AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE:
+ termMatchTypeProto = TermMatchType.Code.UNKNOWN;
+ break;
+ case AppSearchSchema.PropertyConfig.INDEXING_TYPE_EXACT_TERMS:
+ termMatchTypeProto = TermMatchType.Code.EXACT_ONLY;
+ break;
+ case AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES:
+ termMatchTypeProto = TermMatchType.Code.PREFIX;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid indexingType: " + indexingType);
+ }
+ indexingConfig.setTermMatchType(termMatchTypeProto);
+
+ // Set tokenizerType
+ @AppSearchSchema.PropertyConfig.TokenizerType int tokenizerType =
+ bundle.getInt(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_FIELD);
+ IndexingConfig.TokenizerType.Code tokenizerTypeProto =
+ IndexingConfig.TokenizerType.Code.forNumber(tokenizerType);
+ if (tokenizerTypeProto == null) {
+ throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
+ }
+ indexingConfig.setTokenizerType(tokenizerTypeProto);
+
+ // Build!
+ propertyConfigProto.setIndexingConfig(indexingConfig);
+ return propertyConfigProto.build();
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SearchSpecToProtoConverter.java
new file mode 100644
index 000000000000..a5d913a20590
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localbackend/converter/SearchSpecToProtoConverter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 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.appsearch.external.localbackend.converter;
+
+import android.os.Bundle;
+
+import android.annotation.NonNull;
+
+import android.app.appsearch.SearchSpec;
+import com.android.internal.util.Preconditions;
+
+import com.google.android.icing.proto.ResultSpecProto;
+import com.google.android.icing.proto.ScoringSpecProto;
+import com.google.android.icing.proto.SearchSpecProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import java.util.Arrays;
+
+/**
+ * Translates a {@link SearchSpec} into icing search protos.
+ * @hide
+ */
+
+public final class SearchSpecToProtoConverter {
+ private SearchSpecToProtoConverter() {}
+
+ /** Extracts {@link SearchSpecProto} information from a {@link SearchSpec}. */
+ @NonNull
+ public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
+ Preconditions.checkNotNull(spec);
+ Bundle bundle = spec.getBundle();
+ SearchSpecProto.Builder protoBuilder = SearchSpecProto.newBuilder();
+
+ @SearchSpec.TermMatchCode int termMatchCode =
+ bundle.getInt(SearchSpec.TERM_MATCH_TYPE_FIELD);
+ TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
+ if (termMatchCodeProto == null || termMatchCodeProto.equals(TermMatchType.Code.UNKNOWN)) {
+ throw new IllegalArgumentException("Invalid term match type: " + termMatchCode);
+ }
+ protoBuilder.setTermMatchType(termMatchCodeProto);
+
+ String[] schemaTypes = bundle.getStringArray(SearchSpec.SCHEMA_TYPES_FIELD);
+ if (schemaTypes != null) {
+ protoBuilder.addAllSchemaTypeFilters(Arrays.asList(schemaTypes));
+ }
+ String[] namespaces = bundle.getStringArray(SearchSpec.NAMESPACE_FIELD);
+ if (namespaces != null) {
+ protoBuilder.addAllNamespaceFilters(Arrays.asList(namespaces));
+ }
+ return protoBuilder.build();
+ }
+
+ /** Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. */
+ @NonNull
+ public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
+ Preconditions.checkNotNull(spec);
+ Bundle bundle = spec.getBundle();
+ return ResultSpecProto.newBuilder()
+ .setNumPerPage(bundle.getInt(
+ SearchSpec.NUM_PER_PAGE_FIELD, SearchSpec.DEFAULT_NUM_PER_PAGE))
+ .setSnippetSpec(ResultSpecProto.SnippetSpecProto.newBuilder()
+ .setNumToSnippet(bundle.getInt(SearchSpec.SNIPPET_COUNT_FIELD))
+ .setNumMatchesPerProperty(
+ bundle.getInt(SearchSpec.SNIPPET_COUNT_PER_PROPERTY_FIELD))
+ .setMaxWindowBytes(bundle.getInt(SearchSpec.MAX_SNIPPET_FIELD)))
+ .build();
+
+ }
+
+ /** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
+ @NonNull
+ public static ScoringSpecProto toScoringSpecProto(@NonNull SearchSpec spec) {
+ Preconditions.checkNotNull(spec);
+ Bundle bundle = spec.getBundle();
+ ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder();
+
+ @SearchSpec.OrderCode int orderCode = bundle.getInt(SearchSpec.ORDER_FIELD);
+ ScoringSpecProto.Order.Code orderCodeProto =
+ ScoringSpecProto.Order.Code.forNumber(orderCode);
+ if (orderCodeProto == null) {
+ throw new IllegalArgumentException("Invalid result ranking order: " + orderCode);
+ }
+ protoBuilder.setOrderBy(orderCodeProto);
+
+ @SearchSpec.RankingStrategyCode int rankingStrategyCode =
+ bundle.getInt(SearchSpec.RANKING_STRATEGY_FIELD);
+ ScoringSpecProto.RankingStrategy.Code rankingStrategyCodeProto =
+ ScoringSpecProto.RankingStrategy.Code.forNumber(rankingStrategyCode);
+ if (rankingStrategyCodeProto == null) {
+ throw new IllegalArgumentException("Invalid result ranking strategy: "
+ + rankingStrategyCode);
+ }
+ protoBuilder.setRankBy(rankingStrategyCodeProto);
+
+ return protoBuilder.build();
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
deleted file mode 100644
index 4358d2086181..000000000000
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * 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 com.android.server.appsearch.impl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.android.icing.proto.DocumentProto;
-import com.google.android.icing.proto.PropertyConfigProto;
-import com.google.android.icing.proto.PropertyProto;
-import com.google.android.icing.proto.ResultSpecProto;
-import com.google.android.icing.proto.SchemaProto;
-import com.google.android.icing.proto.SchemaTypeConfigProto;
-import com.google.android.icing.proto.ScoringSpecProto;
-import com.google.android.icing.proto.SearchResultProto;
-import com.google.android.icing.proto.SearchSpecProto;
-
-import java.util.Set;
-
-/**
- * Manages interaction with {@link FakeIcing} and other components to implement AppSearch
- * functionality.
- */
-public final class AppSearchImpl {
- private final Context mContext;
- private final @UserIdInt int mUserId;
- private final FakeIcing mFakeIcing = new FakeIcing();
-
- AppSearchImpl(@NonNull Context context, @UserIdInt int userId) {
- mContext = context;
- mUserId = userId;
- }
-
- /**
- * Updates the AppSearch schema for this app.
- *
- * @param callingUid The uid of the app calling AppSearch.
- * @param origSchema The schema to set for this app.
- * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents
- * which do not comply with the new schema will be deleted.
- */
- public void setSchema(int callingUid, @NonNull SchemaProto origSchema, boolean forceOverride) {
- // Rewrite schema type names to include the calling app's package and uid.
- String typePrefix = getTypePrefix(callingUid);
- SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
- rewriteSchemaTypes(typePrefix, schemaBuilder);
-
- // TODO(b/145635424): Save in schema type map
- // TODO(b/145635424): Apply the schema to Icing and report results
- }
-
- /**
- * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend
- * {@code typePrefix}.
- *
- * @param typePrefix The prefix to add
- * @param schemaBuilder The schema to mutate
- */
- @VisibleForTesting
- void rewriteSchemaTypes(
- @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) {
- for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) {
- SchemaTypeConfigProto.Builder typeConfigBuilder =
- schemaBuilder.getTypes(typeIdx).toBuilder();
-
- // Rewrite SchemaProto.types.schema_type
- String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType();
- typeConfigBuilder.setSchemaType(newSchemaType);
-
- // Rewrite SchemaProto.types.properties.schema_type
- for (int propertyIdx = 0;
- propertyIdx < typeConfigBuilder.getPropertiesCount();
- propertyIdx++) {
- PropertyConfigProto.Builder propertyConfigBuilder =
- typeConfigBuilder.getProperties(propertyIdx).toBuilder();
- if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
- String newPropertySchemaType =
- typePrefix + propertyConfigBuilder.getSchemaType();
- propertyConfigBuilder.setSchemaType(newPropertySchemaType);
- typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
- }
- }
-
- schemaBuilder.setTypes(typeIdx, typeConfigBuilder);
- }
- }
-
- /**
- * Adds a document to the AppSearch index.
- *
- * @param callingUid The uid of the app calling AppSearch.
- * @param origDocument The document to index.
- */
- public void putDocument(int callingUid, @NonNull DocumentProto origDocument) {
- // Rewrite the type names to include the app's prefix
- String typePrefix = getTypePrefix(callingUid);
- DocumentProto.Builder documentBuilder = origDocument.toBuilder();
- rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ true);
- mFakeIcing.put(documentBuilder.build());
- }
-
- /**
- * Retrieves a document from the AppSearch index by URI.
- *
- * @param callingUid The uid of the app calling AppSearch.
- * @param uri The URI of the document to get.
- * @return The Document contents, or {@code null} if no such URI exists in the system.
- */
- @Nullable
- public DocumentProto getDocument(int callingUid, @NonNull String uri) {
- String typePrefix = getTypePrefix(callingUid);
- DocumentProto document = mFakeIcing.get(uri);
- if (document == null) {
- return null;
- }
-
- // TODO(b/146526096): Since FakeIcing doesn't currently handle namespaces, we perform a
- // post-filter to make sure we don't return documents we shouldn't. This should be removed
- // once the real Icing Lib is implemented.
- if (!document.getNamespace().equals(typePrefix)) {
- return null;
- }
-
- // Rewrite the type names to remove the app's prefix
- DocumentProto.Builder documentBuilder = document.toBuilder();
- rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/ false);
- return documentBuilder.build();
- }
-
- /**
- * Executes a query against the AppSearch index and returns results.
- *
- * @param callingUid The uid of the app calling AppSearch.
- * @param searchSpec Defines what and how to search
- * @param resultSpec Defines what results to show
- * @param scoringSpec Defines how to order results
- * @return The results of performing this search The proto might have no {@code results} if no
- * documents matched the query.
- */
- @NonNull
- public SearchResultProto query(
- int callingUid,
- @NonNull SearchSpecProto searchSpec,
- @NonNull ResultSpecProto resultSpec,
- @NonNull ScoringSpecProto scoringSpec) {
- String typePrefix = getTypePrefix(callingUid);
- SearchResultProto searchResults = mFakeIcing.query(searchSpec.getQuery());
- if (searchResults.getResultsCount() == 0) {
- return searchResults;
- }
- Set<String> qualifiedSearchFilters = null;
- if (searchSpec.getSchemaTypeFiltersCount() > 0) {
- qualifiedSearchFilters = new ArraySet<>(searchSpec.getSchemaTypeFiltersCount());
- for (String schema : searchSpec.getSchemaTypeFiltersList()) {
- String qualifiedSchema = typePrefix + schema;
- qualifiedSearchFilters.add(qualifiedSchema);
- }
- }
- // Rewrite the type names to remove the app's prefix
- SearchResultProto.Builder searchResultsBuilder = searchResults.toBuilder();
- for (int i = 0; i < searchResultsBuilder.getResultsCount(); i++) {
- if (searchResults.getResults(i).hasDocument()) {
- SearchResultProto.ResultProto.Builder resultBuilder =
- searchResultsBuilder.getResults(i).toBuilder();
-
- // TODO(b/145631811): Since FakeIcing doesn't currently handle namespaces, we
- // perform a post-filter to make sure we don't return documents we shouldn't. This
- // should be removed once the real Icing Lib is implemented.
- if (!resultBuilder.getDocument().getNamespace().equals(typePrefix)) {
- searchResultsBuilder.removeResults(i);
- i--;
- continue;
- }
-
- // TODO(b/145631811): Since FakeIcing doesn't currently handle type names, we
- // perform a post-filter to make sure we don't return documents we shouldn't. This
- // should be removed once the real Icing Lib is implemented.
- if (qualifiedSearchFilters != null
- && !qualifiedSearchFilters.contains(
- resultBuilder.getDocument().getSchema())) {
- searchResultsBuilder.removeResults(i);
- i--;
- continue;
- }
-
- DocumentProto.Builder documentBuilder = resultBuilder.getDocument().toBuilder();
- rewriteDocumentTypes(typePrefix, documentBuilder, /*add=*/false);
- resultBuilder.setDocument(documentBuilder);
- searchResultsBuilder.setResults(i, resultBuilder);
- }
- }
- return searchResultsBuilder.build();
- }
-
- /** Deletes the given document by URI */
- public boolean delete(int callingUid, @NonNull String uri) {
- DocumentProto document = mFakeIcing.get(uri);
- if (document == null) {
- return false;
- }
-
- // TODO(b/146526096): Since FakeIcing doesn't currently handle namespaces, we perform a
- // post-filter to make sure we don't delete documents we shouldn't. This should be
- // removed once the real Icing Lib is implemented.
- String typePrefix = getTypePrefix(callingUid);
- if (!typePrefix.equals(document.getNamespace())) {
- throw new SecurityException(
- "Failed to delete document " + uri + "; URI collision in FakeIcing");
- }
-
- return mFakeIcing.delete(uri);
- }
-
- /** Deletes all documents having the given {@code schemaType}. */
- public boolean deleteByType(int callingUid, @NonNull String schemaType) {
- String typePrefix = getTypePrefix(callingUid);
- String qualifiedType = typePrefix + schemaType;
- return mFakeIcing.deleteByType(qualifiedType);
- }
-
- /**
- * Deletes all documents owned by the calling app.
- *
- * @param callingUid The uid of the app calling AppSearch.
- */
- public void deleteAll(int callingUid) {
- String namespace = getTypePrefix(callingUid);
- mFakeIcing.deleteByNamespace(namespace);
- }
-
- /**
- * Rewrites all types mentioned anywhere in {@code documentBuilder} to prepend or remove
- * {@code typePrefix}.
- *
- * @param typePrefix The prefix to add or remove
- * @param documentBuilder The document to mutate
- * @param add Whether to add typePrefix to the types. If {@code false}, typePrefix will be
- * removed from the types.
- * @throws IllegalArgumentException If {@code add=false} and the document has a type that
- * doesn't start with {@code typePrefix}.
- */
- @VisibleForTesting
- void rewriteDocumentTypes(
- @NonNull String typePrefix,
- @NonNull DocumentProto.Builder documentBuilder,
- boolean add) {
- // Rewrite the type name to include/remove the app's prefix
- String newSchema;
- if (add) {
- newSchema = typePrefix + documentBuilder.getSchema();
- } else {
- newSchema = removePrefix(typePrefix, documentBuilder.getSchema());
- }
- documentBuilder.setSchema(newSchema);
-
- // Add/remove namespace. If we ever allow users to set their own namespaces, this will have
- // to change to prepend the prefix instead of setting the whole namespace. We will also have
- // to store the namespaces in a map similar to the type map so we can rewrite queries with
- // empty namespaces.
- if (add) {
- documentBuilder.setNamespace(typePrefix);
- } else if (!documentBuilder.getNamespace().equals(typePrefix)) {
- throw new IllegalStateException(
- "Unexpected namespace \"" + documentBuilder.getNamespace()
- + "\" (expected \"" + typePrefix + "\")");
- } else {
- documentBuilder.clearNamespace();
- }
-
- // Recurse into derived documents
- for (int propertyIdx = 0;
- propertyIdx < documentBuilder.getPropertiesCount();
- propertyIdx++) {
- int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
- if (documentCount > 0) {
- PropertyProto.Builder propertyBuilder =
- documentBuilder.getProperties(propertyIdx).toBuilder();
- for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
- DocumentProto.Builder derivedDocumentBuilder =
- propertyBuilder.getDocumentValues(documentIdx).toBuilder();
- rewriteDocumentTypes(typePrefix, derivedDocumentBuilder, add);
- propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
- }
- documentBuilder.setProperties(propertyIdx, propertyBuilder);
- }
- }
- }
-
- /**
- * Returns a type prefix in a format like {@code com.example.package@1000/} or
- * {@code com.example.sharedname:5678@1000/}.
- */
- @NonNull
- private String getTypePrefix(int callingUid) {
- // For regular apps, this call will return the package name. If callingUid is an
- // android:sharedUserId, this value may be another type of name and have a :uid suffix.
- String callingUidName = mContext.getPackageManager().getNameForUid(callingUid);
- if (callingUidName == null) {
- // Not sure how this is possible --- maybe app was uninstalled?
- throw new IllegalStateException("Failed to look up package name for uid " + callingUid);
- }
- return callingUidName + "@" + mUserId + "/";
- }
-
- @NonNull
- private static String removePrefix(@NonNull String prefix, @NonNull String input) {
- if (!input.startsWith(prefix)) {
- throw new IllegalArgumentException(
- "Input \"" + input + "\" does not start with \"" + prefix + "\"");
- }
- return input.substring(prefix.length());
- }
-}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
deleted file mode 100644
index da1573463b73..000000000000
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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 com.android.server.appsearch.impl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.google.android.icing.proto.DocumentProto;
-import com.google.android.icing.proto.PropertyProto;
-import com.google.android.icing.proto.SearchResultProto;
-import com.google.android.icing.proto.StatusProto;
-
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Fake in-memory implementation of the Icing key-value store and reverse index.
- * <p>
- * Currently, only queries by single exact term are supported. There is no support for persistence,
- * namespaces, i18n tokenization, or schema.
- */
-public class FakeIcing {
- private final AtomicInteger mNextDocId = new AtomicInteger();
- private final Map<String, Integer> mUriToDocIdMap = new ArrayMap<>();
- /** Array of Documents where index into the array is the docId. */
- private final SparseArray<DocumentProto> mDocStore = new SparseArray<>();
- /** Map of term to posting-list (the set of DocIds containing that term). */
- private final Map<String, Set<Integer>> mIndex = new ArrayMap<>();
-
- /**
- * Inserts a document into the index.
- *
- * @param document The document to insert.
- */
- public void put(@NonNull DocumentProto document) {
- String uri = document.getUri();
-
- // Update mDocIdMap
- Integer docId = mUriToDocIdMap.get(uri);
- if (docId != null) {
- // Delete the old doc
- mDocStore.remove(docId);
- }
-
- // Allocate a new docId
- docId = mNextDocId.getAndIncrement();
- mUriToDocIdMap.put(uri, docId);
-
- // Update mDocStore
- mDocStore.put(docId, document);
-
- // Update mIndex
- indexDocument(docId, document);
- }
-
- /**
- * Retrieves a document from the index.
- *
- * @param uri The URI of the document to retrieve.
- * @return The body of the document, or {@code null} if no such document exists.
- */
- @Nullable
- public DocumentProto get(@NonNull String uri) {
- Integer docId = mUriToDocIdMap.get(uri);
- if (docId == null) {
- return null;
- }
- return mDocStore.get(docId);
- }
-
- /**
- * Returns documents containing all words in the given query string.
- *
- * @param queryExpression A set of words to search for. They will be implicitly AND-ed together.
- * No operators are supported.
- * @return A {@link SearchResultProto} containing the matching documents, which may have no
- * results if no documents match.
- */
- @NonNull
- public SearchResultProto query(@NonNull String queryExpression) {
- String[] terms = normalizeString(queryExpression).split("\\s+");
- SearchResultProto.Builder results = SearchResultProto.newBuilder()
- .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK));
- if (terms.length == 0) {
- return results.build();
- }
- Set<Integer> docIds = mIndex.get(terms[0]);
- if (docIds == null || docIds.isEmpty()) {
- return results.build();
- }
- for (int i = 1; i < terms.length; i++) {
- Set<Integer> termDocIds = mIndex.get(terms[i]);
- if (termDocIds == null) {
- return results.build();
- }
- docIds.retainAll(termDocIds);
- if (docIds.isEmpty()) {
- return results.build();
- }
- }
- for (int docId : docIds) {
- DocumentProto document = mDocStore.get(docId);
- if (document != null) {
- results.addResults(
- SearchResultProto.ResultProto.newBuilder().setDocument(document));
- }
- }
- return results.build();
- }
-
- /**
- * Deletes a document by its URI.
- *
- * @param uri The URI of the document to be deleted.
- * @return Whether deletion was successful.
- */
- public boolean delete(@NonNull String uri) {
- // Update mDocIdMap
- Integer docId = mUriToDocIdMap.get(uri);
- if (docId != null) {
- // Delete the old doc
- mDocStore.remove(docId);
- mUriToDocIdMap.remove(uri);
- return true;
- }
- return false;
- }
-
- /** Deletes all documents having the given namespace. */
- public void deleteByNamespace(@NonNull String namespace) {
- for (int i = 0; i < mDocStore.size(); i++) {
- DocumentProto document = mDocStore.valueAt(i);
- if (namespace.equals(document.getNamespace())) {
- mDocStore.removeAt(i);
- mUriToDocIdMap.remove(document.getUri());
- i--;
- }
- }
- }
-
- /**
- * Deletes all documents having the given type.
- *
- * @return true if any documents were deleted.
- */
- public boolean deleteByType(@NonNull String type) {
- boolean deletedAny = false;
- for (int i = 0; i < mDocStore.size(); i++) {
- DocumentProto document = mDocStore.valueAt(i);
- if (type.equals(document.getSchema())) {
- mDocStore.removeAt(i);
- mUriToDocIdMap.remove(document.getUri());
- i--;
- deletedAny = true;
- }
- }
- return deletedAny;
- }
-
- private void indexDocument(int docId, DocumentProto document) {
- for (PropertyProto property : document.getPropertiesList()) {
- for (String stringValue : property.getStringValuesList()) {
- String[] words = normalizeString(stringValue).split("\\s+");
- for (String word : words) {
- indexTerm(docId, word);
- }
- }
- for (Long longValue : property.getInt64ValuesList()) {
- indexTerm(docId, longValue.toString());
- }
- for (Double doubleValue : property.getDoubleValuesList()) {
- indexTerm(docId, doubleValue.toString());
- }
- for (Boolean booleanValue : property.getBooleanValuesList()) {
- indexTerm(docId, booleanValue.toString());
- }
- // Intentionally skipping bytes values
- for (DocumentProto documentValue : property.getDocumentValuesList()) {
- indexDocument(docId, documentValue);
- }
- }
- }
-
- private void indexTerm(int docId, String term) {
- Set<Integer> postingList = mIndex.get(term);
- if (postingList == null) {
- postingList = new ArraySet<>();
- mIndex.put(term, postingList);
- }
- postingList.add(docId);
- }
-
- /** Strips out punctuation and converts to lowercase. */
- private static String normalizeString(String input) {
- return input.replaceAll("\\p{P}", "").toLowerCase(Locale.getDefault());
- }
-}
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 bb9f13f1712c..5cebf8d91cfc 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -128,7 +128,7 @@ class BlobStoreConfig {
*/
public static final String KEY_USE_REVOCABLE_FD_FOR_READS =
"use_revocable_fd_for_reads";
- public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true;
+ public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = false;
public static boolean USE_REVOCABLE_FD_FOR_READS =
DEFAULT_USE_REVOCABLE_FD_FOR_READS;
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 59915e145c49..1a81587990f2 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -1682,7 +1682,7 @@ public class DeviceIdleController extends SystemService
}
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return addPowerSaveWhitelistAppsInternal(packageNames);
} finally {
@@ -1696,7 +1696,7 @@ public class DeviceIdleController extends SystemService
}
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (!removePowerSaveWhitelistAppInternal(name)
&& mPowerSaveWhitelistAppsExceptIdle.containsKey(name)) {
@@ -1713,7 +1713,7 @@ public class DeviceIdleController extends SystemService
}
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
removeSystemPowerWhitelistAppInternal(name);
} finally {
@@ -1727,7 +1727,7 @@ public class DeviceIdleController extends SystemService
}
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
restoreSystemPowerWhitelistAppInternal(name);
} finally {
@@ -1815,7 +1815,7 @@ public class DeviceIdleController extends SystemService
@Override public void exitIdle(String reason) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
exitIdleInternal(reason);
} finally {
@@ -1826,7 +1826,7 @@ public class DeviceIdleController extends SystemService
@Override public int setPreIdleTimeoutMode(int mode) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return DeviceIdleController.this.setPreIdleTimeoutMode(mode);
} finally {
@@ -1837,7 +1837,7 @@ public class DeviceIdleController extends SystemService
@Override public void resetPreIdleTimeoutMode() {
getContext().enforceCallingOrSelfPermission(Manifest.permission.DEVICE_POWER,
null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
DeviceIdleController.this.resetPreIdleTimeoutMode();
} finally {
@@ -4031,7 +4031,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
String arg = shell.getNextArg();
try {
if (arg == null || "deep".equals(arg)) {
@@ -4052,7 +4052,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
String arg = shell.getNextArg();
try {
if (arg == null || "deep".equals(arg)) {
@@ -4100,7 +4100,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mForceIdle = true;
becomeInactiveIfAppropriateLocked();
@@ -4116,7 +4116,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
exitForceIdleLocked();
pw.print("Light state: ");
@@ -4133,7 +4133,7 @@ public class DeviceIdleController extends SystemService
synchronized (this) {
String arg = shell.getNextArg();
if (arg != null) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
switch (arg) {
case "light": pw.println(lightStateToString(mLightState)); break;
@@ -4156,7 +4156,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
String arg = shell.getNextArg();
try {
boolean becomeActive = false;
@@ -4193,7 +4193,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
String arg = shell.getNextArg();
try {
boolean becomeInactive = false;
@@ -4242,7 +4242,7 @@ public class DeviceIdleController extends SystemService
if (arg != null) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
do {
if (arg.length() < 1 || (arg.charAt(0) != '-'
@@ -4418,7 +4418,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
motionLocked();
pw.print("Light state: ");
@@ -4433,7 +4433,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
int ret = SET_IDLE_FACTOR_RESULT_UNINIT;
try {
String arg = shell.getNextArg();
@@ -4468,7 +4468,7 @@ public class DeviceIdleController extends SystemService
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
synchronized (this) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
resetPreIdleTimeoutMode();
} finally {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index f672e4b711c4..45ea23321d15 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -16,10 +16,13 @@
package com.android.server.alarm;
+import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.RTC_WAKEUP;
+import static com.android.server.alarm.AlarmManagerService.clampPositive;
+
import android.app.AlarmManager;
import android.app.IAlarmListener;
import android.app.PendingIntent;
@@ -32,8 +35,28 @@ import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
+/**
+ * Class to describe an alarm that is used to the set the kernel timer that returns when the timer
+ * expires. The timer will wake up the device if the alarm is a "wakeup" alarm.
+ */
class Alarm {
+ private static final int NUM_POLICIES = 2;
+ /**
+ * Index used to store the time the alarm was requested to expire. To be used with
+ * {@link #setPolicyElapsed(int, long)}
+ */
+ public static final int REQUESTER_POLICY_INDEX = 0;
+ /**
+ * Index used to store the earliest time the alarm can expire based on app-standby policy.
+ * To be used with {@link #setPolicyElapsed(int, long)}
+ */
+ public static final int APP_STANDBY_POLICY_INDEX = 1;
+
public final int type;
+ /**
+ * The original trigger time supplied by the caller. This can be in the elapsed or rtc time base
+ * depending on the type of this alarm
+ */
public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
@@ -47,42 +70,40 @@ class Alarm {
public final int creatorUid;
public final String packageName;
public final String sourcePackage;
+ public final long windowLength;
+ public final long repeatInterval;
public int count;
- public long when;
- public long windowLength;
- public long whenElapsed; // 'when' in the elapsed time base
- public long maxWhenElapsed; // also in the elapsed time base
- // Expected alarm expiry time before app standby deferring is applied.
- public long expectedWhenElapsed;
- public long expectedMaxWhenElapsed;
- public long repeatInterval;
+ /** The earliest time this alarm is eligible to fire according to each policy */
+ private long[] mPolicyWhenElapsed;
+ /** The ultimate delivery time to be used for this alarm */
+ private long mWhenElapsed;
+ private long mMaxWhenElapsed;
public AlarmManagerService.PriorityClass priorityClass;
- Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
- WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
- int _uid, String _pkgName) {
- type = _type;
- origWhen = _when;
- wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
- || _type == AlarmManager.RTC_WAKEUP;
- when = _when;
- whenElapsed = _whenElapsed;
- expectedWhenElapsed = _whenElapsed;
- windowLength = _windowLength;
- maxWhenElapsed = expectedMaxWhenElapsed = AlarmManagerService.clampPositive(_maxWhen);
- repeatInterval = _interval;
- operation = _op;
- listener = _rec;
- listenerTag = _listenerTag;
- statsTag = makeTag(_op, _listenerTag, _type);
- workSource = _ws;
- flags = _flags;
- alarmClock = _info;
- uid = _uid;
- packageName = _pkgName;
+ Alarm(int type, long when, long requestedWhenElapsed, long windowLength, long interval,
+ PendingIntent op, IAlarmListener rec, String listenerTag, WorkSource ws, int flags,
+ AlarmManager.AlarmClockInfo info, int uid, String pkgName) {
+ this.type = type;
+ origWhen = when;
+ wakeup = type == AlarmManager.ELAPSED_REALTIME_WAKEUP
+ || type == AlarmManager.RTC_WAKEUP;
+ mPolicyWhenElapsed = new long[NUM_POLICIES];
+ mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] = requestedWhenElapsed;
+ mWhenElapsed = requestedWhenElapsed;
+ this.windowLength = windowLength;
+ mMaxWhenElapsed = clampPositive(requestedWhenElapsed + windowLength);
+ repeatInterval = interval;
+ operation = op;
+ listener = rec;
+ this.listenerTag = listenerTag;
+ statsTag = makeTag(op, listenerTag, type);
+ workSource = ws;
+ this.flags = flags;
+ alarmClock = info;
+ this.uid = uid;
+ packageName = pkgName;
sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
- creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
+ creatorUid = (operation != null) ? operation.getCreatorUid() : this.uid;
}
public static String makeTag(PendingIntent pi, String tag, int type) {
@@ -91,13 +112,6 @@ class Alarm {
return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
}
- public AlarmManagerService.WakeupEvent makeWakeupEvent(long nowRTC) {
- return new AlarmManagerService.WakeupEvent(nowRTC, creatorUid,
- (operation != null)
- ? operation.getIntent().getAction()
- : ("<listener>:" + listenerTag));
- }
-
// Returns true if either matches
public boolean matches(PendingIntent pi, IAlarmListener rec) {
return (operation != null)
@@ -109,6 +123,65 @@ class Alarm {
return packageName.equals(sourcePackage);
}
+ /**
+ * Get the earliest time this alarm is allowed to expire based on the given policy.
+ *
+ * @param policyIndex The index of the policy. One of [{@link #REQUESTER_POLICY_INDEX},
+ * {@link #APP_STANDBY_POLICY_INDEX}].
+ */
+ public long getPolicyElapsed(int policyIndex) {
+ return mPolicyWhenElapsed[policyIndex];
+ }
+
+ /**
+ * Get the earliest time that this alarm should be delivered to the requesting app.
+ */
+ public long getWhenElapsed() {
+ return mWhenElapsed;
+ }
+
+ /**
+ * Get the latest time that this alarm should be delivered to the requesting app. Will be equal
+ * to {@link #getWhenElapsed()} in case this is an exact alarm.
+ */
+ public long getMaxWhenElapsed() {
+ return mMaxWhenElapsed;
+ }
+
+ /**
+ * Set the earliest time this alarm can expire based on the passed policy index.
+ *
+ * @return {@code true} if this change resulted in a change in the ultimate delivery time (or
+ * time window in the case of inexact alarms) of this alarm.
+ * @see #getWhenElapsed()
+ * @see #getMaxWhenElapsed()
+ * @see #getPolicyElapsed(int)
+ */
+ public boolean setPolicyElapsed(int policyIndex, long policyElapsed) {
+ mPolicyWhenElapsed[policyIndex] = policyElapsed;
+ return updateWhenElapsed();
+ }
+
+ /**
+ * @return {@code true} if either {@link #mWhenElapsed} or {@link #mMaxWhenElapsed} changes
+ * due to this call.
+ */
+ private boolean updateWhenElapsed() {
+ final long oldWhenElapsed = mWhenElapsed;
+ mWhenElapsed = 0;
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ mWhenElapsed = Math.max(mWhenElapsed, mPolicyWhenElapsed[i]);
+ }
+
+ final long oldMaxWhenElapsed = mMaxWhenElapsed;
+ // windowLength should always be >= 0 here.
+ final long maxRequestedElapsed = clampPositive(
+ mPolicyWhenElapsed[REQUESTER_POLICY_INDEX] + windowLength);
+ mMaxWhenElapsed = Math.max(maxRequestedElapsed, mWhenElapsed);
+
+ return (oldWhenElapsed != mWhenElapsed) || (oldMaxWhenElapsed != mMaxWhenElapsed);
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder(128);
@@ -116,11 +189,11 @@ class Alarm {
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" type ");
sb.append(type);
- sb.append(" when ");
- sb.append(when);
+ sb.append(" origWhen ");
+ sb.append(origWhen);
sb.append(" ");
sb.append(" whenElapsed ");
- sb.append(whenElapsed);
+ sb.append(getWhenElapsed());
sb.append(" ");
sb.append(sourcePackage);
sb.append('}');
@@ -136,30 +209,46 @@ class Alarm {
dump(ipw, nowELAPSED, sdf);
}
+ private static String policyIndexToString(int index) {
+ switch (index) {
+ case REQUESTER_POLICY_INDEX:
+ return "requester";
+ case APP_STANDBY_POLICY_INDEX:
+ return "app_standby";
+ default:
+ return "unknown";
+ }
+ }
+
+ public static String typeToString(int type) {
+ switch (type) {
+ case RTC:
+ return "RTC";
+ case RTC_WAKEUP:
+ return "RTC_WAKEUP";
+ case ELAPSED_REALTIME:
+ return "ELAPSED";
+ case ELAPSED_REALTIME_WAKEUP:
+ return "ELAPSED_WAKEUP";
+ default:
+ return "--unknown--";
+ }
+ }
+
public void dump(IndentingPrintWriter ipw, long nowELAPSED, SimpleDateFormat sdf) {
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
ipw.print("tag=");
ipw.println(statsTag);
ipw.print("type=");
- ipw.print(type);
- ipw.print(" expectedWhenElapsed=");
- TimeUtils.formatDuration(expectedWhenElapsed, nowELAPSED, ipw);
- ipw.print(" expectedMaxWhenElapsed=");
- TimeUtils.formatDuration(expectedMaxWhenElapsed, nowELAPSED, ipw);
- ipw.print(" whenElapsed=");
- TimeUtils.formatDuration(whenElapsed, nowELAPSED, ipw);
- ipw.print(" maxWhenElapsed=");
- TimeUtils.formatDuration(maxWhenElapsed, nowELAPSED, ipw);
- ipw.print(" when=");
+ ipw.print(typeToString(type));
+ ipw.print(" origWhen=");
if (isRtc) {
- ipw.print(sdf.format(new Date(when)));
+ ipw.print(sdf.format(new Date(origWhen)));
} else {
- TimeUtils.formatDuration(when, nowELAPSED, ipw);
+ TimeUtils.formatDuration(origWhen, nowELAPSED, ipw);
}
- ipw.println();
-
- ipw.print("window=");
+ ipw.print(" window=");
TimeUtils.formatDuration(windowLength, ipw);
ipw.print(" repeatInterval=");
ipw.print(repeatInterval);
@@ -168,6 +257,19 @@ class Alarm {
ipw.print(" flags=0x");
ipw.println(Integer.toHexString(flags));
+ ipw.print("policyWhenElapsed:");
+ for (int i = 0; i < NUM_POLICIES; i++) {
+ ipw.print(" " + policyIndexToString(i) + "=");
+ TimeUtils.formatDuration(mPolicyWhenElapsed[i], nowELAPSED, ipw);
+ }
+ ipw.println();
+
+ ipw.print("whenElapsed=");
+ TimeUtils.formatDuration(getWhenElapsed(), nowELAPSED, ipw);
+ ipw.print(" maxWhenElapsed=");
+ TimeUtils.formatDuration(mMaxWhenElapsed, nowELAPSED, ipw);
+ ipw.println();
+
if (alarmClock != null) {
ipw.println("Alarm clock:");
@@ -177,9 +279,10 @@ class Alarm {
ipw.print(" showIntent=");
ipw.println(alarmClock.getShowIntent());
}
- ipw.print("operation=");
- ipw.println(operation);
-
+ if (operation != null) {
+ ipw.print("operation=");
+ ipw.println(operation);
+ }
if (listener != null) {
ipw.print("listener=");
ipw.println(listener.asBinder());
@@ -191,7 +294,7 @@ class Alarm {
proto.write(AlarmProto.TAG, statsTag);
proto.write(AlarmProto.TYPE, type);
- proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+ proto.write(AlarmProto.TIME_UNTIL_WHEN_ELAPSED_MS, getWhenElapsed() - nowElapsed);
proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
proto.write(AlarmProto.COUNT, count);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 16be7e5e3732..82819dab0f69 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -26,6 +26,10 @@ import static android.app.AlarmManager.RTC_WAKEUP;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.UserHandle.USER_SYSTEM;
+import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
+import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManagerInternal;
@@ -39,12 +43,10 @@ import android.app.PendingIntent;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManagerInternal;
-import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Binder;
@@ -68,6 +70,7 @@ import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.system.Os;
import android.text.TextUtils;
@@ -76,7 +79,6 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
-import android.util.KeyValueListParser;
import android.util.Log;
import android.util.LongArrayQueue;
import android.util.MutableBoolean;
@@ -102,6 +104,7 @@ import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
import com.android.server.DeviceIdleInternal;
import com.android.server.EventLogTags;
+import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
@@ -374,7 +377,7 @@ public class AlarmManagerService extends SystemService {
* holding the AlarmManagerService.mLock lock.
*/
@VisibleForTesting
- final class Constants extends ContentObserver {
+ final class Constants implements DeviceConfig.OnPropertiesChangedListener {
// Key names stored in the settings value.
@VisibleForTesting
static final String KEY_MIN_FUTURITY = "min_futurity";
@@ -394,17 +397,19 @@ public class AlarmManagerService extends SystemService {
@VisibleForTesting
static final String KEY_MAX_ALARMS_PER_UID = "max_alarms_per_uid";
private static final String KEY_APP_STANDBY_WINDOW = "app_standby_window";
+ private static final String KEY_PREFIX_STANDBY_QUOTA = "standby_quota_";
@VisibleForTesting
final String[] KEYS_APP_STANDBY_QUOTAS = {
- "standby_active_quota",
- "standby_working_quota",
- "standby_frequent_quota",
- "standby_rare_quota",
- "standby_never_quota",
+ KEY_PREFIX_STANDBY_QUOTA + "active",
+ KEY_PREFIX_STANDBY_QUOTA + "working",
+ KEY_PREFIX_STANDBY_QUOTA + "frequent",
+ KEY_PREFIX_STANDBY_QUOTA + "rare",
+ KEY_PREFIX_STANDBY_QUOTA + "never",
};
// Not putting this in the KEYS_APP_STANDBY_QUOTAS array because this uses a different
// window size.
- private static final String KEY_APP_STANDBY_RESTRICTED_QUOTA = "standby_restricted_quota";
+ private static final String KEY_APP_STANDBY_RESTRICTED_QUOTA =
+ KEY_PREFIX_STANDBY_QUOTA + "restricted";
private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW =
"app_standby_restricted_window";
@@ -458,20 +463,18 @@ public class AlarmManagerService extends SystemService {
public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA;
public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW;
- private ContentResolver mResolver;
- private final KeyValueListParser mParser = new KeyValueListParser(',');
private long mLastAllowWhileIdleWhitelistDuration = -1;
- public Constants(Handler handler) {
- super(handler);
+ Constants() {
updateAllowWhileIdleWhitelistDurationLocked();
+ for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
+ APP_STANDBY_QUOTAS[i] = DEFAULT_APP_STANDBY_QUOTAS[i];
+ }
}
- public void start(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.ALARM_MANAGER_CONSTANTS), false, this);
- updateConstants();
+ public void start() {
+ mInjector.registerDeviceConfigListener(this);
+ onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ALARM_MANAGER));
}
public void updateAllowWhileIdleWhitelistDurationLocked() {
@@ -484,71 +487,115 @@ public class AlarmManagerService extends SystemService {
}
@Override
- public void onChange(boolean selfChange, Uri uri) {
- updateConstants();
- }
-
- private void updateConstants() {
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ boolean standbyQuotaUpdated = false;
synchronized (mLock) {
- try {
- mParser.setString(Settings.Global.getString(mResolver,
- Settings.Global.ALARM_MANAGER_CONSTANTS));
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad alarm manager settings", e);
- }
-
- MIN_FUTURITY = mParser.getLong(KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
- MIN_INTERVAL = mParser.getLong(KEY_MIN_INTERVAL, DEFAULT_MIN_INTERVAL);
- MAX_INTERVAL = mParser.getLong(KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL);
- ALLOW_WHILE_IDLE_SHORT_TIME = mParser.getLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME,
- DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME);
- ALLOW_WHILE_IDLE_LONG_TIME = mParser.getLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME,
- DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME);
- ALLOW_WHILE_IDLE_WHITELIST_DURATION = mParser.getLong(
- KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
- DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
- LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
- DEFAULT_LISTENER_TIMEOUT);
-
- APP_STANDBY_WINDOW = mParser.getLong(KEY_APP_STANDBY_WINDOW,
- DEFAULT_APP_STANDBY_WINDOW);
- if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
- Slog.w(TAG, "Cannot exceed the app_standby_window size of "
- + DEFAULT_APP_STANDBY_WINDOW);
- APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
- } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
- // Not recommended outside of testing.
- Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
- }
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ continue;
+ }
- APP_STANDBY_QUOTAS[ACTIVE_INDEX] = mParser.getInt(
- KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
- DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
- for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
- APP_STANDBY_QUOTAS[i] = mParser.getInt(KEYS_APP_STANDBY_QUOTAS[i],
- Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
+ switch (name) {
+ case KEY_MIN_FUTURITY:
+ MIN_FUTURITY = properties.getLong(
+ KEY_MIN_FUTURITY, DEFAULT_MIN_FUTURITY);
+ break;
+ case KEY_MIN_INTERVAL:
+ MIN_INTERVAL = properties.getLong(
+ KEY_MIN_INTERVAL, DEFAULT_MIN_INTERVAL);
+ break;
+ case KEY_MAX_INTERVAL:
+ MAX_INTERVAL = properties.getLong(
+ KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL);
+ break;
+ case KEY_ALLOW_WHILE_IDLE_SHORT_TIME:
+ ALLOW_WHILE_IDLE_SHORT_TIME = properties.getLong(
+ KEY_ALLOW_WHILE_IDLE_SHORT_TIME,
+ DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME);
+ break;
+ case KEY_ALLOW_WHILE_IDLE_LONG_TIME:
+ ALLOW_WHILE_IDLE_LONG_TIME = properties.getLong(
+ KEY_ALLOW_WHILE_IDLE_LONG_TIME,
+ DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME);
+ break;
+ case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
+ ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
+ KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
+ DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+ updateAllowWhileIdleWhitelistDurationLocked();
+ break;
+ case KEY_LISTENER_TIMEOUT:
+ LISTENER_TIMEOUT = properties.getLong(
+ KEY_LISTENER_TIMEOUT, DEFAULT_LISTENER_TIMEOUT);
+ break;
+ case KEY_MAX_ALARMS_PER_UID:
+ MAX_ALARMS_PER_UID = properties.getInt(
+ KEY_MAX_ALARMS_PER_UID, DEFAULT_MAX_ALARMS_PER_UID);
+ if (MAX_ALARMS_PER_UID < DEFAULT_MAX_ALARMS_PER_UID) {
+ Slog.w(TAG, "Cannot set " + KEY_MAX_ALARMS_PER_UID + " lower than "
+ + DEFAULT_MAX_ALARMS_PER_UID);
+ MAX_ALARMS_PER_UID = DEFAULT_MAX_ALARMS_PER_UID;
+ }
+ break;
+ case KEY_APP_STANDBY_WINDOW:
+ case KEY_APP_STANDBY_RESTRICTED_WINDOW:
+ updateStandbyWindowsLocked();
+ break;
+ default:
+ if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
+ // The quotas need to be updated in order, so we can't just rely
+ // on the property iteration order.
+ updateStandbyQuotasLocked();
+ standbyQuotaUpdated = true;
+ }
+ break;
+ }
}
+ }
+ }
- APP_STANDBY_RESTRICTED_QUOTA = Math.max(1,
- mParser.getInt(KEY_APP_STANDBY_RESTRICTED_QUOTA,
- DEFAULT_APP_STANDBY_RESTRICTED_QUOTA));
+ private void updateStandbyQuotasLocked() {
+ // The bucket quotas need to be read as an atomic unit but the properties passed to
+ // onPropertiesChanged may only have one key populated at a time.
+ final DeviceConfig.Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_ALARM_MANAGER, KEYS_APP_STANDBY_QUOTAS);
- APP_STANDBY_RESTRICTED_WINDOW = Math.max(APP_STANDBY_WINDOW,
- mParser.getLong(KEY_APP_STANDBY_RESTRICTED_WINDOW,
- DEFAULT_APP_STANDBY_RESTRICTED_WINDOW));
+ APP_STANDBY_QUOTAS[ACTIVE_INDEX] = properties.getInt(
+ KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX],
+ DEFAULT_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ for (int i = WORKING_INDEX; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
+ APP_STANDBY_QUOTAS[i] = properties.getInt(
+ KEYS_APP_STANDBY_QUOTAS[i],
+ Math.min(APP_STANDBY_QUOTAS[i - 1], DEFAULT_APP_STANDBY_QUOTAS[i]));
+ }
- MAX_ALARMS_PER_UID = mParser.getInt(KEY_MAX_ALARMS_PER_UID,
- DEFAULT_MAX_ALARMS_PER_UID);
- if (MAX_ALARMS_PER_UID < DEFAULT_MAX_ALARMS_PER_UID) {
- Slog.w(TAG, "Cannot set " + KEY_MAX_ALARMS_PER_UID + " lower than "
- + DEFAULT_MAX_ALARMS_PER_UID);
- MAX_ALARMS_PER_UID = DEFAULT_MAX_ALARMS_PER_UID;
- }
+ APP_STANDBY_RESTRICTED_QUOTA = Math.max(1,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_ALARM_MANAGER,
+ KEY_APP_STANDBY_RESTRICTED_QUOTA,
+ DEFAULT_APP_STANDBY_RESTRICTED_QUOTA));
+ }
- updateAllowWhileIdleWhitelistDurationLocked();
+ private void updateStandbyWindowsLocked() {
+ // The bucket windows need to be read as an atomic unit but the properties passed to
+ // onPropertiesChanged may only have one key populated at a time.
+ final DeviceConfig.Properties properties = DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_ALARM_MANAGER,
+ KEY_APP_STANDBY_WINDOW, KEY_APP_STANDBY_RESTRICTED_WINDOW);
+ APP_STANDBY_WINDOW = properties.getLong(
+ KEY_APP_STANDBY_WINDOW, DEFAULT_APP_STANDBY_WINDOW);
+ if (APP_STANDBY_WINDOW > DEFAULT_APP_STANDBY_WINDOW) {
+ Slog.w(TAG, "Cannot exceed the app_standby_window size of "
+ + DEFAULT_APP_STANDBY_WINDOW);
+ APP_STANDBY_WINDOW = DEFAULT_APP_STANDBY_WINDOW;
+ } else if (APP_STANDBY_WINDOW < DEFAULT_APP_STANDBY_WINDOW) {
+ // Not recommended outside of testing.
+ Slog.w(TAG, "Using a non-default app_standby_window of " + APP_STANDBY_WINDOW);
}
+
+ APP_STANDBY_RESTRICTED_WINDOW = Math.max(APP_STANDBY_WINDOW,
+ properties.getLong(
+ KEY_APP_STANDBY_RESTRICTED_WINDOW,
+ DEFAULT_APP_STANDBY_RESTRICTED_WINDOW));
}
void dump(PrintWriter pw, String prefix) {
@@ -683,9 +730,9 @@ public class AlarmManagerService extends SystemService {
}
// within each class, sort by nominal delivery time
- if (lhs.whenElapsed < rhs.whenElapsed) {
+ if (lhs.getWhenElapsed() < rhs.getWhenElapsed()) {
return -1;
- } else if (lhs.whenElapsed > rhs.whenElapsed) {
+ } else if (lhs.getWhenElapsed() > rhs.getWhenElapsed()) {
return 1;
}
@@ -754,9 +801,12 @@ public class AlarmManagerService extends SystemService {
this(context, new Injector(context));
}
+ private static boolean isRtc(int type) {
+ return (type == RTC || type == RTC_WAKEUP);
+ }
+
private long convertToElapsed(long when, int type) {
- final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
- if (isRtc) {
+ if (isRtc(type)) {
when -= mInjector.getCurrentTimeMillis() - mInjector.getElapsedRealtime();
}
return when;
@@ -779,13 +829,29 @@ public class AlarmManagerService extends SystemService {
}
// The RTC clock has moved arbitrarily, so we need to recalculate all the RTC alarm deliveries.
- void reevaluateRtcAlarms(final long nowElapsed) {
+ void reevaluateRtcAlarms() {
synchronized (mLock) {
- final ArrayList<Alarm> rtcAlarms = mAlarmStore.remove(a -> (a.type == RTC
- || a.type == RTC_WAKEUP));
- for (final Alarm a : rtcAlarms) {
- restoreAlarmLocked(a, nowElapsed);
- setImplLocked(a);
+ boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
+ if (!isRtc(a.type)) {
+ return false;
+ }
+ return restoreRequestedTime(a);
+ });
+
+ if (mNextWakeFromIdle != null && isRtc(mNextWakeFromIdle.type)) {
+ // The next wake from idle got updated due to the rtc time change, implying we need
+ // to update the time we have to come out of idle too.
+ changed |= mAlarmStore.updateAlarmDeliveries(a -> {
+ if (a != mPendingIdleUntil) {
+ return false;
+ }
+ return adjustIdleUntilTime(a);
+ });
+ }
+
+ if (changed) {
+ rescheduleKernelAlarmsLocked();
+ // Only time shifted, so the next alarm clock will not change
}
}
}
@@ -800,7 +866,7 @@ public class AlarmManagerService extends SystemService {
boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
final long start = mStatLogger.getTime();
- final boolean changed = mAlarmStore.recalculateAlarmDeliveries(a -> {
+ final boolean changed = mAlarmStore.updateAlarmDeliveries(a -> {
final Pair<String, Integer> packageUser =
Pair.create(a.sourcePackage, UserHandle.getUserId(a.creatorUid));
if (targetPackages != null && !targetPackages.contains(packageUser)) {
@@ -813,23 +879,8 @@ public class AlarmManagerService extends SystemService {
return changed;
}
- private void restoreAlarmLocked(Alarm a, long nowElapsed) {
- a.when = a.origWhen;
- long whenElapsed = convertToElapsed(a.when, a.type);
- final long maxElapsed;
- if (a.windowLength == AlarmManager.WINDOW_EXACT) {
- // Exact
- maxElapsed = whenElapsed;
- } else {
- // Not exact. Preserve any explicit window, otherwise recalculate
- // the window based on the alarm's new futurity. Note that this
- // reflects a policy of preferring timely to deferred delivery.
- maxElapsed = (a.windowLength > 0)
- ? clampPositive(whenElapsed + a.windowLength)
- : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
- }
- a.expectedWhenElapsed = a.whenElapsed = whenElapsed;
- a.expectedMaxWhenElapsed = a.maxWhenElapsed = maxElapsed;
+ private boolean restoreRequestedTime(Alarm a) {
+ return a.setPolicyElapsed(REQUESTER_POLICY_INDEX, convertToElapsed(a.origWhen, a.type));
}
static long clampPositive(long val) {
@@ -929,14 +980,17 @@ public class AlarmManagerService extends SystemService {
// Recurring alarms may have passed several alarm intervals while the
// alarm was kept pending. Send the appropriate trigger count.
if (alarm.repeatInterval > 0) {
- alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX))
+ / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
- final long nextElapsed = alarm.expectedWhenElapsed + delta;
- setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
- maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, null, null, alarm.flags,
- alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
+ final long nextElapsed = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX) + delta;
+ final long nextMaxElapsed = maxTriggerTime(nowELAPSED, nextElapsed,
+ alarm.repeatInterval);
+ setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
+ nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
+ null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
+ alarm.packageName);
// Kernel alarms will be rescheduled as needed in setImplLocked
}
}
@@ -982,18 +1036,10 @@ public class AlarmManagerService extends SystemService {
if (mPendingWhileIdleAlarms.size() > 0) {
ArrayList<Alarm> alarms = mPendingWhileIdleAlarms;
mPendingWhileIdleAlarms = new ArrayList<>();
- final long nowElapsed = mInjector.getElapsedRealtime();
for (int i = alarms.size() - 1; i >= 0; i--) {
- Alarm a = alarms.get(i);
- restoreAlarmLocked(a, nowElapsed);
- setImplLocked(a);
+ setImplLocked(alarms.get(i));
}
}
-
- // Reschedule everything.
- rescheduleKernelAlarmsLocked();
- updateNextAlarmClockLocked();
-
}
static final class InFlight {
@@ -1199,7 +1245,7 @@ public class AlarmManagerService extends SystemService {
synchronized (mLock) {
mHandler = new AlarmHandler();
- mConstants = new Constants(mHandler);
+ mConstants = new Constants();
mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
mNextWakeup = mNextNonWakeup = 0;
@@ -1284,7 +1330,7 @@ public class AlarmManagerService extends SystemService {
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
- mConstants.start(getContext().getContentResolver());
+ mConstants.start();
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController =
LocalServices.getService(DeviceIdleInternal.class);
@@ -1405,6 +1451,11 @@ public class AlarmManagerService extends SystemService {
}
}
+ if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // Do not support windows for idle-until alarms.
+ windowLength = AlarmManager.WINDOW_EXACT;
+ }
+
// Sanity check the window length. This will catch people mistakenly
// trying to pass an end-of-window timestamp rather than a duration.
if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
@@ -1471,17 +1522,17 @@ public class AlarmManagerService extends SystemService {
Slog.w(TAG, errorMsg);
throw new IllegalStateException(errorMsg);
}
- setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, directReceiver, listenerTag, flags, workSource,
- alarmClock, callingUid, callingPackage);
+ setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
+ directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
+ callingPackage);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+ long interval, PendingIntent operation, IAlarmListener directReceiver,
String listenerTag, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
- Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
+ final Alarm a = new Alarm(type, when, whenElapsed, windowLength, interval,
operation, directReceiver, listenerTag, workSource, flags, alarmClock,
callingUid, callingPackage);
if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, callingPackage)) {
@@ -1516,72 +1567,55 @@ public class AlarmManagerService extends SystemService {
}
/**
- * Adjusts the idle-until alarm delivery time based on the upcoming wake-from-idle alarm.
+ * An alarm with {@link AlarmManager#FLAG_IDLE_UNTIL} is a special alarm that will put the
+ * system into idle until it goes off. We need to pull it earlier if there are existing alarms
+ * that have requested to bring us out of idle at an earlier time.
*
* @param alarm The alarm to adjust
* @return true if the alarm delivery time was updated.
*/
private boolean adjustIdleUntilTime(Alarm alarm) {
- if ((alarm.flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ if ((alarm.flags & AlarmManager.FLAG_IDLE_UNTIL) == 0) {
return false;
}
- // This is a special alarm that will put the system into idle until it goes off.
- // The caller has given the time they want this to happen at, however we need
- // to pull that earlier if there are existing alarms that have requested to
- // bring us out of idle at an earlier time.
- if (mNextWakeFromIdle != null && alarm.whenElapsed > mNextWakeFromIdle.whenElapsed) {
- alarm.when = alarm.whenElapsed = alarm.maxWhenElapsed = mNextWakeFromIdle.whenElapsed;
+ restoreRequestedTime(alarm);
+ long triggerBeforeFuzz = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX);
+ if (mNextWakeFromIdle != null && triggerBeforeFuzz > mNextWakeFromIdle.getWhenElapsed()) {
+ triggerBeforeFuzz = mNextWakeFromIdle.getWhenElapsed();
}
// Add fuzz to make the alarm go off some time before the actual desired time.
- final long nowElapsed = mInjector.getElapsedRealtime();
- final int fuzz = fuzzForDuration(alarm.whenElapsed - nowElapsed);
+ final int fuzz = fuzzForDuration(alarm.getWhenElapsed() - mInjector.getElapsedRealtime());
+ final int delta;
if (fuzz > 0) {
if (mRandom == null) {
mRandom = new Random();
}
- final int delta = mRandom.nextInt(fuzz);
- alarm.whenElapsed -= delta;
- if (false) {
- Slog.d(TAG, "Alarm when: " + alarm.whenElapsed);
- Slog.d(TAG, "Delta until alarm: " + (alarm.whenElapsed - nowElapsed));
- Slog.d(TAG, "Applied fuzz: " + fuzz);
- Slog.d(TAG, "Final delta: " + delta);
- Slog.d(TAG, "Final when: " + alarm.whenElapsed);
- }
- alarm.when = alarm.maxWhenElapsed = alarm.whenElapsed;
+ delta = mRandom.nextInt(fuzz);
+ } else {
+ delta = 0;
}
+ alarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, triggerBeforeFuzz - delta);
return true;
}
/**
- * Adjusts the alarm delivery time based on the current app standby bucket.
+ * Adjusts the alarm's policy time for app_standby.
*
- * @param alarm The alarm to adjust
- * @return true if the alarm delivery time was updated.
+ * @param alarm The alarm to update.
+ * @return {@code true} if the actual delivery time of the given alarm was updated due to
+ * adjustments made in this call.
*/
private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
- if (isExemptFromAppStandby(alarm)) {
- return false;
- }
- if (mAppStandbyParole) {
- if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
- // We did defer this alarm earlier, restore original requirements
- alarm.whenElapsed = alarm.expectedWhenElapsed;
- alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
- return true;
- }
- return false;
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ if (isExemptFromAppStandby(alarm) || mAppStandbyParole) {
+ return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
}
- final long oldWhenElapsed = alarm.whenElapsed;
- final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
final String sourcePackage = alarm.sourcePackage;
final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
- sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
+ sourcePackage, sourceUserId, nowElapsed);
- // Quota deferring implementation:
- boolean deferred = false;
final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
sourceUserId);
if (standbyBucket == UsageStatsManager.STANDBY_BUCKET_RESTRICTED) {
@@ -1591,14 +1625,9 @@ public class AlarmManagerService extends SystemService {
if (wakeupsInWindow > 0) {
final long lastWakeupTime = mAppWakeupHistory.getNthLastWakeupForPackage(
sourcePackage, sourceUserId, mConstants.APP_STANDBY_RESTRICTED_QUOTA);
- if (mInjector.getElapsedRealtime() - lastWakeupTime
- < mConstants.APP_STANDBY_RESTRICTED_WINDOW) {
- final long minElapsed =
- lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW;
- if (alarm.expectedWhenElapsed < minElapsed) {
- alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
- deferred = true;
- }
+ if ((nowElapsed - lastWakeupTime) < mConstants.APP_STANDBY_RESTRICTED_WINDOW) {
+ return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX,
+ lastWakeupTime + mConstants.APP_STANDBY_RESTRICTED_WINDOW);
}
}
} else {
@@ -1607,7 +1636,7 @@ public class AlarmManagerService extends SystemService {
final long minElapsed;
if (quotaForBucket <= 0) {
// Just keep deferring for a day till the quota changes
- minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
+ minElapsed = nowElapsed + MILLIS_IN_DAY;
} else {
// Suppose the quota for window was q, and the qth last delivery time for this
// package was t(q) then the next delivery must be after t(q) + <window_size>
@@ -1615,19 +1644,11 @@ public class AlarmManagerService extends SystemService {
sourcePackage, sourceUserId, quotaForBucket);
minElapsed = t + mConstants.APP_STANDBY_WINDOW;
}
- if (alarm.expectedWhenElapsed < minElapsed) {
- alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
- deferred = true;
- }
+ return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, minElapsed);
}
}
- if (!deferred) {
- // Restore original requirements in case they were changed earlier.
- alarm.whenElapsed = alarm.expectedWhenElapsed;
- alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
- }
-
- return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
+ // wakeupsInWindow are less than the permitted quota, hence no deferring is needed.
+ return alarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, nowElapsed);
}
private static boolean isAllowedWhileIdle(Alarm a) {
@@ -1647,7 +1668,7 @@ public class AlarmManagerService extends SystemService {
ent.tag = a.operation.getTag("");
ent.op = "SET";
ent.elapsedRealtime = mInjector.getElapsedRealtime();
- ent.argRealtime = a.whenElapsed;
+ ent.argRealtime = a.getWhenElapsed();
mAllowWhileIdleDispatches.add(ent);
if (mPendingIdleUntil == null) {
IdleDispatchEntry ent2 = new IdleDispatchEntry();
@@ -1660,6 +1681,7 @@ public class AlarmManagerService extends SystemService {
if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) {
Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil
+ " to " + a);
+ mAlarmStore.remove(mPendingIdleUntil::equals);
}
mPendingIdleUntil = a;
final ArrayList<Alarm> notAllowedWhileIdleAlarms = mAlarmStore.remove(
@@ -1674,18 +1696,16 @@ public class AlarmManagerService extends SystemService {
}
}
if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
- if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
+ if (mNextWakeFromIdle == null || mNextWakeFromIdle.getWhenElapsed()
+ > a.getWhenElapsed()) {
mNextWakeFromIdle = a;
// If this wake from idle is earlier than whatever was previously scheduled,
- // and we are currently idling, then we need to rebatch alarms in case the idle
- // until time needs to be updated.
+ // and we are currently idling, then the idle-until time needs to be updated.
if (mPendingIdleUntil != null) {
- final long nowElapsed = mInjector.getElapsedRealtime();
- mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+ mAlarmStore.updateAlarmDeliveries(alarm -> {
if (alarm != mPendingIdleUntil) {
return false;
}
- restoreAlarmLocked(alarm, nowElapsed);
return adjustIdleUntilTime(alarm);
});
}
@@ -2519,7 +2539,7 @@ public class AlarmManagerService extends SystemService {
long getNextWakeFromIdleTimeImpl() {
synchronized (mLock) {
- return mNextWakeFromIdle != null ? mNextWakeFromIdle.whenElapsed : Long.MAX_VALUE;
+ return mNextWakeFromIdle != null ? mNextWakeFromIdle.getWhenElapsed() : Long.MAX_VALUE;
}
}
@@ -2740,12 +2760,11 @@ public class AlarmManagerService extends SystemService {
restorePending = true;
}
if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
- mNextWakeFromIdle = null;
- mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+ mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+ mAlarmStore.updateAlarmDeliveries(alarm -> {
if (alarm != mPendingIdleUntil) {
return false;
}
- restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
return adjustIdleUntilTime(alarm);
});
}
@@ -2790,15 +2809,14 @@ public class AlarmManagerService extends SystemService {
mPendingBackgroundAlarms.removeAt(i);
}
}
- // If we're currently keying off of this app's alarms for doze transitions,
- // make sure to reset to other triggers.
+ // If we're currently using this app's alarms to come out of doze,
+ // make sure to reset to any remaining WAKE_FROM_IDLE alarms.
if (mNextWakeFromIdle != null && mNextWakeFromIdle.uid == uid) {
- mNextWakeFromIdle = null;
- mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+ mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+ mAlarmStore.updateAlarmDeliveries(alarm -> {
if (alarm != mPendingIdleUntil) {
return false;
}
- restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
return adjustIdleUntilTime(alarm);
});
}
@@ -2862,15 +2880,14 @@ public class AlarmManagerService extends SystemService {
mPendingBackgroundAlarms.removeAt(i);
}
}
- // If we're currently keying off of this app's alarms for doze transitions,
- // make sure to reset to other triggers.
+ // If we're currently using this app's alarms to come out of doze,
+ // make sure to reset to any remaining WAKE_FROM_IDLE alarms.
if (removedNextWakeFromIdle.value) {
- mNextWakeFromIdle = null;
- mAlarmStore.recalculateAlarmDeliveries(alarm -> {
+ mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+ mAlarmStore.updateAlarmDeliveries(alarm -> {
if (alarm != mPendingIdleUntil) {
return false;
}
- restoreAlarmLocked(alarm, mInjector.getElapsedRealtime());
return adjustIdleUntilTime(alarm);
});
}
@@ -3027,20 +3044,6 @@ public class AlarmManagerService extends SystemService {
}
}
- private static final String labelForType(int type) {
- switch (type) {
- case RTC:
- return "RTC";
- case RTC_WAKEUP:
- return "RTC_WAKEUP";
- case ELAPSED_REALTIME:
- return "ELAPSED";
- case ELAPSED_REALTIME_WAKEUP:
- return "ELAPSED_WAKEUP";
- }
- return "--unknown--";
- }
-
private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
String prefix, long nowELAPSED, SimpleDateFormat sdf) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, prefix, prefix);
@@ -3051,7 +3054,7 @@ public class AlarmManagerService extends SystemService {
long nowELAPSED, SimpleDateFormat sdf) {
for (int i = list.size() - 1; i >= 0; i--) {
final Alarm a = list.get(i);
- final String label = labelForType(a.type);
+ final String label = Alarm.typeToString(a.type);
ipw.print(label);
ipw.print(" #");
ipw.print(i);
@@ -3081,6 +3084,9 @@ public class AlarmManagerService extends SystemService {
}
final String sourcePackage = alarm.sourcePackage;
final int sourceUid = alarm.creatorUid;
+ if (UserHandle.isCore(sourceUid)) {
+ return false;
+ }
return (mAppStateTracker != null) &&
mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage,
exemptOnBatterySaver);
@@ -3125,11 +3131,7 @@ public class AlarmManagerService extends SystemService {
// Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
// alarm went off for this app. Reschedule the alarm to be in the
// correct time period.
- alarm.expectedWhenElapsed = alarm.whenElapsed = minTime;
- if (alarm.maxWhenElapsed < minTime) {
- alarm.maxWhenElapsed = minTime;
- }
- alarm.expectedMaxWhenElapsed = alarm.maxWhenElapsed;
+ alarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, minTime);
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
@@ -3169,12 +3171,11 @@ public class AlarmManagerService extends SystemService {
restorePendingWhileIdleAlarmsLocked();
}
if (mNextWakeFromIdle == alarm) {
- mNextWakeFromIdle = null;
- mAlarmStore.recalculateAlarmDeliveries(a -> {
+ mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
+ mAlarmStore.updateAlarmDeliveries(a -> {
if (a != mPendingIdleUntil) {
return false;
}
- restoreAlarmLocked(a, nowELAPSED);
return adjustIdleUntilTime(a);
});
}
@@ -3184,14 +3185,17 @@ public class AlarmManagerService extends SystemService {
if (alarm.repeatInterval > 0) {
// this adjustment will be zero if we're late by
// less than one full repeat interval
- alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
+ alarm.count += (nowELAPSED - alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX))
+ / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
- final long nextElapsed = alarm.expectedWhenElapsed + delta;
- setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
- maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, null, null, alarm.flags,
- alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
+ final long nextElapsed = alarm.getPolicyElapsed(REQUESTER_POLICY_INDEX) + delta;
+ final long nextMaxElapsed = maxTriggerTime(nowELAPSED, nextElapsed,
+ alarm.repeatInterval);
+ setImplLocked(alarm.type, alarm.origWhen + delta, nextElapsed,
+ nextMaxElapsed - nextElapsed, alarm.repeatInterval, alarm.operation, null,
+ null, alarm.flags, alarm.workSource, alarm.alarmClock, alarm.uid,
+ alarm.packageName);
}
if (alarm.wakeup) {
@@ -3233,7 +3237,7 @@ public class AlarmManagerService extends SystemService {
}
}
- static int fuzzForDuration(long duration) {
+ int fuzzForDuration(long duration) {
if (duration < 15 * 60 * 1000) {
// If the duration until the time is less than 15 minutes, the maximum fuzz
// is the duration.
@@ -3382,6 +3386,11 @@ public class AlarmManagerService extends SystemService {
ClockReceiver getClockReceiver(AlarmManagerService service) {
return service.new ClockReceiver();
}
+
+ void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER,
+ JobSchedulerBackgroundThread.getExecutor(), listener);
+ }
}
private class AlarmThread extends Thread {
@@ -3431,7 +3440,7 @@ public class AlarmManagerService extends SystemService {
FrameworkStatsLog.write(FrameworkStatsLog.WALL_CLOCK_TIME_SHIFTED, nowRTC);
removeImpl(null, mTimeTickTrigger);
removeImpl(mDateChangeSender, null);
- reevaluateRtcAlarms(nowELAPSED);
+ reevaluateRtcAlarms();
mClockReceiver.scheduleTimeTickEvent();
mClockReceiver.scheduleDateChangedEvent();
synchronized (mLock) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
index 9fdbb8bbffc7..7a846b9b82db 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
@@ -48,6 +48,15 @@ public interface AlarmStore {
ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);
/**
+ * Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
+ * based on {@link Alarm#getWhenElapsed()}.
+ *
+ * @return An alarm object matching the description above or {@code null} if no such alarm was
+ * found.
+ */
+ Alarm getNextWakeFromIdleAlarm();
+
+ /**
* Returns the total number of alarms in this store.
*/
int size();
@@ -71,7 +80,7 @@ public interface AlarmStore {
/**
* Removes all alarms that are pending delivery at the given time.
*
- * @param nowElapsed The time at which delivery eligibility is evaluated.
+ * @param nowElapsed The time at which delivery eligibility is evaluated.
* @return The list of alarms pending at the given time.
*/
ArrayList<Alarm> removePendingAlarms(long nowElapsed);
@@ -82,7 +91,7 @@ public interface AlarmStore {
*
* @return {@code true} if any of the alarm deliveries changed due to this call.
*/
- boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);
+ boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator);
/**
* Returns all the alarms in the form of a list.
@@ -97,6 +106,7 @@ public interface AlarmStore {
* Primary useful for debugging. Can be called from the
* {@link android.os.Binder#dump(FileDescriptor PrintWriter, String[]) dump} method of the
* caller.
+ *
* @param ipw The {@link IndentingPrintWriter} to write to.
* @param nowElapsed the time when the dump is requested in the
* {@link SystemClock#elapsedRealtime()
@@ -112,7 +122,7 @@ public interface AlarmStore {
/**
* A functional interface used to update the alarm. Used to describe the update in
- * {@link #recalculateAlarmDeliveries(AlarmDeliveryCalculator)}
+ * {@link #updateAlarmDeliveries(AlarmDeliveryCalculator)}
*/
@FunctionalInterface
interface AlarmDeliveryCalculator {
@@ -125,3 +135,4 @@ public interface AlarmStore {
boolean updateAlarmDelivery(Alarm a);
}
}
+
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
index 91c0c0579c69..cbfe80bdce24 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
@@ -66,8 +66,8 @@ public class BatchingAlarmStore implements AlarmStore {
};
private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
- long when1 = a1.whenElapsed;
- long when2 = a2.whenElapsed;
+ long when1 = a1.getWhenElapsed();
+ long when2 = a2.getWhenElapsed();
if (when1 > when2) {
return 1;
}
@@ -99,11 +99,28 @@ public class BatchingAlarmStore implements AlarmStore {
}
if (!removed.isEmpty()) {
mSize -= removed.size();
+ // Not needed if only whole batches were removed, but keeping existing behavior.
rebatchAllAlarms();
}
return removed;
}
+ @Override
+ public Alarm getNextWakeFromIdleAlarm() {
+ for (final Batch batch : mAlarmBatches) {
+ if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) {
+ continue;
+ }
+ for (int i = 0; i < batch.size(); i++) {
+ final Alarm a = batch.get(i);
+ if ((a.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ return a;
+ }
+ }
+ }
+ return null;
+ }
+
private void rebatchAllAlarms() {
final long start = mStatLogger.getTime();
final ArrayList<Batch> oldBatches = (ArrayList<Batch>) mAlarmBatches.clone();
@@ -157,7 +174,7 @@ public class BatchingAlarmStore implements AlarmStore {
}
@Override
- public boolean recalculateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
+ public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
boolean changed = false;
for (final Batch b : mAlarmBatches) {
for (int i = 0; i < b.size(); i++) {
@@ -204,7 +221,7 @@ public class BatchingAlarmStore implements AlarmStore {
private void insertAndBatchAlarm(Alarm alarm) {
final int whichBatch = ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) ? -1
- : attemptCoalesce(alarm.whenElapsed, alarm.maxWhenElapsed);
+ : attemptCoalesce(alarm.getWhenElapsed(), alarm.getMaxWhenElapsed());
if (whichBatch < 0) {
addBatch(mAlarmBatches, new Batch(alarm));
@@ -247,8 +264,8 @@ public class BatchingAlarmStore implements AlarmStore {
final ArrayList<Alarm> mAlarms = new ArrayList<>();
Batch(Alarm seed) {
- mStart = seed.whenElapsed;
- mEnd = clampPositive(seed.maxWhenElapsed);
+ mStart = seed.getWhenElapsed();
+ mEnd = clampPositive(seed.getMaxWhenElapsed());
mFlags = seed.flags;
mAlarms.add(seed);
}
@@ -276,12 +293,12 @@ public class BatchingAlarmStore implements AlarmStore {
if (DEBUG_BATCH) {
Slog.v(TAG, "Adding " + alarm + " to " + this);
}
- if (alarm.whenElapsed > mStart) {
- mStart = alarm.whenElapsed;
+ if (alarm.getWhenElapsed() > mStart) {
+ mStart = alarm.getWhenElapsed();
newStart = true;
}
- if (alarm.maxWhenElapsed < mEnd) {
- mEnd = alarm.maxWhenElapsed;
+ if (alarm.getMaxWhenElapsed() < mEnd) {
+ mEnd = alarm.getMaxWhenElapsed();
}
mFlags |= alarm.flags;
@@ -309,11 +326,11 @@ public class BatchingAlarmStore implements AlarmStore {
Slog.wtf(TAG, "Removed TIME_TICK alarm");
}
} else {
- if (alarm.whenElapsed > newStart) {
- newStart = alarm.whenElapsed;
+ if (alarm.getWhenElapsed() > newStart) {
+ newStart = alarm.getWhenElapsed();
}
- if (alarm.maxWhenElapsed < newEnd) {
- newEnd = alarm.maxWhenElapsed;
+ if (alarm.getMaxWhenElapsed() < newEnd) {
+ newEnd = alarm.getMaxWhenElapsed();
}
newFlags |= alarm.flags;
i++;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b638fef36a9e..6c14233dba13 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -2590,13 +2590,14 @@ public class JobSchedulerService extends com.android.server.SystemService
// job that runs one of the app's services, as well as verifying that the
// named service properly requires the BIND_JOB_SERVICE permission
private void enforceValidJobRequest(int uid, JobInfo job) {
- final IPackageManager pm = AppGlobals.getPackageManager();
+ final PackageManager pm = getContext()
+ .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0)
+ .getPackageManager();
final ComponentName service = job.getService();
try {
ServiceInfo si = pm.getServiceInfo(service,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- UserHandle.getUserId(uid));
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
if (si == null) {
throw new IllegalArgumentException("No such service " + service);
}
@@ -2608,8 +2609,10 @@ public class JobSchedulerService extends com.android.server.SystemService
throw new IllegalArgumentException("Scheduled service " + service
+ " does not require android.permission.BIND_JOB_SERVICE permission");
}
- } catch (RemoteException e) {
- // Can't happen; the Package Manager is in this same process
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException(
+ "Tried to schedule job for non-existent package: "
+ + service.getPackageName());
}
}
@@ -2670,7 +2673,7 @@ public class JobSchedulerService extends com.android.server.SystemService
validateJobFlags(job, uid);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
null);
@@ -2698,7 +2701,7 @@ public class JobSchedulerService extends com.android.server.SystemService
validateJobFlags(job, uid);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
null);
@@ -2729,7 +2732,7 @@ public class JobSchedulerService extends com.android.server.SystemService
validateJobFlags(job, callerUid);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
packageName, userId, tag);
@@ -2742,7 +2745,7 @@ public class JobSchedulerService extends com.android.server.SystemService
public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
final int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
} finally {
@@ -2754,7 +2757,7 @@ public class JobSchedulerService extends com.android.server.SystemService
public JobInfo getPendingJob(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return JobSchedulerService.this.getPendingJob(uid, jobId);
} finally {
@@ -2765,7 +2768,7 @@ public class JobSchedulerService extends com.android.server.SystemService
@Override
public void cancelAll() throws RemoteException {
final int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid,
"cancelAll() called by app, callingUid=" + uid);
@@ -2778,7 +2781,7 @@ public class JobSchedulerService extends com.android.server.SystemService
public void cancel(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJob(uid, jobId, uid);
} finally {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 47ebf32fa875..999c53fb7daf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -18,16 +18,15 @@ package com.android.server.job.controllers;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.net.Uri;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Log;
@@ -116,10 +115,15 @@ public class ComponentController extends StateController {
ServiceInfo si = mServiceInfoCache.get(userId, service);
if (si == null) {
try {
- si = AppGlobals.getPackageManager().getServiceInfo(
- service, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ // createContextAsUser may potentially be expensive
+ // TODO: cache user context or improve ContextImpl implementation if this becomes
+ // a problem
+ si = mContext.createContextAsUser(UserHandle.of(userId), 0)
+ .getPackageManager()
+ .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
+ return null;
}
mServiceInfoCache.add(userId, service, si);
}
diff --git a/apex/media/OWNERS b/apex/media/OWNERS
index e83ea3a5087a..ced2fb5e2dcd 100644
--- a/apex/media/OWNERS
+++ b/apex/media/OWNERS
@@ -1,7 +1,10 @@
andrewlewis@google.com
aquilescanta@google.com
chz@google.com
+hdmoon@google.com
hkuang@google.com
+jinpark@google.com
+klhyun@google.com
lnilsson@google.com
marcone@google.com
sungsoo@google.com
diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp
index 409a04897f56..c2b00d5849c4 100644
--- a/apex/media/aidl/Android.bp
+++ b/apex/media/aidl/Android.bp
@@ -15,21 +15,21 @@
//
filegroup {
- name: "stable-mediasession2-aidl-srcs",
+ name: "stable-media-aidl-srcs",
srcs: ["stable/**/*.aidl"],
path: "stable",
}
filegroup {
- name: "private-mediasession2-aidl-srcs",
+ name: "private-media-aidl-srcs",
srcs: ["private/**/I*.aidl"],
path: "private",
}
filegroup {
- name: "mediasession2-aidl-srcs",
+ name: "media-aidl-srcs",
srcs: [
- ":private-mediasession2-aidl-srcs",
- ":stable-mediasession2-aidl-srcs",
+ ":private-media-aidl-srcs",
+ ":stable-media-aidl-srcs",
],
}
diff --git a/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
new file mode 100644
index 000000000000..92d673fd25cb
--- /dev/null
+++ b/apex/media/aidl/stable/android/media/MediaParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.media;
+
+parcelable MediaParceledListSlice<T>;
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index ce4b030467a7..813631e28a88 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -54,9 +54,10 @@ java_library {
filegroup {
name: "updatable-media-srcs",
srcs: [
+ ":media-aidl-srcs",
+ ":mediaparceledlistslice-java-srcs",
":mediaparser-srcs",
":mediasession2-java-srcs",
- ":mediasession2-aidl-srcs",
],
}
@@ -77,6 +78,15 @@ filegroup {
}
filegroup {
+ name: "mediaparceledlistslice-java-srcs",
+ srcs: [
+ "java/android/media/MediaParceledListSlice.java",
+ "java/android/media/BaseMediaParceledListSlice.java",
+ ],
+ path: "java",
+}
+
+filegroup {
name: "mediaparser-srcs",
srcs: [
"java/android/media/MediaParser.java"
diff --git a/apex/media/framework/api/module-lib-current.txt b/apex/media/framework/api/module-lib-current.txt
index d802177e249b..2b69863675a5 100644
--- a/apex/media/framework/api/module-lib-current.txt
+++ b/apex/media/framework/api/module-lib-current.txt
@@ -1 +1,15 @@
// Signature format: 2.0
+package android.media {
+
+ @Deprecated public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable {
+ ctor @Deprecated public MediaParceledListSlice(@NonNull java.util.List<T>);
+ method @Deprecated public int describeContents();
+ method @Deprecated @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList();
+ method @Deprecated public java.util.List<T> getList();
+ method @Deprecated public void setInlineCountLimit(int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR;
+ }
+
+}
+
diff --git a/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
new file mode 100644
index 000000000000..fb666098301a
--- /dev/null
+++ b/apex/media/framework/java/android/media/BaseMediaParceledListSlice.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 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.media;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a copied version of BaseParceledListSlice in framework with hidden API usages
+ * removed.
+ *
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * Caveat: for efficiency and security, all elements must be the same concrete type.
+ * In order to avoid writing the class name of each object, we must ensure that
+ * each object is the same type, or else unparceling then reparceling the data may yield
+ * a different result if the class name encoded in the Parcelable is a Base type.
+ * See b/17671747.
+ *
+ * @hide
+ */
+abstract class BaseMediaParceledListSlice<T> implements Parcelable {
+ private static String TAG = "BaseMediaParceledListSlice";
+ private static boolean DEBUG = false;
+
+ /*
+ * TODO get this number from somewhere else. For now set it to a quarter of
+ * the 1MB limit.
+ */
+ // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+ private static final int MAX_IPC_SIZE = 64 * 1024;
+
+ private final List<T> mList;
+
+ private int mInlineCountLimit = Integer.MAX_VALUE;
+
+ public BaseMediaParceledListSlice(List<T> list) {
+ mList = list;
+ }
+
+ @SuppressWarnings("unchecked")
+ BaseMediaParceledListSlice(Parcel p, ClassLoader loader) {
+ final int N = p.readInt();
+ mList = new ArrayList<T>(N);
+ if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+ if (N <= 0) {
+ return;
+ }
+
+ Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
+ Class<?> listElementClass = null;
+
+ int i = 0;
+ while (i < N) {
+ if (p.readInt() == 0) {
+ break;
+ }
+
+ final T parcelable = readCreator(creator, p, loader);
+ if (listElementClass == null) {
+ listElementClass = parcelable.getClass();
+ } else {
+ verifySameType(listElementClass, parcelable.getClass());
+ }
+
+ mList.add(parcelable);
+
+ if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ if (i >= N) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < N) {
+ if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+ return;
+ }
+ while (i < N && reply.readInt() != 0) {
+ final T parcelable = readCreator(creator, reply, loader);
+ verifySameType(listElementClass, parcelable.getClass());
+
+ mList.add(parcelable);
+
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+ (Parcelable.ClassLoaderCreator<?>) creator;
+ return (T) classLoaderCreator.createFromParcel(p, loader);
+ }
+ return (T) creator.createFromParcel(p);
+ }
+
+ private static void verifySameType(final Class<?> expected, final Class<?> actual) {
+ if (!actual.equals(expected)) {
+ throw new IllegalArgumentException("Can't unparcel type "
+ + (actual == null ? null : actual.getName()) + " in list of type "
+ + (expected == null ? null : expected.getName()));
+ }
+ }
+
+ public List<T> getList() {
+ return mList;
+ }
+
+ /**
+ * Set a limit on the maximum number of entries in the array that will be included
+ * inline in the initial parcelling of this object.
+ */
+ public void setInlineCountLimit(int maxCount) {
+ mInlineCountLimit = maxCount;
+ }
+
+ /**
+ * Write this to another Parcel. Note that this discards the internal Parcel
+ * and should not be used anymore. This is so we can pass this to a Binder
+ * where we won't have a chance to call recycle on this.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int N = mList.size();
+ final int callFlags = flags;
+ dest.writeInt(N);
+ if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+ if (N > 0) {
+ final Class<?> listElementClass = mList.get(0).getClass();
+ writeParcelableCreator(mList.get(0), dest);
+ int i = 0;
+ while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
+ dest.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, dest, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+ while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, reply, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+ dest.writeStrongBinder(retriever);
+ }
+ }
+ }
+
+ abstract void writeElement(T parcelable, Parcel reply, int callFlags);
+
+ abstract void writeParcelableCreator(T parcelable, Parcel dest);
+
+ abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
+}
diff --git a/apex/media/framework/java/android/media/MediaParceledListSlice.java b/apex/media/framework/java/android/media/MediaParceledListSlice.java
new file mode 100644
index 000000000000..47ac193231a0
--- /dev/null
+++ b/apex/media/framework/java/android/media/MediaParceledListSlice.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This is a copied version of MediaParceledListSlice in framework with hidden API usages removed,
+ * and also with some lint error fixed.
+ *
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * TODO: Remove this from @SystemApi once all the MediaSession related classes are moved
+ * to apex (or ParceledListSlice moved to apex). This class is temporaily added to system API
+ * for moving classes step by step.
+ *
+ * @param <T> The type of the elements in the list.
+ * @see BaseMediaParceledListSlice
+ * @deprecated This is temporary marked as @SystemApi. Should be removed from the API surface.
+ * @hide
+ */
+@Deprecated
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class MediaParceledListSlice<T extends Parcelable>
+ extends BaseMediaParceledListSlice<T> {
+ public MediaParceledListSlice(@NonNull List<T> list) {
+ super(list);
+ }
+
+ private MediaParceledListSlice(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ }
+
+ @NonNull
+ public static <T extends Parcelable> MediaParceledListSlice<T> emptyList() {
+ return new MediaParceledListSlice<T>(Collections.<T> emptyList());
+ }
+
+ @Override
+ public int describeContents() {
+ int contents = 0;
+ final List<T> list = getList();
+ for (int i=0; i<list.size(); i++) {
+ contents |= list.get(i).describeContents();
+ }
+ return contents;
+ }
+
+ @Override
+ void writeElement(T parcelable, Parcel dest, int callFlags) {
+ parcelable.writeToParcel(dest, callFlags);
+ }
+
+ @Override
+ void writeParcelableCreator(T parcelable, Parcel dest) {
+ dest.writeParcelableCreator((Parcelable) parcelable);
+ }
+
+ @Override
+ Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+ return from.readParcelableCreator(loader);
+ }
+
+ @NonNull
+ @SuppressWarnings("unchecked")
+ public static final Parcelable.ClassLoaderCreator<MediaParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<MediaParceledListSlice>() {
+ public MediaParceledListSlice createFromParcel(Parcel in) {
+ return new MediaParceledListSlice(in, null);
+ }
+
+ @Override
+ public MediaParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new MediaParceledListSlice(in, loader);
+ }
+
+ @Override
+ public MediaParceledListSlice[] newArray(int size) {
+ return new MediaParceledListSlice[size];
+ }
+ };
+}
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index a7d20572ca96..41803cfd6960 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -547,7 +547,7 @@ public final class StatsManager {
@Override
public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
List<StatsEvent> data = new ArrayList<>();
diff --git a/api/Android.bp b/api/Android.bp
index 54ff82c97e17..388bb68e8999 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -1,7 +1,190 @@
+// 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 {
+ default_visibility: ["//visibility:private"],
+}
+
+// *-current.txt files for use by modules in other directories like cts
+filegroup {
+ name: "frameworks-base-api-current.txt",
+ srcs: ["current.txt"],
+ visibility: ["//visibility:public"],
+}
+
+filegroup {
+ name: "frameworks-base-api-system-current.txt",
+ srcs: ["system-current.txt"],
+ visibility: ["//visibility:public"],
+}
+
+filegroup {
+ name: "frameworks-base-api-system-removed.txt",
+ srcs: ["system-removed.txt"],
+ visibility: ["//visibility:public"],
+}
+
genrule {
name: "current-api-xml",
tools: ["metalava"],
srcs: ["current.txt"],
out: ["current.api"],
cmd: "$(location metalava) --no-banner -convert2xmlnostrip $(in) $(out)",
+ visibility: ["//visibility:public"],
+}
+
+genrule {
+ name: "frameworks-base-api-current-merged.txt",
+ srcs: [
+ ":conscrypt.module.public.api{.public.api.txt}",
+ ":framework-appsearch{.public.api.txt}",
+ ":framework-graphics{.public.api.txt}",
+ ":framework-media{.public.api.txt}",
+ ":framework-mediaprovider{.public.api.txt}",
+ ":framework-permission{.public.api.txt}",
+ ":framework-sdkextensions{.public.api.txt}",
+ ":framework-statsd{.public.api.txt}",
+ ":framework-tethering{.public.api.txt}",
+ ":framework-wifi{.public.api.txt}",
+ ":non-updatable-current.txt",
+ ],
+ out: ["current.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+ dist: {
+ targets: ["sdk", "win_sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android.txt",
+ },
+}
+
+genrule {
+ name: "frameworks-base-api-removed-merged.txt",
+ srcs: [
+ ":conscrypt.module.public.api{.public.removed-api.txt}",
+ ":framework-appsearch{.public.removed-api.txt}",
+ ":framework-graphics{.public.removed-api.txt}",
+ ":framework-media{.public.removed-api.txt}",
+ ":framework-mediaprovider{.public.removed-api.txt}",
+ ":framework-permission{.public.removed-api.txt}",
+ ":framework-sdkextensions{.public.removed-api.txt}",
+ ":framework-statsd{.public.removed-api.txt}",
+ ":framework-tethering{.public.removed-api.txt}",
+ ":framework-wifi{.public.removed-api.txt}",
+ ":non-updatable-removed.txt",
+ ],
+ out: ["removed.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+}
+
+genrule {
+ name: "frameworks-base-api-system-current-merged.txt",
+ srcs: [
+ ":framework-appsearch{.system.api.txt}",
+ ":framework-graphics{.system.api.txt}",
+ ":framework-media{.system.api.txt}",
+ ":framework-mediaprovider{.system.api.txt}",
+ ":framework-permission{.system.api.txt}",
+ ":framework-sdkextensions{.system.api.txt}",
+ ":framework-statsd{.system.api.txt}",
+ ":framework-tethering{.system.api.txt}",
+ ":framework-wifi{.system.api.txt}",
+ ":non-updatable-system-current.txt",
+ ],
+ out: ["system-current.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+ dist: {
+ targets: ["sdk", "win_sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android.txt",
+ },
+}
+
+genrule {
+ name: "frameworks-base-api-system-removed-merged.txt",
+ srcs: [
+ ":framework-appsearch{.system.removed-api.txt}",
+ ":framework-graphics{.system.removed-api.txt}",
+ ":framework-media{.system.removed-api.txt}",
+ ":framework-mediaprovider{.system.removed-api.txt}",
+ ":framework-permission{.system.removed-api.txt}",
+ ":framework-sdkextensions{.system.removed-api.txt}",
+ ":framework-statsd{.system.removed-api.txt}",
+ ":framework-tethering{.system.removed-api.txt}",
+ ":framework-wifi{.system.removed-api.txt}",
+ ":non-updatable-system-removed.txt",
+ ],
+ out: ["system-removed.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+}
+
+genrule {
+ name: "frameworks-base-api-module-lib-current-merged.txt",
+ srcs: [
+ ":framework-appsearch{.module-lib.api.txt}",
+ ":framework-graphics{.module-lib.api.txt}",
+ ":framework-media{.module-lib.api.txt}",
+ ":framework-mediaprovider{.module-lib.api.txt}",
+ ":framework-permission{.module-lib.api.txt}",
+ ":framework-sdkextensions{.module-lib.api.txt}",
+ ":framework-statsd{.module-lib.api.txt}",
+ ":framework-tethering{.module-lib.api.txt}",
+ ":framework-wifi{.module-lib.api.txt}",
+ ":non-updatable-module-lib-current.txt",
+ ],
+ out: ["module-lib-current.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+ dist: {
+ targets: ["sdk", "win_sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android.txt",
+ },
+}
+
+genrule {
+ name: "frameworks-base-api-module-lib-removed-merged.txt",
+ srcs: [
+ ":framework-appsearch{.module-lib.removed-api.txt}",
+ ":framework-graphics{.module-lib.removed-api.txt}",
+ ":framework-media{.module-lib.removed-api.txt}",
+ ":framework-mediaprovider{.module-lib.removed-api.txt}",
+ ":framework-permission{.module-lib.removed-api.txt}",
+ ":framework-sdkextensions{.module-lib.removed-api.txt}",
+ ":framework-statsd{.module-lib.removed-api.txt}",
+ ":framework-tethering{.module-lib.removed-api.txt}",
+ ":framework-wifi{.module-lib.removed-api.txt}",
+ ":non-updatable-module-lib-removed.txt",
+ ],
+ out: ["module-lib-removed.txt"],
+ tools: ["metalava"],
+ cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)",
+}
+
+genrule {
+ name: "combined-removed-dex",
+ srcs: [
+ ":frameworks-base-api-removed-merged.txt",
+ ":frameworks-base-api-system-removed-merged.txt",
+ ":android.car-stubs-docs{.removed-api.txt}",
+ ":android.car-system-stubs-docs{.removed-api.txt}",
+ ],
+ tool_files: ["gen_combined_removed_dex.sh"],
+ tools: ["metalava"],
+ out: ["combined-removed-dex.txt"],
+ cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)",
}
diff --git a/api/current.txt b/api/current.txt
index 0f4cffc9427b..ac6970a6bf47 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21,6 +21,7 @@ package android {
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@ package android {
field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -644,10 +646,10 @@ package android {
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
- field public static final int fontProviderAuthority = 16844112; // 0x1010550
- field public static final int fontProviderCerts = 16844125; // 0x101055d
- field public static final int fontProviderPackage = 16844119; // 0x1010557
- field public static final int fontProviderQuery = 16844113; // 0x1010551
+ field @Deprecated public static final int fontProviderAuthority = 16844112; // 0x1010550
+ field @Deprecated public static final int fontProviderCerts = 16844125; // 0x101055d
+ field @Deprecated public static final int fontProviderPackage = 16844119; // 0x1010557
+ field @Deprecated public static final int fontProviderQuery = 16844113; // 0x1010551
field public static final int fontStyle = 16844095; // 0x101053f
field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
@@ -2887,6 +2889,7 @@ package android.accessibilityservice {
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
+ field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
@@ -2895,11 +2898,13 @@ package android.accessibilityservice {
field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
+ field public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44; // 0x2c
field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20
field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
+ field public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45; // 0x2d
field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
@@ -4374,6 +4379,7 @@ package android.app {
method @Deprecated public void checkPackage(int, @NonNull String);
method @Deprecated public void finishOp(@NonNull String, int, @NonNull String);
method public void finishOp(@NonNull String, int, @NonNull String, @Nullable String);
+ method public void finishProxyOp(@NonNull String, int, @NonNull String, @Nullable String);
method public boolean isOpActive(@NonNull String, int, @NonNull String);
method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
@@ -4390,6 +4396,8 @@ package android.app {
method public int startOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
method public int startOpNoThrow(@NonNull String, int, @NonNull String, @NonNull String, @Nullable String);
+ method public int startProxyOp(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
+ method public int startProxyOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -6138,6 +6146,7 @@ package android.app {
field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR;
field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000
field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000
+ field public static final int FLAG_MUTABLE = 33554432; // 0x2000000
field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000
field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000
field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000
@@ -6512,6 +6521,7 @@ package android.app {
method public void dropShellPermissionIdentity();
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
method public android.os.ParcelFileDescriptor executeShellCommand(String);
+ method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRw(@NonNull String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -8783,6 +8793,7 @@ package android.bluetooth {
method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
+ method public void onServiceChanged(@NonNull android.bluetooth.BluetoothGatt);
method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
}
@@ -9137,6 +9148,7 @@ package android.bluetooth.le {
method public boolean getIncludeTxPowerLevel();
method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
+ method @Nullable public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
@@ -9146,6 +9158,7 @@ package android.bluetooth.le {
ctor public AdvertiseData.Builder();
method public android.bluetooth.le.AdvertiseData.Builder addManufacturerData(int, byte[]);
method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
+ method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
method public android.bluetooth.le.AdvertiseData build();
method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
@@ -10654,12 +10667,15 @@ package android.content {
field public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
field public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
field public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
+ field public static final String ACTION_PACKAGE_FULLY_LOADED = "android.intent.action.PACKAGE_FULLY_LOADED";
field public static final String ACTION_PACKAGE_FULLY_REMOVED = "android.intent.action.PACKAGE_FULLY_REMOVED";
field @Deprecated public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
field public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+ field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
+ field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
field public static final String ACTION_PASTE = "android.intent.action.PASTE";
field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -10800,6 +10816,7 @@ package android.content {
field public static final String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
field public static final String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
+ field public static final String EXTRA_REMOVED_BY_SYSTEM = "android.intent.extra.REMOVED_BY_SYSTEM";
field public static final String EXTRA_REPLACEMENT_EXTRAS = "android.intent.extra.REPLACEMENT_EXTRAS";
field public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
field public static final String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
@@ -10823,6 +10840,7 @@ package android.content {
field public static final String EXTRA_TIMEZONE = "time-zone";
field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final String EXTRA_UID = "android.intent.extra.UID";
+ field public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
field public static final String EXTRA_USER = "android.intent.extra.USER";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
@@ -11359,6 +11377,7 @@ package android.content.pm {
field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
+ field public static final int CONFIG_FORCE_BOLD_TEXT = 268435456; // 0x10000000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20
field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000
@@ -11449,9 +11468,10 @@ package android.content.pm {
public final class ApkChecksum implements android.os.Parcelable {
method public int describeContents();
- method public int getKind();
- method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public java.security.cert.Certificate getInstallerCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public String getInstallerPackageName();
method @Nullable public String getSplitName();
+ method public int getType();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ApkChecksum> CREATOR;
@@ -11559,18 +11579,19 @@ package android.content.pm {
}
public final class Checksum implements android.os.Parcelable {
+ ctor public Checksum(int, @NonNull byte[]);
method public int describeContents();
- method public int getKind();
+ method public int getType();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.Checksum> CREATOR;
- field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
- field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
- field public static final int WHOLE_MD5 = 2; // 0x2
- field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
- field public static final int WHOLE_SHA1 = 4; // 0x4
- field public static final int WHOLE_SHA256 = 8; // 0x8
- field public static final int WHOLE_SHA512 = 16; // 0x10
+ field public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
+ field public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
+ field @Deprecated public static final int TYPE_WHOLE_MD5 = 2; // 0x2
+ field public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
+ field @Deprecated public static final int TYPE_WHOLE_SHA1 = 4; // 0x4
+ field @Deprecated public static final int TYPE_WHOLE_SHA256 = 8; // 0x8
+ field @Deprecated public static final int TYPE_WHOLE_SHA512 = 16; // 0x10
}
public class ComponentInfo extends android.content.pm.PackageItemInfo {
@@ -11864,7 +11885,7 @@ package android.content.pm {
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
- method public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
+ method @Deprecated public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
method public void addChildSessionId(int);
method public void close();
method public void commit(@NonNull android.content.IntentSender);
@@ -12020,7 +12041,6 @@ package android.content.pm {
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
- method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
@@ -12093,6 +12113,7 @@ package android.content.pm {
method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
method public abstract void removePermission(@NonNull String);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
+ method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
@@ -12230,6 +12251,7 @@ package android.content.pm {
field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+ field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12278,6 +12300,9 @@ package android.content.pm {
field public static final int SYNCHRONOUS = 2; // 0x2
field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
+ field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
+ field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
+ field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -12339,6 +12364,7 @@ package android.content.pm {
field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+ field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
field public static final int PROTECTION_DANGEROUS = 1; // 0x1
field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
@@ -12680,6 +12706,9 @@ package android.content.res {
field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
+ field public static final int FORCE_BOLD_TEXT_NO = 1; // 0x1
+ field public static final int FORCE_BOLD_TEXT_UNDEFINED = 0; // 0x0
+ field public static final int FORCE_BOLD_TEXT_YES = 2; // 0x2
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2
@@ -12746,6 +12775,7 @@ package android.content.res {
field public int colorMode;
field public int densityDpi;
field public float fontScale;
+ field public int forceBoldText;
field public int hardKeyboardHidden;
field public int keyboard;
field public int keyboardHidden;
@@ -14285,6 +14315,7 @@ package android.graphics {
public final class BlurShader extends android.graphics.Shader {
ctor public BlurShader(float, float, @Nullable android.graphics.Shader);
+ ctor public BlurShader(float, float, @Nullable android.graphics.Shader, @NonNull android.graphics.Shader.TileMode);
}
public class Camera {
@@ -14342,6 +14373,7 @@ package android.graphics {
method public void drawColor(@ColorLong long, @NonNull android.graphics.BlendMode);
method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint);
method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.Paint);
+ method public void drawGlyphs(@NonNull int[], @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.fonts.Font, @NonNull android.graphics.Paint);
method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint);
@@ -15188,6 +15220,20 @@ package android.graphics {
ctor public PaintFlagsDrawFilter(int, int);
}
+ public final class ParcelableColorSpace extends android.graphics.ColorSpace implements android.os.Parcelable {
+ ctor public ParcelableColorSpace(@NonNull android.graphics.ColorSpace);
+ method public int describeContents();
+ method @NonNull public float[] fromXyz(@NonNull float[]);
+ method @NonNull public android.graphics.ColorSpace getColorSpace();
+ method public float getMaxValue(int);
+ method public float getMinValue(int);
+ method public static boolean isParcelable(@NonNull android.graphics.ColorSpace);
+ method public boolean isWideGamut();
+ method @NonNull public float[] toXyz(@NonNull float[]);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR;
+ }
+
public class Path {
ctor public Path();
ctor public Path(@Nullable android.graphics.Path);
@@ -15623,6 +15669,7 @@ package android.graphics {
public enum Shader.TileMode {
enum_constant public static final android.graphics.Shader.TileMode CLAMP;
+ enum_constant public static final android.graphics.Shader.TileMode DECAL;
enum_constant public static final android.graphics.Shader.TileMode MIRROR;
enum_constant public static final android.graphics.Shader.TileMode REPEAT;
}
@@ -16366,7 +16413,9 @@ package android.graphics.fonts {
method @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
method @NonNull public java.nio.ByteBuffer getBuffer();
method @Nullable public java.io.File getFile();
+ method public float getGlyphBounds(@IntRange(from=0) int, @NonNull android.graphics.Paint, @Nullable android.graphics.RectF);
method @NonNull public android.os.LocaleList getLocaleList();
+ method public void getMetrics(@NonNull android.graphics.Paint, @Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.graphics.fonts.FontStyle getStyle();
method @IntRange(from=0) public int getTtcIndex();
}
@@ -16378,6 +16427,7 @@ package android.graphics.fonts {
ctor public Font.Builder(@NonNull android.os.ParcelFileDescriptor, @IntRange(from=0) long, @IntRange(from=0xffffffff) long);
ctor public Font.Builder(@NonNull android.content.res.AssetManager, @NonNull String);
ctor public Font.Builder(@NonNull android.content.res.Resources, int);
+ ctor public Font.Builder(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.Font build() throws java.io.IOException;
method @NonNull public android.graphics.fonts.Font.Builder setFontVariationSettings(@Nullable String);
method @NonNull public android.graphics.fonts.Font.Builder setFontVariationSettings(@Nullable android.graphics.fonts.FontVariationAxis[]);
@@ -16482,6 +16532,23 @@ package android.graphics.pdf {
package android.graphics.text {
+ public class GlyphStyle {
+ ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int);
+ ctor public GlyphStyle(@NonNull android.graphics.Paint);
+ method public void applyToPaint(@NonNull android.graphics.Paint);
+ method @ColorInt public int getColor();
+ method public int getFlags();
+ method @FloatRange(from=0) public float getFontSize();
+ method @FloatRange(from=0) public float getScaleX();
+ method @FloatRange(from=0) public float getSkewX();
+ method public void setColor(@ColorInt int);
+ method public void setFlags(int);
+ method public void setFontSize(@FloatRange(from=0) float);
+ method public void setFromPaint(@NonNull android.graphics.Paint);
+ method public void setScaleX(@FloatRange(from=0) float);
+ method public void setSkewX(@FloatRange(from=0) float);
+ }
+
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -16542,6 +16609,25 @@ package android.graphics.text {
method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
}
+ public final class PositionedGlyphs {
+ method public float getAscent();
+ method public float getDescent();
+ method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
+ method public float getOriginX();
+ method public float getOriginY();
+ method public float getPositionX(@IntRange(from=0) int);
+ method public float getPositionY(@IntRange(from=0) int);
+ method @NonNull public android.graphics.text.GlyphStyle getStyle();
+ method public float getTotalAdvance();
+ method @IntRange(from=0) public int glyphCount();
+ }
+
+ public class TextShaper {
+ method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
+ method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
+ }
+
}
package android.hardware {
@@ -24906,6 +24992,10 @@ package android.media {
method public double getAttributeDouble(@NonNull String, double);
method public int getAttributeInt(@NonNull String, int);
method @Nullable public long[] getAttributeRange(@NonNull String);
+ method public long getDateTime();
+ method public long getDateTimeDigitized();
+ method public long getDateTimeOriginal();
+ method public long getGpsDateTime();
method public boolean getLatLong(float[]);
method public byte[] getThumbnail();
method public android.graphics.Bitmap getThumbnailBitmap();
@@ -25376,6 +25466,7 @@ package android.media {
public static final class MediaCodec.CryptoInfo {
ctor public MediaCodec.CryptoInfo();
+ method @NonNull public android.media.MediaCodec.CryptoInfo.Pattern getPattern();
method public void set(int, @NonNull int[], @NonNull int[], @NonNull byte[], @NonNull byte[], int);
method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
field public byte[] iv;
@@ -26276,6 +26367,7 @@ package android.media {
field public static final String KEY_ROTATION = "rotation-degrees";
field public static final String KEY_SAMPLE_RATE = "sample-rate";
field public static final String KEY_SLICE_HEIGHT = "slice-height";
+ field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
field public static final String KEY_STRIDE = "stride";
field public static final String KEY_TEMPORAL_LAYERING = "ts-schema";
field public static final String KEY_TILE_HEIGHT = "tile-height";
@@ -26327,6 +26419,7 @@ package android.media {
method public boolean containsKey(String);
method public int describeContents();
method public android.graphics.Bitmap getBitmap(String);
+ method @IntRange(from=1) public int getBitmapDimensionLimit();
method @NonNull public android.media.MediaDescription getDescription();
method public long getLong(String);
method public android.media.Rating getRating(String);
@@ -26376,6 +26469,7 @@ package android.media {
method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating);
method public android.media.MediaMetadata.Builder putString(String, String);
method public android.media.MediaMetadata.Builder putText(String, CharSequence);
+ method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(@IntRange(from=1) int);
}
@Deprecated public abstract class MediaMetadataEditor {
@@ -31536,6 +31630,7 @@ package android.net.wifi {
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isMultiStaConcurrencySupported();
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
@@ -31695,6 +31790,7 @@ package android.net.wifi {
method @IntRange(from=0) public int getPriority();
method public int getPriorityGroup();
method @Nullable public String getSsid();
+ method public int getSubscriptionId();
method public boolean isAppInteractionRequired();
method public boolean isCredentialSharedWithUser();
method public boolean isEnhancedOpen();
@@ -31723,6 +31819,7 @@ package android.net.wifi {
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
@@ -32002,9 +32099,15 @@ package android.net.wifi.hotspot2.pps {
method public int describeContents();
method public String getFqdn();
method public String getFriendlyName();
+ method @Nullable public long[] getMatchAllOis();
+ method @Nullable public long[] getMatchAnyOis();
+ method @Nullable public String[] getOtherHomePartners();
method public long[] getRoamingConsortiumOis();
method public void setFqdn(String);
method public void setFriendlyName(String);
+ method public void setMatchAllOis(@Nullable long[]);
+ method public void setMatchAnyOis(@Nullable long[]);
+ method public void setOtherHomePartners(@Nullable String[]);
method public void setRoamingConsortiumOis(long[]);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
@@ -36593,7 +36696,7 @@ package android.os {
method public int dataCapacity();
method public int dataPosition();
method public int dataSize();
- method public void enforceInterface(String);
+ method public void enforceInterface(@NonNull String);
method public boolean hasFileDescriptors();
method public byte[] marshall();
method @NonNull public static android.os.Parcel obtain();
@@ -36664,7 +36767,7 @@ package android.os {
method public void writeFloatArray(@Nullable float[]);
method public void writeInt(int);
method public void writeIntArray(@Nullable int[]);
- method public void writeInterfaceToken(String);
+ method public void writeInterfaceToken(@NonNull String);
method public void writeList(@Nullable java.util.List);
method public void writeLong(long);
method public void writeLongArray(@Nullable long[]);
@@ -40108,62 +40211,62 @@ package android.provider {
method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
}
- public final class FontRequest {
- ctor public FontRequest(@NonNull String, @NonNull String, @NonNull String);
- ctor public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
- method public java.util.List<java.util.List<byte[]>> getCertificates();
- method public String getProviderAuthority();
- method public String getProviderPackage();
- method public String getQuery();
- }
-
- public class FontsContract {
- method public static android.graphics.Typeface buildTypeface(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontInfo[]);
- method @NonNull public static android.provider.FontsContract.FontFamilyResult fetchFonts(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
- method public static void requestFonts(@NonNull android.content.Context, @NonNull android.provider.FontRequest, @NonNull android.os.Handler, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontRequestCallback);
- }
-
- public static final class FontsContract.Columns implements android.provider.BaseColumns {
- field public static final String FILE_ID = "file_id";
- field public static final String ITALIC = "font_italic";
- field public static final String RESULT_CODE = "result_code";
- field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
- field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
- field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
- field public static final int RESULT_CODE_OK = 0; // 0x0
- field public static final String TTC_INDEX = "font_ttc_index";
- field public static final String VARIATION_SETTINGS = "font_variation_settings";
- field public static final String WEIGHT = "font_weight";
- }
-
- public static class FontsContract.FontFamilyResult {
- method @NonNull public android.provider.FontsContract.FontInfo[] getFonts();
- method public int getStatusCode();
- field public static final int STATUS_OK = 0; // 0x0
- field public static final int STATUS_REJECTED = 3; // 0x3
- field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
- field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
- }
-
- public static class FontsContract.FontInfo {
- method @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
- method public int getResultCode();
- method @IntRange(from=0) public int getTtcIndex();
- method @NonNull public android.net.Uri getUri();
- method @IntRange(from=1, to=1000) public int getWeight();
- method public boolean isItalic();
- }
-
- public static class FontsContract.FontRequestCallback {
- ctor public FontsContract.FontRequestCallback();
- method public void onTypefaceRequestFailed(int);
- method public void onTypefaceRetrieved(android.graphics.Typeface);
- field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
- field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
- field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
- field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
- field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
- field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+ @Deprecated public final class FontRequest {
+ ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String);
+ ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
+ method @Deprecated public java.util.List<java.util.List<byte[]>> getCertificates();
+ method @Deprecated public String getProviderAuthority();
+ method @Deprecated public String getProviderPackage();
+ method @Deprecated public String getQuery();
+ }
+
+ @Deprecated public class FontsContract {
+ method @Deprecated public static android.graphics.Typeface buildTypeface(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontInfo[]);
+ method @Deprecated @NonNull public static android.provider.FontsContract.FontFamilyResult fetchFonts(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public static void requestFonts(@NonNull android.content.Context, @NonNull android.provider.FontRequest, @NonNull android.os.Handler, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontRequestCallback);
+ }
+
+ @Deprecated public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ field @Deprecated public static final String FILE_ID = "file_id";
+ field @Deprecated public static final String ITALIC = "font_italic";
+ field @Deprecated public static final String RESULT_CODE = "result_code";
+ field @Deprecated public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
+ field @Deprecated public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
+ field @Deprecated public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
+ field @Deprecated public static final int RESULT_CODE_OK = 0; // 0x0
+ field @Deprecated public static final String TTC_INDEX = "font_ttc_index";
+ field @Deprecated public static final String VARIATION_SETTINGS = "font_variation_settings";
+ field @Deprecated public static final String WEIGHT = "font_weight";
+ }
+
+ @Deprecated public static class FontsContract.FontFamilyResult {
+ method @Deprecated @NonNull public android.provider.FontsContract.FontInfo[] getFonts();
+ method @Deprecated public int getStatusCode();
+ field @Deprecated public static final int STATUS_OK = 0; // 0x0
+ field @Deprecated public static final int STATUS_REJECTED = 3; // 0x3
+ field @Deprecated public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+ field @Deprecated public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+ }
+
+ @Deprecated public static class FontsContract.FontInfo {
+ method @Deprecated @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
+ method @Deprecated public int getResultCode();
+ method @Deprecated @IntRange(from=0) public int getTtcIndex();
+ method @Deprecated @NonNull public android.net.Uri getUri();
+ method @Deprecated @IntRange(from=1, to=1000) public int getWeight();
+ method @Deprecated public boolean isItalic();
+ }
+
+ @Deprecated public static class FontsContract.FontRequestCallback {
+ ctor @Deprecated public FontsContract.FontRequestCallback();
+ method @Deprecated public void onTypefaceRequestFailed(int);
+ method @Deprecated public void onTypefaceRetrieved(android.graphics.Typeface);
+ field @Deprecated public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+ field @Deprecated public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+ field @Deprecated public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+ field @Deprecated public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+ field @Deprecated public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+ field @Deprecated public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
}
@Deprecated public final class LiveFolders implements android.provider.BaseColumns {
@@ -40295,7 +40398,7 @@ package android.provider {
field public static final android.net.Uri INTERNAL_CONTENT_URI;
}
- public static final class MediaStore.Audio.Artists.Albums implements android.provider.MediaStore.Audio.AlbumColumns {
+ public static final class MediaStore.Audio.Artists.Albums implements android.provider.BaseColumns android.provider.MediaStore.Audio.AlbumColumns {
ctor public MediaStore.Audio.Artists.Albums();
method public static android.net.Uri getContentUri(String, long);
}
@@ -44008,11 +44111,13 @@ package android.service.notification {
method public long getLastAudiblyAlertedMillis();
method public String getOverrideGroupKey();
method public int getRank();
+ method @Nullable public android.content.pm.ShortcutInfo getShortcutInfo();
method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions();
method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
+ method public boolean isConversation();
method public boolean isSuspended();
method public boolean matchesInterruptionFilter();
field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
@@ -44287,54 +44392,8 @@ package android.service.textservice {
package android.service.voice {
- public class AlwaysOnHotwordDetector {
- method public android.content.Intent createEnrollIntent();
- method public android.content.Intent createReEnrollIntent();
- method public android.content.Intent createUnEnrollIntent();
- method public int getParameter(int);
- method public int getSupportedAudioCapabilities();
- method public int getSupportedRecognitionModes();
- method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
- method public int setParameter(int, int);
- method public boolean startRecognition(int);
- method public boolean stopRecognition();
- field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
- field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
- field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
- field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
- field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
- field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
- field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
- field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
- field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
- field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
- field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
- field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
- field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
- }
-
- public abstract static class AlwaysOnHotwordDetector.Callback {
- ctor public AlwaysOnHotwordDetector.Callback();
- method public abstract void onAvailabilityChanged(int);
- method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
- method public abstract void onError();
- method public abstract void onRecognitionPaused();
- method public abstract void onRecognitionResumed();
- }
-
- public static class AlwaysOnHotwordDetector.EventPayload {
- method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
- method @Nullable public byte[] getTriggerAudio();
- }
-
- public static final class AlwaysOnHotwordDetector.ModelParamRange {
- method public int getEnd();
- method public int getStart();
- }
-
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
- method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method public int getDisabledShowContext();
method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
method public android.os.IBinder onBind(android.content.Intent);
@@ -46105,6 +46164,7 @@ package android.telecom {
field public static final int MISSED = 5; // 0x5
field public static final int OTHER = 9; // 0x9
field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
+ field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
field public static final int REJECTED = 6; // 0x6
@@ -46804,6 +46864,7 @@ package android.telephony {
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
+ field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
@@ -46998,6 +47059,10 @@ package android.telephony {
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
field public static final int SERVICE_CLASS_NONE = 0; // 0x0
field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
+ field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
+ field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0
+ field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3
+ field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1
}
public static final class CarrierConfigManager.Apn {
@@ -47172,9 +47237,9 @@ package android.telephony {
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoWcdma> CREATOR;
}
- public abstract class CellLocation {
- ctor public CellLocation();
- method public static android.telephony.CellLocation getEmpty();
+ @Deprecated public abstract class CellLocation {
+ ctor @Deprecated public CellLocation();
+ method @Deprecated public static android.telephony.CellLocation getEmpty();
method @Deprecated public static void requestLocationUpdate();
}
@@ -48045,6 +48110,7 @@ package android.telephony {
method @NonNull public android.os.Bundle getCarrierConfigValues();
method @Deprecated public static android.telephony.SmsManager getDefault();
method public static int getDefaultSmsSubscriptionId();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getSmsCapacityOnIcc();
method @Deprecated public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback);
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSmscAddress();
@@ -48387,6 +48453,7 @@ package android.telephony {
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String[] getForbiddenPlmns();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1();
method public String getIccAuthentication(int, int, String);
@@ -48411,7 +48478,7 @@ package android.telephony {
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
- method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
method @Nullable public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
method @Nullable public CharSequence getSimCarrierIdName();
@@ -48434,7 +48501,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
- method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
+ method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
method public boolean hasCarrierPrivileges();
method public boolean hasIccCard();
method @Deprecated public boolean iccCloseLogicalChannel(int);
@@ -48692,19 +48759,19 @@ package android.telephony {
package android.telephony.cdma {
- public class CdmaCellLocation extends android.telephony.CellLocation {
- ctor public CdmaCellLocation();
- ctor public CdmaCellLocation(android.os.Bundle);
- method public static double convertQuartSecToDecDegrees(int);
- method public void fillInNotifierBundle(android.os.Bundle);
- method public int getBaseStationId();
- method public int getBaseStationLatitude();
- method public int getBaseStationLongitude();
- method public int getNetworkId();
- method public int getSystemId();
- method public void setCellLocationData(int, int, int);
- method public void setCellLocationData(int, int, int, int, int);
- method public void setStateInvalid();
+ @Deprecated public class CdmaCellLocation extends android.telephony.CellLocation {
+ ctor @Deprecated public CdmaCellLocation();
+ ctor @Deprecated public CdmaCellLocation(android.os.Bundle);
+ method @Deprecated public static double convertQuartSecToDecDegrees(int);
+ method @Deprecated public void fillInNotifierBundle(android.os.Bundle);
+ method @Deprecated public int getBaseStationId();
+ method @Deprecated public int getBaseStationLatitude();
+ method @Deprecated public int getBaseStationLongitude();
+ method @Deprecated public int getNetworkId();
+ method @Deprecated public int getSystemId();
+ method @Deprecated public void setCellLocationData(int, int, int);
+ method @Deprecated public void setCellLocationData(int, int, int, int, int);
+ method @Deprecated public void setStateInvalid();
}
}
@@ -48904,15 +48971,15 @@ package android.telephony.euicc {
package android.telephony.gsm {
- public class GsmCellLocation extends android.telephony.CellLocation {
- ctor public GsmCellLocation();
- ctor public GsmCellLocation(android.os.Bundle);
- method public void fillInNotifierBundle(android.os.Bundle);
- method public int getCid();
- method public int getLac();
- method public int getPsc();
- method public void setLacAndCid(int, int);
- method public void setStateInvalid();
+ @Deprecated public class GsmCellLocation extends android.telephony.CellLocation {
+ ctor @Deprecated public GsmCellLocation();
+ ctor @Deprecated public GsmCellLocation(android.os.Bundle);
+ method @Deprecated public void fillInNotifierBundle(android.os.Bundle);
+ method @Deprecated public int getCid();
+ method @Deprecated public int getLac();
+ method @Deprecated public int getPsc();
+ method @Deprecated public void setLacAndCid(int, int);
+ method @Deprecated public void setStateInvalid();
}
@Deprecated public final class SmsManager {
@@ -49999,6 +50066,10 @@ package android.text {
method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
}
+ public class StyledTextShaper {
+ method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint);
+ }
+
public interface TextDirectionHeuristic {
method public boolean isRtl(char[], int, int);
method public boolean isRtl(CharSequence, int, int);
@@ -50826,6 +50897,7 @@ package android.text.style {
field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionSpan> CREATOR;
field public static final int FLAG_AUTO_CORRECTION = 4; // 0x4
field public static final int FLAG_EASY_CORRECT = 1; // 0x1
+ field public static final int FLAG_GRAMMAR_ERROR = 8; // 0x8
field public static final int FLAG_MISSPELLED = 2; // 0x2
field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5
field @Deprecated public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
@@ -53651,6 +53723,33 @@ package android.view {
field public int toolType;
}
+ public interface OnReceiveContentCallback<T extends android.view.View> {
+ method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T);
+ method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
+ }
+
+ public static final class OnReceiveContentCallback.Payload {
+ method @NonNull public android.content.ClipData getClip();
+ method @Nullable public android.os.Bundle getExtras();
+ method public int getFlags();
+ method @Nullable public android.net.Uri getLinkUri();
+ method public int getSource();
+ field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+ field public static final int SOURCE_AUTOFILL = 3; // 0x3
+ field public static final int SOURCE_CLIPBOARD = 0; // 0x0
+ field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2
+ field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
+ field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4
+ }
+
+ public static final class OnReceiveContentCallback.Payload.Builder {
+ ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload build();
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri);
+ }
+
public abstract class OrientationEventListener {
ctor public OrientationEventListener(android.content.Context);
ctor public OrientationEventListener(android.content.Context, int);
@@ -54202,6 +54301,7 @@ package android.view {
method @IdRes public int getNextFocusRightId();
method @IdRes public int getNextFocusUpId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
+ method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback();
method @ColorInt public int getOutlineAmbientShadowColor();
method public android.view.ViewOutlineProvider getOutlineProvider();
method @ColorInt public int getOutlineSpotShadowColor();
@@ -54553,6 +54653,7 @@ package android.view {
method public void setOnHoverListener(android.view.View.OnHoverListener);
method public void setOnKeyListener(android.view.View.OnKeyListener);
method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
+ method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>);
method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -58171,6 +58272,7 @@ package android.view.textservice {
field @NonNull public static final android.os.Parcelable.Creator<android.view.textservice.SuggestionsInfo> CREATOR;
field public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 4; // 0x4
field public static final int RESULT_ATTR_IN_THE_DICTIONARY = 1; // 0x1
+ field public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 8; // 0x8
field public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 2; // 0x2
}
@@ -60815,17 +60917,6 @@ package android.widget {
method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup);
}
- public interface RichContentReceiver<T extends android.view.View> {
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes();
- method public boolean onReceive(@NonNull T, @NonNull android.content.ClipData, int, int);
- field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
- field public static final int SOURCE_AUTOFILL = 3; // 0x3
- field public static final int SOURCE_CLIPBOARD = 0; // 0x0
- field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2
- field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
- field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4
- }
-
public class ScrollView extends android.widget.FrameLayout {
ctor public ScrollView(android.content.Context);
ctor public ScrollView(android.content.Context, android.util.AttributeSet);
@@ -61380,10 +61471,10 @@ package android.widget {
method public int getMinWidth();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
+ method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback();
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public String getPrivateImeOptions();
- method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver();
method public int getSelectionEnd();
method public int getSelectionStart();
method @ColorInt public int getShadowColor();
@@ -61511,7 +61602,6 @@ package android.widget {
method public void setPaintFlags(int);
method public void setPrivateImeOptions(String);
method public void setRawInputType(int);
- method public void setRichContentReceiver(@NonNull android.widget.RichContentReceiver<android.widget.TextView>);
method public void setScroller(android.widget.Scroller);
method public void setSelectAllOnFocus(boolean);
method public void setShadowLayer(float, float, float, int);
@@ -61552,7 +61642,6 @@ package android.widget {
method public void setWidth(int);
field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
- field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER;
}
public enum TextView.BufferType {
@@ -61569,6 +61658,12 @@ package android.widget {
field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR;
}
+ public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
+ ctor public TextViewOnReceiveContentCallback();
+ method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView);
+ method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
+ }
+
public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter {
method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme();
method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme);
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
new file mode 100755
index 000000000000..9225fe8dfe85
--- /dev/null
+++ b/api/gen_combined_removed_dex.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+metalava_path="$1"
+tmp_dir="$2"
+shift 2
+
+# Convert each removed.txt to the "dex format" equivalent, and print all output.
+for f in "$@"; do
+ "$metalava_path" --no-banner "$f" --dex-api "${tmp_dir}/tmp"
+ cat "${tmp_dir}/tmp"
+done
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 50d68db56a28..b8b66879dbae 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -1,17 +1,35 @@
// Signature format: 2.0
package android.app {
+ public class ActivityManager {
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
+ }
+
public class AppOpsManager {
field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
}
+ public abstract class HomeVisibilityListener {
+ ctor public HomeVisibilityListener();
+ method public abstract void onHomeVisibilityChanged(boolean);
+ }
+
public class NotificationManager {
method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
}
public class StatusBarManager {
- method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
+ }
+
+}
+
+package android.app.role {
+
+ public final class RoleManager {
+ method @Nullable public String getSmsRoleHolder(int);
}
}
@@ -45,8 +63,18 @@ package android.media {
field public static final int FLAG_FROM_KEY = 4096; // 0x1000
}
- public static final class MediaMetadata.Builder {
- ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int);
+ public class MediaMetadataRetriever implements java.lang.AutoCloseable {
+ field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
+ }
+
+ @Deprecated public final class MediaParceledListSlice<T extends android.os.Parcelable> implements android.os.Parcelable {
+ ctor @Deprecated public MediaParceledListSlice(@NonNull java.util.List<T>);
+ method @Deprecated public int describeContents();
+ method @Deprecated @NonNull public static <T extends android.os.Parcelable> android.media.MediaParceledListSlice<T> emptyList();
+ method @Deprecated public java.util.List<T> getList();
+ method @Deprecated public void setInlineCountLimit(int);
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.ClassLoaderCreator<android.media.MediaParceledListSlice> CREATOR;
}
}
@@ -66,10 +94,11 @@ package android.media.session {
}
public final class MediaSessionManager {
+ method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, int, @Nullable android.os.Handler);
method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
- method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+ method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
- method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+ method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
field public static final int RESULT_MEDIA_KEY_HANDLED = 1; // 0x1
field public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; // 0x0
}
@@ -153,6 +182,7 @@ package android.os {
package android.provider {
public final class DeviceConfig {
+ field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 1ec2cf31d7a4..63bae9da7b3b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -38,6 +38,7 @@ package android {
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
field public static final String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
+ field public static final String BIND_MUSIC_RECOGNITION_SERVICE = "android.permission.BIND_MUSIC_RECOGNITION_SERVICE";
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
@@ -95,6 +96,7 @@ package android {
field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM";
field public static final String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS";
+ field public static final String INSTALL_LOCATION_TIME_ZONE_PROVIDER = "android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER";
field public static final String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
field public static final String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
field public static final String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
@@ -119,12 +121,15 @@ package android {
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
+ field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
field public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
+ field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION";
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
@@ -305,6 +310,7 @@ package android {
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemGallery = 17039399; // 0x1040027
+ field public static final int config_systemVideoCall = 17039401; // 0x1040029
}
public static final class R.style {
@@ -669,8 +675,10 @@ package android.app {
public class NotificationManager {
method @NonNull public java.util.List<java.lang.String> getAllowedAssistantAdjustments();
method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public java.util.List<android.content.ComponentName> getEnabledNotificationListeners();
method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean);
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL = "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_OPEN_NOTIFICATION_HANDLER_PANEL = "android.app.action.OPEN_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL = "android.app.action.TOGGLE_NOTIFICATION_HANDLER_PANEL";
@@ -1389,6 +1397,57 @@ package android.app.role {
}
+package android.app.time {
+
+ public final class TimeManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void removeTimeZoneDetectorListener(@NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeZoneConfiguration(@NonNull android.app.time.TimeZoneConfiguration);
+ }
+
+ @java.lang.FunctionalInterface public static interface TimeManager.TimeZoneDetectorListener {
+ method public void onChange();
+ }
+
+ public final class TimeZoneCapabilities implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConfigureAutoDetectionEnabledCapability();
+ method public int getConfigureGeoDetectionEnabledCapability();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14
+ field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e
+ field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa
+ field public static final int CAPABILITY_POSSESSED = 40; // 0x28
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR;
+ }
+
+ public final class TimeZoneCapabilitiesAndConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.app.time.TimeZoneCapabilities getCapabilities();
+ method @NonNull public android.app.time.TimeZoneConfiguration getConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilitiesAndConfig> CREATOR;
+ }
+
+ public final class TimeZoneConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isAutoDetectionEnabled();
+ method public boolean isGeoDetectionEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneConfiguration> CREATOR;
+ }
+
+ public static final class TimeZoneConfiguration.Builder {
+ ctor public TimeZoneConfiguration.Builder();
+ ctor public TimeZoneConfiguration.Builder(@NonNull android.app.time.TimeZoneConfiguration);
+ method @NonNull public android.app.time.TimeZoneConfiguration build();
+ method @NonNull public android.app.time.TimeZoneConfiguration.Builder setAutoDetectionEnabled(boolean);
+ method @NonNull public android.app.time.TimeZoneConfiguration.Builder setGeoDetectionEnabled(boolean);
+ }
+
+}
+
package android.app.usage {
public final class CacheQuotaHint implements android.os.Parcelable {
@@ -2157,6 +2216,7 @@ package android.content.pm {
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -4116,7 +4176,7 @@ package android.location {
method @Deprecated @NonNull public String getProvider();
method public int getQuality();
method @Deprecated public float getSmallestDisplacement();
- method @Nullable public android.os.WorkSource getWorkSource();
+ method @NonNull public android.os.WorkSource getWorkSource();
method public boolean isHiddenFromAppOps();
method public boolean isLocationSettingsIgnored();
method public boolean isLowPower();
@@ -4399,6 +4459,8 @@ package android.media {
}
public static final class MediaTranscodeManager.TranscodingRequest {
+ method public int getClientPid();
+ method public int getClientUid();
method @NonNull public android.net.Uri getDestinationUri();
method public int getPriority();
method @NonNull public android.net.Uri getSourceUri();
@@ -4409,6 +4471,8 @@ package android.media {
public static final class MediaTranscodeManager.TranscodingRequest.Builder {
ctor public MediaTranscodeManager.TranscodingRequest.Builder();
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build();
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int);
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri);
@@ -4558,6 +4622,58 @@ package android.media.audiopolicy {
}
+package android.media.musicrecognition {
+
+ public class MusicRecognitionManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_MUSIC_RECOGNITION) public void beginStreamingSearch(@NonNull android.media.musicrecognition.RecognitionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.musicrecognition.MusicRecognitionManager.RecognitionCallback);
+ field public static final int RECOGNITION_FAILED_AUDIO_UNAVAILABLE = 7; // 0x7
+ field public static final int RECOGNITION_FAILED_NOT_FOUND = 1; // 0x1
+ field public static final int RECOGNITION_FAILED_NO_CONNECTIVITY = 2; // 0x2
+ field public static final int RECOGNITION_FAILED_SERVICE_KILLED = 5; // 0x5
+ field public static final int RECOGNITION_FAILED_SERVICE_UNAVAILABLE = 3; // 0x3
+ field public static final int RECOGNITION_FAILED_TIMEOUT = 6; // 0x6
+ field public static final int RECOGNITION_FAILED_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static interface MusicRecognitionManager.RecognitionCallback {
+ method public void onAudioStreamClosed();
+ method public void onRecognitionFailed(@NonNull android.media.musicrecognition.RecognitionRequest, int);
+ method public void onRecognitionSucceeded(@NonNull android.media.musicrecognition.RecognitionRequest, @NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public abstract class MusicRecognitionService extends android.app.Service {
+ ctor public MusicRecognitionService();
+ method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
+ }
+
+ public static interface MusicRecognitionService.Callback {
+ method public void onRecognitionFailed(int);
+ method public void onRecognitionSucceeded(@NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public final class RecognitionRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.media.AudioAttributes getAudioAttributes();
+ method @NonNull public android.media.AudioFormat getAudioFormat();
+ method public int getCaptureSession();
+ method public int getIgnoreBeginningFrames();
+ method public int getMaxAudioLengthSeconds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.musicrecognition.RecognitionRequest> CREATOR;
+ }
+
+ public static final class RecognitionRequest.Builder {
+ ctor public RecognitionRequest.Builder();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest build();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setCaptureSession(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setIgnoreBeginningFrames(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setMaxAudioLengthSeconds(int);
+ }
+
+}
+
package android.media.session {
public final class MediaSessionManager {
@@ -4923,6 +5039,7 @@ package android.media.tv.tuner {
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+ method public int linkFrontendToCiCam(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -4936,9 +5053,13 @@ package android.media.tv.tuner {
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
+ method public int unlinkFrontendToCiCam(int);
method public void updateResourcePriority(int, int);
field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+ field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+ field public static final int INVALID_LTS_ID = -1; // 0xffffffff
+ field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
field public static final int INVALID_STREAM_ID = 65535; // 0xffff
field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
field public static final int INVALID_TS_PID = 65535; // 0xffff
@@ -4958,6 +5079,13 @@ package android.media.tv.tuner {
method public void onResourceLost(@NonNull android.media.tv.tuner.Tuner);
}
+ public final class TunerVersionChecker {
+ method public static int getTunerVersion();
+ field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+ field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+ field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+ }
+
}
package android.media.tv.tuner.dvr {
@@ -5091,6 +5219,7 @@ package android.media.tv.tuner.filter {
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
method public int flush();
method public int getId();
+ method public long getId64Bit();
method public int read(@NonNull byte[], long, long);
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int start();
@@ -5142,16 +5271,19 @@ package android.media.tv.tuner.filter {
method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
method public int getDstPort();
+ method public int getIpFilterContextId();
method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
method public int getSrcPort();
method public int getType();
method public boolean isPassthrough();
+ field public static final int INVALID_IP_FILTER_CONTEXT_ID = -1; // 0xffffffff
}
public static final class IpFilterConfiguration.Builder {
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration build();
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstIpAddress(@NonNull byte[]);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstPort(int);
+ method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setIpFilterContextId(int);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setPassthrough(boolean);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSettings(@Nullable android.media.tv.tuner.filter.Settings);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcIpAddress(@NonNull byte[]);
@@ -5191,6 +5323,8 @@ package android.media.tv.tuner.filter {
public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
+ method public int getMpuSequenceNumber();
+ method public long getPts();
method public int getScHevcIndexMask();
}
@@ -5353,6 +5487,7 @@ package android.media.tv.tuner.filter {
public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
method public int getPacketId();
+ method public long getPts();
method public int getScIndexMask();
method public int getTsIndexMask();
}
@@ -7248,6 +7383,7 @@ package android.net.wifi {
field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
+ field @Deprecated public static final int RECENT_FAILURE_MBO_OCE_DISCONNECT = 1001; // 0x3e9
field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0
field @Deprecated public boolean allowAutojoin;
field @Deprecated public int carrierId;
@@ -7265,6 +7401,7 @@ package android.net.wifi {
field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
field @Deprecated public boolean requirePmf;
field @Deprecated public boolean shared;
+ field @Deprecated public int subscriptionId;
field @Deprecated public boolean useExternalScores;
}
@@ -7423,8 +7560,8 @@ package android.net.wifi {
field public static final int EASY_CONNECT_NETWORK_ROLE_AP = 1; // 0x1
field public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0; // 0x0
field public static final String EXTRA_CHANGE_REASON = "changeReason";
- field public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
- field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+ field @Deprecated public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
+ field @Deprecated public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
@@ -7432,7 +7569,7 @@ package android.net.wifi {
field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
- field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+ field @Deprecated public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0
@@ -7560,10 +7697,12 @@ package android.net.wifi {
public final class WifiNetworkSuggestion implements android.os.Parcelable {
method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+ method public boolean isOemPaid();
}
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
}
public class WifiScanner {
@@ -10272,7 +10411,53 @@ package android.service.trust {
package android.service.voice {
+ public class AlwaysOnHotwordDetector {
+ method @Nullable public android.content.Intent createEnrollIntent();
+ method @Nullable public android.content.Intent createReEnrollIntent();
+ method @Nullable public android.content.Intent createUnEnrollIntent();
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int);
+ method public int getSupportedAudioCapabilities();
+ method public int getSupportedRecognitionModes();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
+ field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+ field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
+ field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
+ field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
+ field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
+ field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
+ field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
+ field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
+ field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
+ }
+
+ public abstract static class AlwaysOnHotwordDetector.Callback {
+ ctor public AlwaysOnHotwordDetector.Callback();
+ method public abstract void onAvailabilityChanged(int);
+ method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
+ method public abstract void onError();
+ method public abstract void onRecognitionPaused();
+ method public abstract void onRecognitionResumed();
+ }
+
+ public static class AlwaysOnHotwordDetector.EventPayload {
+ method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
+ method @Nullable public byte[] getTriggerAudio();
+ }
+
+ public static final class AlwaysOnHotwordDetector.ModelParamRange {
+ method public int getEnd();
+ method public int getStart();
+ }
+
public class VoiceInteractionService extends android.app.Service {
+ method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
}
@@ -10390,10 +10575,6 @@ package android.telecom {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
- public final class DisconnectCause implements android.os.Parcelable {
- field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
- }
-
public abstract class InCallService extends android.app.Service {
method @Deprecated public android.telecom.Phone getPhone();
method @Deprecated public void onPhoneCreated(android.telecom.Phone);
@@ -10663,6 +10844,22 @@ package android.telephony {
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
+ public final class CallForwardingInfo implements android.os.Parcelable {
+ ctor public CallForwardingInfo(boolean, int, @Nullable String, int);
+ method public int describeContents();
+ method @Nullable public String getNumber();
+ method public int getReason();
+ method public int getTimeoutSeconds();
+ method public boolean isEnabled();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
+ field public static final int REASON_ALL = 4; // 0x4
+ field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
+ field public static final int REASON_BUSY = 1; // 0x1
+ field public static final int REASON_NOT_REACHABLE = 3; // 0x3
+ field public static final int REASON_NO_REPLY = 2; // 0x2
+ field public static final int REASON_UNCONDITIONAL = 0; // 0x0
+ }
+
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
@@ -10852,6 +11049,25 @@ package android.telephony {
field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
}
+ public final class ModemActivityInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+ method public long getIdleTimeMillis();
+ method public static int getNumTxPowerLevels();
+ method public long getReceiveTimeMillis();
+ method public long getSleepTimeMillis();
+ method public long getTimestampMillis();
+ method public long getTransmitDurationMillisAtPowerLevel(int);
+ method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+ field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+ field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+ field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+ field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+ field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+ }
+
public final class NetworkRegistrationInfo implements android.os.Parcelable {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getRegistrationState();
@@ -11231,7 +11447,6 @@ package android.telephony {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
@@ -11324,6 +11539,8 @@ package android.telephony {
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -11377,6 +11594,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11400,11 +11618,14 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
@@ -11437,6 +11658,10 @@ package android.telephony {
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
+ field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
+ field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
+ field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
+ field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -11451,6 +11676,8 @@ package android.telephony {
field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
field public static final int KEY_TYPE_EPDG = 1; // 0x1
field public static final int KEY_TYPE_WLAN = 2; // 0x2
+ field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
+ field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
@@ -11491,6 +11718,15 @@ package android.telephony {
field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0
}
+ public static interface TelephonyManager.CallForwardingInfoCallback {
+ method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
+ method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
@@ -11568,11 +11804,11 @@ package android.telephony.data {
method public int getSuggestedRetryTime();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
- field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
- field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
- field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+ field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+ field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
field public static final int LINK_STATUS_DORMANT = 1; // 0x1
field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
@@ -12881,11 +13117,12 @@ package android.webkit {
}
public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor createInstance();
method @Nullable public String findProxyForUrl(@NonNull String);
method @NonNull public static android.webkit.PacProcessor getInstance();
- method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network);
method @Nullable public default android.net.Network getNetwork();
method public default void releasePacProcessor();
+ method public default void setNetwork(@Nullable android.net.Network);
method public boolean setProxyScript(@NonNull String);
}
@@ -13021,11 +13258,11 @@ package android.webkit {
}
public interface WebViewFactoryProvider {
+ method @NonNull public default android.webkit.PacProcessor createPacProcessor();
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
method @NonNull public default android.webkit.PacProcessor getPacProcessor();
- method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network);
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 32bca9771097..cb885296e72a 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -172,8 +172,6 @@ MissingNullability: android.telephony.ModemActivityInfo#toString():
MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
-MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
-
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
MissingNullability: android.telephony.SmsCbCmasInfo#toString():
@@ -246,6 +244,9 @@ MutableBareField: android.net.wifi.WifiConfiguration#shareThisAp:
MutableBareField: android.net.wifi.WifiConfiguration#shared:
+MutableBareField: android.net.wifi.WifiConfiguration#subscriptionId:
+ Bare field subscriptionId must be marked final, or moved behind accessors if mutable
+
MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type:
MutableBareField: android.net.wifi.wificond.WifiNl80211Manager.WifiGenerationCapabilities#htSupport2g:
diff --git a/api/test-current.txt b/api/test-current.txt
index fa1d156d82e4..30485b28415e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14,14 +14,17 @@ package android {
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
+ field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
+ field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
+ field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
@@ -44,6 +47,7 @@ package android {
field public static final int config_defaultDialer = 17039395; // 0x1040023
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemGallery = 17039399; // 0x1040027
+ field public static final int config_systemVideoCall = 17039401; // 0x1040029
}
}
@@ -76,19 +80,22 @@ package android.app {
}
public class ActivityManager {
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String);
method public long getTotalRam();
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int);
+ method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
method public static boolean isHighEndGfx();
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
- method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7
field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
@@ -133,12 +140,12 @@ package android.app {
public class ActivityTaskManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToRootTask(int, int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedRootTask(int, @NonNull android.graphics.Rect);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeRootTasksInWindowingModes(@NonNull int[]);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeRootTasksWithActivityTypes(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void requestPictureInPictureMode(@NonNull android.os.IBinder);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeDockedStack(android.graphics.Rect, android.graphics.Rect);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizePrimarySplitScreen(@NonNull android.graphics.Rect, @NonNull android.graphics.Rect);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeTask(int, android.graphics.Rect);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setDisplayToSingleTaskInstance(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
@@ -197,12 +204,12 @@ package android.app {
public class AppOpsManager {
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void addHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOps);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void clearHistory();
- method @Nullable @RequiresPermission("android.permission.GET_APP_OPS_STATS") public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
- method @RequiresPermission("android.permission.GET_APP_OPS_STATS") public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public android.app.RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage();
+ method @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public void getHistoricalOps(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public static String[] getOpStrs();
- method @NonNull @RequiresPermission("android.permission.GET_APP_OPS_STATS") public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, @NonNull String, @Nullable java.lang.String...);
method public boolean isOperationActive(int, int, String);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void offsetHistory(long);
method public static int opToDefaultMode(@NonNull String);
@@ -448,10 +455,15 @@ package android.app {
}
public class DreamManager {
- method @RequiresPermission("android.permission.READ_DREAM_STATE") public boolean isDreaming();
- method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void setActiveDream(@NonNull android.content.ComponentName);
- method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void startDream(@NonNull android.content.ComponentName);
- method @RequiresPermission("android.permission.WRITE_DREAM_STATE") public void stopDream();
+ method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
+ method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@NonNull android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
+ }
+
+ public abstract class HomeVisibilityListener {
+ ctor public HomeVisibilityListener();
+ method public abstract void onHomeVisibilityChanged(boolean);
}
public final class NotificationChannel implements android.os.Parcelable {
@@ -483,9 +495,11 @@ package android.app {
method @NonNull public java.util.List<java.lang.String> getAllowedAssistantAdjustments();
method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
method public android.content.ComponentName getEffectsSuppressor();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public java.util.List<android.content.ComponentName> getEnabledNotificationListeners();
method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
method public boolean matchesCallFilter(android.os.Bundle);
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
@@ -513,7 +527,7 @@ package android.app {
method public void expandNotificationsPanel();
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
- method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
public static final class StatusBarManager.DisableInfo {
@@ -536,21 +550,21 @@ package android.app {
public final class UiAutomation {
method public void destroy();
- method public android.os.ParcelFileDescriptor[] executeShellCommandRw(String);
+ method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String);
method @Deprecated public boolean grantRuntimePermission(String, String, android.os.UserHandle);
method @Deprecated public boolean revokeRuntimePermission(String, String, android.os.UserHandle);
method public void syncInputTransactions();
}
public class UiModeManager {
- method @RequiresPermission("android.permission.ENTER_CAR_MODE_PRIORITIZED") public void enableCarMode(@IntRange(from=0) int, int);
+ method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
method public boolean isNightModeLocked();
method public boolean isUiModeLocked();
}
public class WallpaperManager {
method @Nullable public android.graphics.Bitmap getBitmap();
- method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+ method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
method public boolean shouldEnableWideColorGamut();
method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
@@ -626,11 +640,11 @@ package android.app.assist {
package android.app.backup {
public class BackupManager {
- method @RequiresPermission("android.permission.BACKUP") public android.content.Intent getConfigurationIntent(String);
- method @RequiresPermission("android.permission.BACKUP") public android.content.Intent getDataManagementIntent(String);
- method @Nullable @RequiresPermission("android.permission.BACKUP") public CharSequence getDataManagementIntentLabel(@NonNull String);
- method @Deprecated @Nullable @RequiresPermission("android.permission.BACKUP") public String getDataManagementLabel(@NonNull String);
- method @RequiresPermission("android.permission.BACKUP") public String getDestinationString(String);
+ method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getConfigurationIntent(String);
+ method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String);
}
}
@@ -755,20 +769,21 @@ package android.app.role {
}
public class RoleControllerManager {
- method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
}
public final class RoleManager {
- method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
- method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
- method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
- method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
- method @NonNull @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
- method @RequiresPermission("android.permission.OBSERVE_ROLE_HOLDERS") public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
- method @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
+ method @Nullable public String getSmsRoleHolder(int);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
@@ -875,7 +890,7 @@ package android.content {
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
- method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
@@ -894,7 +909,7 @@ package android.content {
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
- field @RequiresPermission("android.permission.MANAGE_ROLE_HOLDERS") public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
+ field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
field public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
@@ -994,8 +1009,9 @@ package android.content.pm {
public static class PackageInstaller.SessionParams implements android.os.Parcelable {
method public void setEnableRollback(boolean);
method public void setEnableRollback(boolean, int);
- method @RequiresPermission("android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS") public void setGrantedRuntimePermissions(String[]);
+ method @RequiresPermission(android.Manifest.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS) public void setGrantedRuntimePermissions(String[]);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setInstallAsApex();
+ method public void setInstallAsInstantApp(boolean);
method public void setInstallerPackageName(@Nullable String);
method public void setRequestDowngrade(boolean);
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setStaged();
@@ -1005,24 +1021,25 @@ package android.content.pm {
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract boolean arePermissionsIndividuallyControlled();
method @Nullable public String getContentCaptureServicePackageName();
- method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract String getDefaultBrowserPackageNameAsUser(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int);
method @Nullable public String getDefaultTextClassifierPackageName();
method @Nullable public String getIncidentReportApproverPackageName();
method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
- method @NonNull @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL") public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method @Nullable public abstract String[] getNamesForUids(int[]);
method @NonNull public abstract String getPermissionControllerPackageName();
- method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+ method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
method @Nullable public String getSystemTextClassifierPackageName();
method @Nullable public String getWellbeingPackageName();
- method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public void holdLock(int);
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
- method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
- method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
- method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS"}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, int, int, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle, @NonNull String);
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
@@ -1031,6 +1048,7 @@ package android.content.pm {
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -1216,6 +1234,34 @@ package android.graphics.drawable {
}
+package android.hardware.biometrics {
+
+ public class BiometricManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+ }
+
+ public class BiometricTestSession implements java.lang.AutoCloseable {
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void acceptAuthentication(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void cleanupInternalState(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void close();
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void finishEnroll(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyAcquired(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void notifyError(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void rejectAuthentication(int);
+ method @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public void startEnroll(int);
+ }
+
+ public class SensorProperties {
+ method public int getSensorId();
+ method public int getSensorStrength();
+ field public static final int STRENGTH_CONVENIENCE = 0; // 0x0
+ field public static final int STRENGTH_STRONG = 2; // 0x2
+ field public static final int STRENGTH_WEAK = 1; // 0x1
+ }
+
+}
+
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
@@ -1306,24 +1352,35 @@ package android.hardware.display {
}
public final class DisplayManager {
- method @RequiresPermission("android.permission.ACCESS_AMBIENT_LIGHT_STATS") public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
method @RequiresPermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration();
method public android.graphics.Point getStableDisplaySize();
method public boolean isMinimalPostProcessingRequested(int);
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+ method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
}
}
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+ }
+
+}
+
package android.hardware.hdmi {
public final class HdmiControlManager {
method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
- method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean);
+ method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
field public static final int AVR_VOLUME_MUTED = 101; // 0x65
field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2
@@ -1434,11 +1491,21 @@ package android.hardware.hdmi {
field public static final int PORT_OUTPUT = 1; // 0x1
}
- public class HdmiSwitchClient {
+ public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
method public int getDeviceType();
method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo();
- method public void sendKeyEvent(int, boolean);
- method public void sendVendorCommand(int, byte[], boolean);
+ }
+
+}
+
+package android.hardware.input {
+
+ public final class InputManager {
+ method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context);
+ method public float getMaximumObscuringOpacityForTouch(@NonNull android.content.Context);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, float);
+ field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
}
@@ -1697,8 +1764,7 @@ package android.location {
method @NonNull public String[] getBackgroundThrottlingWhitelist();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void getCurrentLocation(@NonNull android.location.LocationRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Location>);
method @NonNull public String[] getIgnoreSettingsWhitelist();
- method @Deprecated @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
- method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent);
@@ -1707,7 +1773,7 @@ package android.location {
}
public final class LocationRequest implements android.os.Parcelable {
- method @Nullable public android.os.WorkSource getWorkSource();
+ method @NonNull public android.os.WorkSource getWorkSource();
method public boolean isHiddenFromAppOps();
method public boolean isLocationSettingsIgnored();
method public boolean isLowPower();
@@ -1719,7 +1785,7 @@ package android.location {
}
public static final class LocationRequest.Builder {
- method @NonNull @RequiresPermission("android.permission.UPDATE_APP_OPS_STATS") public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
@@ -1754,12 +1820,12 @@ package android.media {
}
public class AudioManager {
- method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method public boolean hasRegisteredDynamicPolicy();
- method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
- method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
- method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
- method @RequiresPermission("android.permission.MODIFY_AUDIO_ROUTING") public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
field public static final int SUCCESS = 0; // 0x0
}
@@ -1849,6 +1915,8 @@ package android.media {
}
public static final class MediaTranscodeManager.TranscodingRequest {
+ method public int getClientPid();
+ method public int getClientUid();
method @NonNull public android.net.Uri getDestinationUri();
method public int getPriority();
method @NonNull public android.net.Uri getSourceUri();
@@ -1859,6 +1927,8 @@ package android.media {
public static final class MediaTranscodeManager.TranscodingRequest.Builder {
ctor public MediaTranscodeManager.TranscodingRequest.Builder();
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build();
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int);
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri);
@@ -2010,6 +2080,57 @@ package android.media.audiopolicy {
}
+package android.media.musicrecognition {
+
+ public class MusicRecognitionManager {
+ field public static final int RECOGNITION_FAILED_AUDIO_UNAVAILABLE = 7; // 0x7
+ field public static final int RECOGNITION_FAILED_NOT_FOUND = 1; // 0x1
+ field public static final int RECOGNITION_FAILED_NO_CONNECTIVITY = 2; // 0x2
+ field public static final int RECOGNITION_FAILED_SERVICE_KILLED = 5; // 0x5
+ field public static final int RECOGNITION_FAILED_SERVICE_UNAVAILABLE = 3; // 0x3
+ field public static final int RECOGNITION_FAILED_TIMEOUT = 6; // 0x6
+ field public static final int RECOGNITION_FAILED_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static interface MusicRecognitionManager.RecognitionCallback {
+ method public void onAudioStreamClosed();
+ method public void onRecognitionFailed(@NonNull android.media.musicrecognition.RecognitionRequest, int);
+ method public void onRecognitionSucceeded(@NonNull android.media.musicrecognition.RecognitionRequest, @NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public abstract class MusicRecognitionService extends android.app.Service {
+ ctor public MusicRecognitionService();
+ method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
+ }
+
+ public static interface MusicRecognitionService.Callback {
+ method public void onRecognitionFailed(int);
+ method public void onRecognitionSucceeded(@NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public final class RecognitionRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.media.AudioAttributes getAudioAttributes();
+ method @NonNull public android.media.AudioFormat getAudioFormat();
+ method public int getCaptureSession();
+ method public int getIgnoreBeginningFrames();
+ method public int getMaxAudioLengthSeconds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.musicrecognition.RecognitionRequest> CREATOR;
+ }
+
+ public static final class RecognitionRequest.Builder {
+ ctor public RecognitionRequest.Builder();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest build();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setCaptureSession(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setIgnoreBeginningFrames(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setMaxAudioLengthSeconds(int);
+ }
+
+}
+
package android.media.tv {
public final class TvInputManager {
@@ -2019,6 +2140,21 @@ package android.media.tv {
}
+package android.media.tv.tuner {
+
+ public final class TunerVersionChecker {
+ method public static int getMajorVersion(int);
+ method public static int getMinorVersion(int);
+ method public static int getTunerVersion();
+ method public static boolean isHigherOrEqualVersionTo(int);
+ method public static boolean supportTunerVersion(int);
+ field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+ field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+ field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+ }
+
+}
+
package android.metrics {
public class LogMaker {
@@ -2197,15 +2333,15 @@ package android.net {
method @NonNull public android.net.NetworkCapabilities build();
method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
- method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
- method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setOwnerUid(int);
- method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
- method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setRequestorUid(int);
- method @NonNull @RequiresPermission("android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP") public android.net.NetworkCapabilities.Builder setSignalStrength(int);
- method @NonNull @RequiresPermission("android.permission.NETWORK_FACTORY") public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
}
@@ -2283,11 +2419,11 @@ package android.net {
public class TetheringManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
- method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
- method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
- method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
- method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
- method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
@@ -2352,9 +2488,9 @@ package android.net {
public static class TetheringManager.TetheringRequest.Builder {
ctor public TetheringManager.TetheringRequest.Builder(int);
method @NonNull public android.net.TetheringManager.TetheringRequest build();
- method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
- method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
- method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
}
public class TrafficStats {
@@ -2556,7 +2692,7 @@ package android.net.util {
package android.os {
public class BatteryManager {
- method @RequiresPermission("android.permission.POWER_SAVER") public boolean setChargingStateUpdateDelayMillis(int);
+ method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setChargingStateUpdateDelayMillis(int);
}
public final class BugreportManager {
@@ -2600,7 +2736,7 @@ package android.os {
}
public class DeviceIdleManager {
- method @RequiresPermission("android.permission.DEVICE_POWER") public void endIdle(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void endIdle(@NonNull String);
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
}
@@ -2853,21 +2989,21 @@ package android.os {
}
public final class PowerManager {
- method @RequiresPermission("android.permission.POWER_SAVER") public int getPowerSaveModeTrigger();
- method @RequiresPermission("android.permission.DEVICE_POWER") public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
- method @RequiresPermission("android.permission.POWER_SAVER") public boolean setDynamicPowerSaveHint(boolean, int);
- method @RequiresPermission(anyOf={"android.permission.DEVICE_POWER", "android.permission.POWER_SAVER"}) public boolean setPowerSaveModeEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public int getPowerSaveModeTrigger();
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean);
+ method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean);
field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED";
field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
}
public class PowerWhitelistManager {
- method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull String);
- method @RequiresPermission("android.permission.DEVICE_POWER") public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
- method @RequiresPermission("android.permission.DEVICE_POWER") public void removeFromWhitelist(@NonNull String);
- method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public void whitelistAppTemporarily(@NonNull String, long);
- method @RequiresPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String);
field public static final int EVENT_MMS = 2; // 0x2
field public static final int EVENT_SMS = 1; // 0x1
field public static final int EVENT_UNSPECIFIED = 0; // 0x0
@@ -2933,8 +3069,8 @@ package android.os {
}
public class SystemConfigManager {
- method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
- method @NonNull @RequiresPermission("android.permission.READ_CARRIER_APP_INFO") public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
}
public class SystemProperties {
@@ -2963,13 +3099,13 @@ package android.os {
}
public class UserManager {
- method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method public static boolean isSplitSystemUser();
field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
}
public final class VibrationAttributes implements android.os.Parcelable {
- method @Deprecated @NonNull public android.media.AudioAttributes getAudioAttributes();
+ method public int getAudioUsage();
}
public static final class VibrationAttributes.Builder {
@@ -3023,10 +3159,10 @@ package android.os {
}
public abstract class Vibrator {
- method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
- method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
- method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public boolean isVibrating();
- method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
}
public static interface Vibrator.OnVibratorStateChangedListener {
@@ -3125,12 +3261,12 @@ package android.os.image {
public class DynamicSystemClient {
ctor public DynamicSystemClient(@NonNull android.content.Context);
- method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void bind();
+ method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void bind();
method public void setOnStatusChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
method public void setOnStatusChangedListener(@NonNull android.os.image.DynamicSystemClient.OnStatusChangedListener);
- method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long);
- method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void start(@NonNull android.net.Uri, long, long);
- method @RequiresPermission("android.permission.INSTALL_DYNAMIC_SYSTEM") public void unbind();
+ method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void start(@NonNull android.net.Uri, long);
+ method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void start(@NonNull android.net.Uri, long, long);
+ method @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) public void unbind();
field public static final int CAUSE_ERROR_EXCEPTION = 6; // 0x6
field public static final int CAUSE_ERROR_INVALID_URL = 4; // 0x4
field public static final int CAUSE_ERROR_IO = 3; // 0x3
@@ -3184,13 +3320,13 @@ package android.os.strictmode {
package android.permission {
public final class PermissionControllerManager {
- method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
- method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
- method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
- method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermission(@NonNull String, @NonNull String);
- method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
- method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
+ method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermission(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -3211,9 +3347,9 @@ package android.permission {
}
public final class PermissionManager {
- method @IntRange(from=0) @RequiresPermission(anyOf={"android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY", android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
+ method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public int getRuntimePermissionsVersion();
method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
- method @RequiresPermission(anyOf={"android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY", android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY, android.Manifest.permission.UPGRADE_RUNTIME_PERMISSIONS}) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
}
public static final class PermissionManager.SplitPermissionInfo {
@@ -3279,18 +3415,19 @@ package android.provider {
}
public final class DeviceConfig {
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(@NonNull String, @NonNull String, float);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static int getInt(@NonNull String, @NonNull String, int);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(@NonNull String, @NonNull String, long);
- method @NonNull @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(@NonNull String, @NonNull String);
- method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(@NonNull String, @NonNull String, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
+ field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
field public static final String NAMESPACE_ANDROID = "android";
field public static final String NAMESPACE_AUTOFILL = "autofill";
field public static final String NAMESPACE_BIOMETRICS = "biometrics";
@@ -3358,6 +3495,7 @@ package android.provider {
field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
+ field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
field public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
@@ -3373,6 +3511,11 @@ package android.provider {
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
field public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
+ field public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY = "accessibility_magnification_capability";
+ field public static final String ACCESSIBILITY_MAGNIFICATION_MODE = "accessibility_magnification_mode";
+ field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_ALL = 3; // 0x3
+ field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+ field public static final int ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW = 2; // 0x2
field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service";
field public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
field public static final String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification";
@@ -3395,6 +3538,8 @@ package android.provider {
field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
field public static final String NOTIFICATION_BADGING = "notification_badging";
field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content";
+ field public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker";
+ field public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype";
field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option";
field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
@@ -4098,6 +4243,27 @@ package android.telephony {
field public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
}
+ public final class ModemActivityInfo implements android.os.Parcelable {
+ ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
+ method public int describeContents();
+ method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+ method public long getIdleTimeMillis();
+ method public static int getNumTxPowerLevels();
+ method public long getReceiveTimeMillis();
+ method public long getSleepTimeMillis();
+ method public long getTimestampMillis();
+ method public long getTransmitDurationMillisAtPowerLevel(int);
+ method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+ method public boolean isValid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+ field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+ field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+ field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+ field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+ field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+ }
+
public final class NetworkRegistrationInfo implements android.os.Parcelable {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getRegistrationState();
@@ -4140,8 +4306,8 @@ package android.telephony {
method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
method @Deprecated public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber, int);
- field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
- field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_CALL = 268435456; // 0x10000000
+ field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 536870912; // 0x20000000
}
public final class PreciseDataConnectionState implements android.os.Parcelable {
@@ -4182,27 +4348,31 @@ package android.telephony {
public class TelephonyManager {
method public int addDevicePolicyOverrideApn(@NonNull android.content.Context, @NonNull android.telephony.data.ApnSetting);
method public int checkCarrierPrivilegesForPackage(String);
- method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
method public int getCarrierIdListVersion();
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
- method @Nullable @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public android.content.ComponentName getDefaultRespondViaMessageApplication();
+ method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getDefaultRespondViaMessageApplication();
method @NonNull public java.util.List<android.telephony.data.ApnSetting> getDevicePolicyOverrideApns(@NonNull android.content.Context);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
- method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void resetOtaEmergencyNumberDbFilePath();
+ method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void resetOtaEmergencyNumberDbFilePath();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
- method @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
+ method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor);
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
+ field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
+ field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
field public static final int UNKNOWN_CARRIER_ID_LIST_VERSION = -1; // 0xffffffff
}
@@ -5279,6 +5449,7 @@ package android.view {
}
public interface WindowManager extends android.view.ViewManager {
+ method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public default void holdLock(int);
method public default void setShouldShowIme(int, boolean);
method public default void setShouldShowSystemDecors(int, boolean);
method public default void setShouldShowWithInsecureKeyguard(int, boolean);
@@ -5299,11 +5470,11 @@ package android.view.accessibility {
public final class AccessibilityManager {
method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
- method @NonNull @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
- method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void performAccessibilityShortcut();
- method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
- method @RequiresPermission("android.permission.MANAGE_ACCESSIBILITY") public void unregisterSystemAction(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
}
public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
@@ -5528,6 +5699,18 @@ package android.view.inspector {
}
+package android.view.textservice {
+
+ public final class SpellCheckerSubtype implements android.os.Parcelable {
+ field public static final int SUBTYPE_ID_NONE = 0; // 0x0
+ }
+
+ public final class TextServicesManager {
+ method public boolean isSpellCheckerEnabled();
+ }
+
+}
+
package android.widget {
public abstract class AbsListView extends android.widget.AdapterView<android.widget.ListAdapter> implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener {
@@ -5571,6 +5754,11 @@ package android.widget {
method public void disableClockTick();
}
+ @android.widget.RemoteViews.RemoteView public class TextView extends android.view.View implements android.view.ViewTreeObserver.OnPreDrawListener {
+ method public void onActivityResult(int, int, @Nullable android.content.Intent);
+ field public static final int PROCESS_TEXT_REQUEST_CODE = 100; // 0x64
+ }
+
public class TimePicker extends android.widget.FrameLayout {
method public android.view.View getAmView();
method public android.view.View getHourView();
@@ -5629,6 +5817,15 @@ package android.window {
field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
}
+ public final class TaskAppearedInfo implements android.os.Parcelable {
+ ctor public TaskAppearedInfo(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
+ method public int describeContents();
+ method @NonNull public android.view.SurfaceControl getLeash();
+ method @NonNull public android.app.ActivityManager.RunningTaskInfo getTaskInfo();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
+ }
+
public class TaskOrganizer extends android.window.WindowOrganizer {
ctor public TaskOrganizer();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public android.app.ActivityManager.RunningTaskInfo createRootTask(int, int);
@@ -5640,10 +5837,10 @@ package android.window {
method @BinderThread public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo, @NonNull android.view.SurfaceControl);
method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void registerOrganizer();
+ method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public final void unregisterOrganizer();
+ method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void unregisterOrganizer();
}
public final class WindowContainerToken implements android.os.Parcelable {
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index c8e751776359..4a8554f8fffb 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -2513,6 +2513,8 @@ NoSettingsProvider: android.provider.Settings.Global#DYNAMIC_POWER_SAVINGS_ENABL
NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
+NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
+
NoSettingsProvider: android.provider.Settings.Global#HIDE_ERROR_DIALOGS:
NoSettingsProvider: android.provider.Settings.Global#LOCATION_GLOBAL_KILL_SWITCH:
@@ -2532,6 +2534,16 @@ NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED:
NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE:
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
+
+NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE:
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index b7fc1c47c0c3..d0ff5805d110 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -482,6 +482,8 @@ status_t BootAnimation::readyToRun() {
mFlingerSurface = s;
mTargetInset = -1;
+ projectSceneToWindow();
+
// Register a display event receiver
mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
status_t status = mDisplayEventReceiver->initCheck();
@@ -493,6 +495,16 @@ status_t BootAnimation::readyToRun() {
return NO_ERROR;
}
+void BootAnimation::projectSceneToWindow() {
+ glViewport(0, 0, mWidth, mHeight);
+ glScissor(0, 0, mWidth, mHeight);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+}
+
void BootAnimation::resizeSurface(int newWidth, int newHeight) {
// We assume this function is called on the animation thread.
if (newWidth == mWidth && newHeight == mHeight) {
@@ -517,8 +529,8 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) {
SLOGE("Can't make the new surface current. Error %d", eglGetError());
return;
}
- glViewport(0, 0, mWidth, mHeight);
- glScissor(0, 0, mWidth, mHeight);
+
+ projectSceneToWindow();
mSurface = surface;
}
@@ -810,6 +822,37 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) {
return status;
}
+void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
+ const int frameHeight, const Animation::Part& part,
+ const int fadedFramesCount) {
+ glEnable(GL_BLEND);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glDisable(GL_TEXTURE_2D);
+ // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
+ glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
+
+ const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
+ glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);
+
+ const float frameStartX = static_cast<float>(frameLeft);
+ const float frameStartY = static_cast<float>(frameBottom);
+ const float frameEndX = frameStartX + frameWidth;
+ const float frameEndY = frameStartY + frameHeight;
+ const GLfloat frameRect[] = {
+ frameStartX, frameStartY,
+ frameEndX, frameStartY,
+ frameEndX, frameEndY,
+ frameStartX, frameEndY
+ };
+ glVertexPointer(2, GL_FLOAT, 0, frameRect);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_TEXTURE_2D);
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisable(GL_BLEND);
+}
+
void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
glEnable(GL_BLEND); // Allow us to draw on top of the animation
glBindTexture(GL_TEXTURE_2D, font.texture.name);
@@ -901,23 +944,34 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
int height = 0;
int count = 0;
int pause = 0;
+ int framesToFadeCount = 0;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
-
char pathType;
+
+ int nextReadPos;
+
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
// SLOGD("> w=%d, h=%d, fps=%d", width, height, fps);
animation.width = width;
animation.height = height;
animation.fps = fps;
- } else if (sscanf(l, " %c %d %d %" STRTO(ANIM_PATH_MAX) "s #%6s %16s %16s",
- &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
- //SLOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
- // pathType, count, pause, path, color, clockPos1, clockPos2);
+ } else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
+ &pathType, &count, &pause, path, &nextReadPos) >= 4) {
+ if (pathType == 'f') {
+ sscanf(l + nextReadPos, " %d #%6s %16s %16s", &framesToFadeCount, color, clockPos1,
+ clockPos2);
+ } else {
+ sscanf(l + nextReadPos, " #%6s %16s %16s", color, clockPos1, clockPos2);
+ }
+ // SLOGD("> type=%c, count=%d, pause=%d, path=%s, framesToFadeCount=%d, color=%s, "
+ // "clockPos1=%s, clockPos2=%s",
+ // pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
+ part.framesToFadeCount = framesToFadeCount;
part.count = count;
part.pause = pause;
part.path = path;
@@ -936,6 +990,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
// SLOGD("> SYSTEM");
Animation::Part part;
part.playUntilComplete = false;
+ part.framesToFadeCount = 0;
part.count = 1;
part.pause = 0;
part.audioData = nullptr;
@@ -1132,12 +1187,19 @@ bool BootAnimation::movie() {
return false;
}
+bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part, const int fadedFramesCount) {
+ // stop playing only if it is time to exit and it's a partial part which has been faded out
+ return exitPending() && !part.playUntilComplete && fadedFramesCount >= part.framesToFadeCount;
+}
+
bool BootAnimation::playAnimation(const Animation& animation) {
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
+
+ int fadedFramesCount = 0;
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
@@ -1151,10 +1213,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
continue; //to next part
}
- for (int r=0 ; !part.count || r<part.count ; r++) {
- // Exit any non playuntil complete parts immediately
- if(exitPending() && !part.playUntilComplete)
- break;
+ // process the part not only while the count allows but also if already fading
+ for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
+ if (shouldStopPlayingPart(part, fadedFramesCount)) break;
mCallbacks->playPart(i, part, r);
@@ -1164,7 +1225,9 @@ bool BootAnimation::playAnimation(const Animation& animation) {
part.backgroundColor[2],
1.0f);
- for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
+ for (size_t j=0 ; j<fcount ; j++) {
+ if (shouldStopPlayingPart(part, fadedFramesCount)) break;
+
processDisplayEvents();
const int animationX = (mWidth - animation.width) / 2;
@@ -1203,11 +1266,22 @@ bool BootAnimation::playAnimation(const Animation& animation) {
}
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
- glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
- 0, frame.trimWidth, frame.trimHeight);
+ const int frameDrawY = mHeight - (yc + frame.trimHeight);
+ glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);
+
+ // if the part hasn't been stopped yet then continue fading if necessary
+ if (exitPending() && part.hasFadingPhase()) {
+ fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
+ ++fadedFramesCount);
+ if (fadedFramesCount >= part.framesToFadeCount) {
+ fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
+ }
+ }
+
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
+
handleViewport(frameDuration);
eglSwapBuffers(mDisplay, mSurface);
@@ -1232,11 +1306,11 @@ bool BootAnimation::playAnimation(const Animation& animation) {
usleep(part.pause * ns2us(frameDuration));
- // For infinite parts, we've now played them at least once, so perhaps exit
- if(exitPending() && !part.count && mCurrentInset >= mTargetInset)
- break;
+ if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
+ !part.hasFadingPhase()) {
+ break; // exit the infinite non-fading part when it has been played at least once
+ }
}
-
}
// Free textures created for looping parts now that the animation is done.
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 9e6e4aa42f1c..4699cfe2ac2d 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -19,6 +19,7 @@
#include <vector>
#include <queue>
+#include <climits>
#include <stdint.h>
#include <sys/types.h>
@@ -43,6 +44,8 @@ class SurfaceControl;
class BootAnimation : public Thread, public IBinder::DeathRecipient
{
public:
+ static constexpr int MAX_FADED_FRAMES_COUNT = std::numeric_limits<int>::max();
+
struct Texture {
GLint w;
GLint h;
@@ -82,10 +85,15 @@ public:
String8 trimData;
SortedVector<Frame> frames;
bool playUntilComplete;
+ int framesToFadeCount;
float backgroundColor[3];
uint8_t* audioData;
int audioLength;
Animation* animation;
+
+ bool hasFadingPhase() const {
+ return !playUntilComplete && framesToFadeCount > 0;
+ }
};
int fps;
int width;
@@ -160,6 +168,8 @@ private:
bool movie();
void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
void drawClock(const Font& font, const int xPos, const int yPos);
+ void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
+ const Animation::Part& part, int fadedFramesCount);
bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
@@ -172,7 +182,9 @@ private:
EGLConfig getEglConfig(const EGLDisplay&);
ui::Size limitSurfaceSize(int width, int height) const;
void resizeSurface(int newWidth, int newHeight);
+ void projectSceneToWindow();
+ bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount);
void checkExit();
void handleViewport(nsecs_t timestep);
diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md
index 5946515aa263..f9b83c957d5b 100644
--- a/cmds/bootanimation/FORMAT.md
+++ b/cmds/bootanimation/FORMAT.md
@@ -30,14 +30,20 @@ The first line defines the general parameters of the animation:
It is followed by a number of rows of the form:
- TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]]
+ TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]
* **TYPE:** a single char indicating what type of animation segment this is:
+ `p` -- this part will play unless interrupted by the end of the boot
+ `c` -- this part will play to completion, no matter what
+ + `f` -- same as `p` but in addition the specified number of frames is being faded out while
+ continue playing. Only the first interrupted `f` part is faded out, other subsequent `f`
+ parts are skipped
* **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
* **PAUSE:** number of FRAMES to delay after this part ends
* **PATH:** directory in which to find the frames for this part (e.g. `part0`)
+ * **FADE:** _(ONLY FOR `f` TYPE)_ number of frames to fade out when interrupted where `0` means
+ _immediately_ which makes `f ... 0` behave like `p` and doesn't count it as a fading
+ part
* **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
* **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches):
+ If only `CLOCK1` is provided it is the y-coordinate of the clock and the x-coordinate
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 0a09801708a5..091e9a752355 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -38,7 +38,6 @@ import "frameworks/base/core/proto/android/net/networkcapabilities.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
-import "frameworks/base/core/proto/android/stats/hdmi/enums.proto";
import "frameworks/base/core/proto/android/server/job/enums.proto";
import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
@@ -51,6 +50,7 @@ import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enum
import "frameworks/base/core/proto/android/stats/docsui/docsui_enums.proto";
import "frameworks/base/core/proto/android/stats/accessibility/accessibility_enums.proto";
import "frameworks/base/core/proto/android/stats/enums.proto";
+import "frameworks/base/core/proto/android/stats/hdmi/enums.proto";
import "frameworks/base/core/proto/android/stats/intelligence/enums.proto";
import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
import "frameworks/base/core/proto/android/stats/location/location_enums.proto";
@@ -266,7 +266,7 @@ message Atom {
147 [(module) = "framework", (module) = "statsd"];
BiometricSystemHealthIssueDetected biometric_system_health_issue_detected =
148 [(module) = "framework"];
- BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"];
+ BubbleUIChanged bubble_ui_changed = 149 [(module) = "framework"];
ScheduledJobConstraintChanged scheduled_job_constraint_changed =
150 [(module) = "framework"];
BluetoothActiveDeviceChanged bluetooth_active_device_changed =
@@ -493,13 +493,15 @@ message Atom {
WifiConnectionStateChanged wifi_connection_state_changed = 308 [(module) = "wifi"];
HdmiCecActiveSourceChanged hdmi_cec_active_source_changed = 309 [(module) = "framework"];
HdmiCecMessageReported hdmi_cec_message_reported = 310 [(module) = "framework"];
+ AirplaneMode airplane_mode = 311 [(module) = "telephony"];
+ ModemRestart modem_restart = 312 [(module) = "telephony"];
// StatsdStats tracks platform atoms with ids upto 500.
// Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
}
// Pulled events will start at field 10000.
- // Next: 10084
+ // Next: 10088
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"];
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"];
@@ -598,6 +600,8 @@ message Atom {
DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"];
GeneralExternalStorageAccessStats general_external_storage_access_stats =
10085 [(module) = "mediaprovider"];
+ IncomingSms incoming_sms = 10086 [(module) = "telephony"];
+ OutgoingSms outgoing_sms = 10087 [(module) = "telephony"];
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -3191,6 +3195,9 @@ message BackGesture {
optional int32 end_y = 7; // Y coordinate for ACTION_MOVE event.
optional int32 left_boundary = 8; // left edge width + left inset
optional int32 right_boundary = 9; // screen width - (right edge width + right inset)
+ // The score between 0 and 1 which is the prediction output for the Back Gesture model.
+ optional float ml_model_score = 10;
+ optional string package_name = 11; // The name of the top 100 most used package by all users.
enum WindowHorizontalLocation {
DEFAULT_LOCATION = 0;
@@ -3346,6 +3353,7 @@ message StyleUIChanged {
optional int32 wallpaper_id_hash = 8;
optional int32 color_preference = 9;
optional android.stats.style.LocationPreference location_preference = 10;
+ optional android.stats.style.DatePreference date_preference = 11;
}
/**
@@ -3773,6 +3781,7 @@ message AppStartOccurred {
LAUNCHER = 1;
NOTIFICATION = 2;
LOCKSCREEN = 3;
+ RECENTS_ANIMATION = 4;
}
// The type of the startup source.
optional SourceType source_type = 16;
@@ -3825,6 +3834,12 @@ message AppStartFullyDrawn {
// App startup time (until call to Activity#reportFullyDrawn()).
optional int64 app_startup_time_millis = 6;
+ // The compiler filter used when when the package was optimized.
+ optional int32 package_optimization_compilation_filter = 7;
+
+ // The reason why the package was optimized.
+ optional int32 package_optimization_compilation_reason = 8;
+
enum SourceType {
UNAVAILABLE = 0;
LAUNCHER = 1;
@@ -3832,11 +3847,11 @@ message AppStartFullyDrawn {
LOCKSCREEN = 3;
}
// The type of the startup source.
- optional SourceType source_type = 7;
+ optional SourceType source_type = 9;
// The time from the startup source to the beginning of handling the startup event.
// -1 means not available.
- optional int32 source_event_delay_millis = 8;
+ optional int32 source_event_delay_millis = 10;
}
/**
@@ -4459,6 +4474,7 @@ message BinaryPushStateChanged {
// which requires reboot and not eligible for any reboot promotion strategy
// (e.g. soft restart, notification restart).
NO_REBOOT_PROMOTION_STRATEGY_ELIGIBLE = 30;
+ REBOOT_TRIGGER_FAILURE = 31;
}
optional State state = 6;
// Possible experiment ids for monitoring this push.
@@ -5259,12 +5275,23 @@ message BlobOpened{
* Event to track Jank for various system interactions.
*
* Logged from:
- * frameworks/base/core/java/android/os/aot/FrameTracker.java
+ * frameworks/base/core/java/com/android/internal/jank/FrameTracker.java
*/
message UIInteractionFrameInfoReported {
enum InteractionType {
UNKNOWN = 0;
NOTIFICATION_SHADE_SWIPE = 1;
+ SHADE_EXPAND_COLLAPSE_LOCK = 2;
+ SHADE_SCROLL_FLING = 3;
+ SHADE_ROW_EXPAND = 4;
+ SHADE_ROW_SWIPE = 5;
+ SHADE_QS_EXPAND_COLLAPSE = 6;
+ SHADE_QS_SCROLL_SWIPE = 7;
+ LAUNCHER_APP_LAUNCH_FROM_RECENTS = 8;
+ LAUNCHER_APP_LAUNCH_FROM_ICON = 9;
+ LAUNCHER_APP_CLOSE_TO_HOME = 10;
+ LAUNCHER_APP_CLOSE_TO_PIP = 11;
+ LAUNCHER_QUICK_SWITCH = 12;
}
optional InteractionType interaction_type = 1;
@@ -10460,12 +10487,156 @@ message SupportedRadioAccessFamily {
}
/**
+ * Pulls information for a single incoming SMS.
+ *
+ * Each pull creates multiple atoms, one for each SMS. The sequence is randomized when pulled.
+ *
+ * Pulled from:
+ * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+ */
+message IncomingSms {
+ // Format of the SMS (3GPP or 3GPP2).
+ optional android.telephony.SmsFormatEnum sms_format = 1;
+
+ // Technology of the SMS (CS or IMS).
+ optional android.telephony.SmsTechEnum sms_tech = 2;
+
+ // Radio access technology (RAT) used for the SMS. It can be IWLAN in case of IMS.
+ optional android.telephony.NetworkTypeEnum rat = 3;
+
+ // Type the SMS.
+ optional android.telephony.SmsTypeEnum sms_type = 4;
+
+ // Number of total parts.
+ optional int32 total_parts = 5;
+
+ // Number of received parts (if smaller than total parts, the SMS was dropped).
+ optional int32 received_parts = 6;
+
+ // Indicates if the incoming SMS was blocked.
+ optional bool blocked = 7;
+
+ // Indicate a specific error handling the SMS
+ optional android.telephony.SmsIncomingErrorEnum error = 8;
+
+ // Whether the SMS was received while roaming.
+ optional bool is_roaming = 9;
+
+ // Index of the SIM is used, 0 for single-SIM devices.
+ optional int32 sim_slot_index = 10;
+
+ // Whether the device was in multi-SIM mode (with multiple active SIM profiles).
+ optional bool is_multi_sim = 11;
+
+ // Whether the message was received with an eSIM profile.
+ optional bool is_esim = 12;
+
+ // Carrier ID of the SIM card used for the SMS.
+ // See https://source.android.com/devices/tech/config/carrierid.
+ optional int32 carrier_id = 13;
+
+ // Random message ID.
+ optional int64 message_id = 14;
+}
+
+/**
+ * Pulls information for a single outgoing SMS.
+ *
+ * Each pull creates multiple atoms, one for each SMS. The sequence is randomized when pulled.
+ *
+ * Pulled from:
+ * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+ */
+message OutgoingSms {
+ // Format of the SMS (3GPP or 3GPP2).
+ optional android.telephony.SmsFormatEnum sms_format = 1;
+
+ // Technology of the SMS (CS or IMS).
+ optional android.telephony.SmsTechEnum sms_tech = 2;
+
+ // Radio access technology (RAT) used for the SMS. It can be IWLAN in case of IMS.
+ optional android.telephony.NetworkTypeEnum rat = 3;
+
+ // Result of the SMS sending.
+ optional android.telephony.SmsSendResultEnum send_result = 4;
+
+ // Error code
+ // For IMS technology, see @SmsManager.Result in
+ // http://cs/android/frameworks/base/telephony/java/android/telephony/SmsManager.java
+ // For CS technology:
+ // - GSM format: see GsmSmsErrorCode (3GPP 27.005 clause 3.2.5)
+ // - CDMA format: see CdmaSmsErrorCode (3GPP2 N.S0005 (IS-41-C) Table 171)
+ optional int32 error_code = 5;
+
+ // Whether the SMS was sent while roaming.
+ optional bool is_roaming = 6;
+
+ // Whether the default SMS application generated the SMS (regardless of which application).
+ optional bool is_from_default_app = 7;
+
+ // Index of the SIM is used, 0 for single-SIM devices.
+ optional int32 sim_slot_index = 8;
+
+ // Whether the device was in multi-SIM mode (with multiple active SIM profiles).
+ optional bool is_multi_sim = 9;
+
+ // Whether the message was sent with an eSIM profile.
+ optional bool is_esim = 10;
+
+ // Carrier ID of the SIM card used for the SMS.
+ // See https://source.android.com/devices/tech/config/carrierid.
+ optional int32 carrier_id = 11;
+
+ // Random message ID.
+ optional int64 message_id = 12;
+
+ // Retry count: 0 for the first attempt and then increasing for each attempt.
+ optional int32 retry_id = 13;
+}
+
+/**
+ * Push information about usage of airplane mode.
+ *
+ * Logged from:
+ * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/AirplaneModeStats.java
+ */
+message AirplaneMode {
+ // Status of airplane mode
+ optional bool is_enabled = 1;
+
+ // When is_enabled is false, indicates if this was a very short airplane mode toggle
+ // (i.e. airplane mode was disabled after less than 10 seconds from enablement).
+ optional bool short_toggle = 2;
+
+ // Carrier ID of the SIM card.
+ // See https://source.android.com/devices/tech/config/carrierid.
+ optional int32 carrier_id = 3;
+}
+
+/**
+ * Push information about modem restarts.
+ *
+ * Logged from:
+ * frameworks/opt/telephony/src/java/com/android/internal/telephony/metrics/ModemRestartStats.java
+ */
+message ModemRestart {
+ // Software version of the modem, as provided by android.os.Build.getRadioVersion().
+ optional string baseband_version = 1;
+
+ // Reason of the modem restart, as provided in the modemReset indication of IRadio HAL.
+ optional string reason = 2;
+
+ // Carrier ID of the first SIM card.
+ // See https://source.android.com/devices/tech/config/carrierid.
+ optional int32 carrier_id = 3;
+}
+
+/**
* Logs gnss stats from location service provider
*
* Pulled from:
* frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
*/
-
message GnssStats {
// Number of location reports since boot
optional int64 location_reports = 1;
diff --git a/cmds/statsd/src/condition/ConditionTimer.h b/cmds/statsd/src/condition/ConditionTimer.h
index 442bc11934fe..1fbe25279736 100644
--- a/cmds/statsd/src/condition/ConditionTimer.h
+++ b/cmds/statsd/src/condition/ConditionTimer.h
@@ -36,7 +36,7 @@ class ConditionTimer {
public:
explicit ConditionTimer(bool initCondition, int64_t bucketStartNs) : mCondition(initCondition) {
if (initCondition) {
- mLastConditionTrueTimestampNs = bucketStartNs;
+ mLastConditionChangeTimestampNs = bucketStartNs;
}
};
@@ -44,21 +44,46 @@ public:
// When a new bucket is created, this value will be reset to 0.
int64_t mTimerNs = 0;
- // Last elapsed real timestamp when condition turned to true
- // When a new bucket is created and the condition is true, then the timestamp is set
- // to be the bucket start timestamp.
- int64_t mLastConditionTrueTimestampNs = 0;
+ // Last elapsed real timestamp when condition changed.
+ int64_t mLastConditionChangeTimestampNs = 0;
bool mCondition = false;
int64_t newBucketStart(int64_t nextBucketStartNs) {
if (mCondition) {
- mTimerNs += (nextBucketStartNs - mLastConditionTrueTimestampNs);
- mLastConditionTrueTimestampNs = nextBucketStartNs;
+ // Normally, the next bucket happens after the last condition
+ // change. In this case, add the time between the condition becoming
+ // true to the next bucket start time.
+ // Otherwise, the next bucket start time is before the last
+ // condition change time, this means that the condition was false at
+ // the bucket boundary before the condition became true, so the
+ // timer should not get updated and the last condition change time
+ // remains as is.
+ if (nextBucketStartNs >= mLastConditionChangeTimestampNs) {
+ mTimerNs += (nextBucketStartNs - mLastConditionChangeTimestampNs);
+ mLastConditionChangeTimestampNs = nextBucketStartNs;
+ }
+ } else if (mLastConditionChangeTimestampNs > nextBucketStartNs) {
+ // The next bucket start time is before the last condition change
+ // time, this means that the condition was true at the bucket
+ // boundary before the condition became false, so adjust the timer
+ // to match how long the condition was true to the bucket boundary.
+ // This means remove the amount the condition stayed true in the
+ // next bucket from the current bucket.
+ mTimerNs -= (mLastConditionChangeTimestampNs - nextBucketStartNs);
}
int64_t temp = mTimerNs;
mTimerNs = 0;
+
+ if (!mCondition && (mLastConditionChangeTimestampNs > nextBucketStartNs)) {
+ // The next bucket start time is before the last condition change
+ // time, this means that the condition was true at the bucket
+ // boundary and remained true in the next bucket up to the condition
+ // change to false, so adjust the timer to match how long the
+ // condition stayed true in the next bucket (now the current bucket).
+ mTimerNs = mLastConditionChangeTimestampNs - nextBucketStartNs;
+ }
return temp;
}
@@ -67,11 +92,10 @@ public:
return;
}
mCondition = newCondition;
- if (newCondition) {
- mLastConditionTrueTimestampNs = timestampNs;
- } else {
- mTimerNs += (timestampNs - mLastConditionTrueTimestampNs);
+ if (newCondition == false) {
+ mTimerNs += (timestampNs - mLastConditionChangeTimestampNs);
}
+ mLastConditionChangeTimestampNs = timestampNs;
}
FRIEND_TEST(ConditionTimerTest, TestTimer_Inital_False);
@@ -80,4 +104,4 @@ public:
} // namespace statsd
} // namespace os
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index 3bf4e636be89..5a6b8cf334eb 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -68,9 +68,9 @@ public:
// index: the new index of this tracker in allConditionProtos and allConditionTrackers.
// allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
// need to call onConfigUpdated() on child conditions)
- // [atomMatchingTrackerMap]: map of atom matcher id to index after the config update
- // [conditionTrackerMap]: map of condition tracker id to index after the config update.
- // returns whether or not the update is successful
+ // atomMatchingTrackerMap: map of atom matcher id to index after the config update.
+ // conditionTrackerMap: map of condition tracker id to index after the config update.
+ // returns whether or not the update is successful.
virtual bool onConfigUpdated(const std::vector<Predicate>& allConditionProtos, const int index,
const std::vector<sp<ConditionTracker>>& allConditionTrackers,
const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index cd9c4e5b947b..03b178a989eb 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -76,7 +76,7 @@ int main(int /*argc*/, char** /*argv*/) {
ABinderProcess_startThreadPool();
std::shared_ptr<LogEventQueue> eventQueue =
- std::make_shared<LogEventQueue>(2000 /*buffer limit. Buffer is NOT pre-allocated*/);
+ std::make_shared<LogEventQueue>(4000 /*buffer limit. Buffer is NOT pre-allocated*/);
// Create the service
gStatsService = SharedRefBase::make<StatsService>(looper, eventQueue);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 3dbb6ed47ff8..a8ef54a335c4 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -24,6 +24,7 @@
#include <stdlib.h>
#include "guardrail/StatsdStats.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
#include "stats_log_util.h"
#include "stats_util.h"
@@ -122,6 +123,47 @@ CountMetricProducer::~CountMetricProducer() {
VLOG("~CountMetricProducer() called");
}
+bool CountMetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!MetricProducer::onConfigUpdatedLocked(
+ config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return false;
+ }
+
+ const CountMetric& metric = config.count_metric(configIndex);
+ int trackerIndex;
+ // Update appropriate indices, specifically mConditionIndex and MetricsManager maps.
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return false;
+ }
+
+ if (metric.has_condition() &&
+ !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, mConditionTrackerIndex,
+ conditionToMetricMap)) {
+ return false;
+ }
+ return true;
+}
+
void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
const HashableDimensionKey& primaryKey,
const FieldValue& oldState, const FieldValue& newState) {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 6b2f2ca61ecc..0769ac459b8c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -97,6 +97,22 @@ private:
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
const int64_t& nextBucketStartTimeNs) override;
+ bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) override;
+
std::unordered_map<MetricDimensionKey, std::vector<CountBucket>> mPastBuckets;
// The current bucket (may be a partial bucket).
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index dfe4559b05fb..ca302c0e71fb 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -18,12 +18,14 @@
#include "Log.h"
#include "EventMetricProducer.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
#include <limits.h>
#include <stdlib.h>
+#include "metrics/parsing_utils/metrics_manager_util.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
@@ -82,6 +84,47 @@ EventMetricProducer::~EventMetricProducer() {
VLOG("~EventMetricProducer() called");
}
+bool EventMetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!MetricProducer::onConfigUpdatedLocked(
+ config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return false;
+ }
+
+ const EventMetric& metric = config.event_metric(configIndex);
+ int trackerIndex;
+ // Update appropriate indices, specifically mConditionIndex and MetricsManager maps.
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return false;
+ }
+
+ if (metric.has_condition() &&
+ !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, mConditionTrackerIndex,
+ conditionToMetricMap)) {
+ return false;
+ }
+ return true;
+}
+
void EventMetricProducer::dropDataLocked(const int64_t dropTimeNs) {
mProto->clear();
StatsdStats::getInstance().noteBucketDropped(mMetricId);
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index e828dddcbb18..3347d7b6aab5 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -69,6 +69,22 @@ private:
// Internal interface to handle sliced condition change.
void onSlicedConditionMayChangeLocked(bool overallCondition, const int64_t eventTime) override;
+ bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) override;
+
void dropDataLocked(const int64_t dropTimeNs) override;
// Internal function to calculate the current used bytes.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 9dda248a6d1f..2a37b587fbce 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -17,9 +17,11 @@
#define DEBUG false // STOPSHIP if true
#include "Log.h"
-#include "../guardrail/StatsdStats.h"
#include "GaugeMetricProducer.h"
-#include "../stats_log_util.h"
+
+#include "guardrail/StatsdStats.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
+#include "stats_log_util.h"
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
@@ -154,6 +156,58 @@ GaugeMetricProducer::~GaugeMetricProducer() {
}
}
+bool GaugeMetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!MetricProducer::onConfigUpdatedLocked(
+ config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, metricToActivationMap,
+ trackerToMetricMap, conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return false;
+ }
+
+ const GaugeMetric& metric = config.gauge_metric(configIndex);
+ // Update appropriate indices: mWhatMatcherIndex, mConditionIndex and MetricsManager maps.
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, /*enforceOneAtom=*/false,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ trackerToMetricMap, mWhatMatcherIndex)) {
+ return false;
+ }
+
+ // Need to update maps since the index changed, but mTriggerAtomId will not change.
+ int triggerTrackerIndex;
+ if (metric.has_trigger_event() &&
+ !handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex,
+ /*enforceOneAtom=*/true, allAtomMatchingTrackers,
+ newAtomMatchingTrackerMap, trackerToMetricMap,
+ triggerTrackerIndex)) {
+ return false;
+ }
+
+ if (metric.has_condition() &&
+ !handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, mConditionTrackerIndex,
+ conditionToMetricMap)) {
+ return false;
+ }
+ sp<EventMatcherWizard> tmpEventWizard = mEventMatcherWizard;
+ mEventMatcherWizard = matcherWizard;
+ return true;
+}
+
void GaugeMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
if (mCurrentSlicedBucket == nullptr ||
mCurrentSlicedBucket->size() == 0) {
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index e933d4b19716..9bdaac96c9ef 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -53,8 +53,8 @@ typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>>
// This gauge metric producer first register the puller to automatically pull the gauge at the
// beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise
// proactively pull the gauge when the condition is changed to be true. Therefore, the gauge metric
-// producer always reports the guage at the earliest time of the bucket when the condition is met.
-class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
+// producer always reports the gauge at the earliest time of the bucket when the condition is met.
+class GaugeMetricProducer : public MetricProducer, public virtual PullDataReceiver {
public:
GaugeMetricProducer(
const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
@@ -142,7 +142,23 @@ private:
void pullAndMatchEventsLocked(const int64_t timestampNs);
- const int mWhatMatcherIndex;
+ bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) override;
+
+ int mWhatMatcherIndex;
sp<EventMatcherWizard> mEventMatcherWizard;
@@ -209,6 +225,8 @@ private:
FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
+
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index c1c1d20f00e2..95a7d40ea9a9 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -20,6 +20,7 @@
#include "MetricProducer.h"
#include "../guardrail/StatsdStats.h"
+#include "metrics/parsing_utils/metrics_manager_util.h"
#include "state/StateTracker.h"
using android::util::FIELD_COUNT_REPEATED;
@@ -72,6 +73,37 @@ MetricProducer::MetricProducer(
mStateGroupMap(stateGroupMap) {
}
+bool MetricProducer::onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ sp<ConditionWizard> tmpWizard = mWizard;
+ mWizard = wizard;
+
+ unordered_map<int, shared_ptr<Activation>> newEventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> newEventDeactivationMap;
+ if (!handleMetricActivationOnConfigUpdate(
+ config, mMetricId, metricIndex, metricToActivationMap, oldAtomMatchingTrackerMap,
+ newAtomMatchingTrackerMap, mEventActivationMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, newEventActivationMap,
+ newEventDeactivationMap)) {
+ return false;
+ }
+ mEventActivationMap = newEventActivationMap;
+ mEventDeactivationMap = newEventDeactivationMap;
+ return true;
+}
+
void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
if (!mIsActive) {
return;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index bb590aac54d6..18e62d28ba46 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -26,6 +26,7 @@
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "config/ConfigKey.h"
+#include "matchers/EventMatcherWizard.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
#include "state/StateListener.h"
@@ -151,6 +152,33 @@ public:
return conditionIndex >= 0 ? initialConditionCache[conditionIndex] : ConditionState::kTrue;
}
+ // Update appropriate state on config updates. Primarily, all indices need to be updated.
+ // This metric and all of its dependencies are guaranteed to be preserved across the update.
+ // This function also updates several maps used by metricsManager.
+ bool onConfigUpdated(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return onConfigUpdatedLocked(config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap,
+ matcherWizard, allConditionTrackers, conditionTrackerMap,
+ wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ };
+
/**
* Force a partial bucket split on app upgrade
*/
@@ -209,6 +237,25 @@ public:
dumpLatency, str_set, protoOutput);
}
+ // Update appropriate state on config updates. Primarily, all indices need to be updated.
+ // This metric and all of its dependencies are guaranteed to be preserved across the update.
+ // This function also updates several maps used by metricsManager.
+ virtual bool onConfigUpdatedLocked(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
void clearPastBuckets(const int64_t dumpTimeNs) {
std::lock_guard<std::mutex> lock(mMutex);
return clearPastBucketsLocked(dumpTimeNs);
@@ -517,6 +564,12 @@ protected:
FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(MetricsManagerTest, TestInitialConditions);
+
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricActivations);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateCountMetrics);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateEventMetrics);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateGaugeMetrics);
+ FRIEND_TEST(ConfigUpdateTest, TestUpdateMetricsMultipleTypes);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 39806890c42d..ab0d286d6b29 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -206,18 +206,33 @@ bool MetricsManager::updateConfig(const StatsdConfig& config, const int64_t time
unordered_map<int64_t, int> newAtomMatchingTrackerMap;
vector<sp<ConditionTracker>> newConditionTrackers;
unordered_map<int64_t, int> newConditionTrackerMap;
+ map<int64_t, uint64_t> newStateProtoHashes;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int64_t, int> newMetricProducerMap;
mTagIds.clear();
+ mConditionToMetricMap.clear();
+ mTrackerToMetricMap.clear();
mTrackerToConditionMap.clear();
+ mActivationAtomTrackerToMetricMap.clear();
+ mDeactivationAtomTrackerToMetricMap.clear();
+ mMetricIndexesWithActivation.clear();
+ mNoReportMetricIds.clear();
mConfigValid = updateStatsdConfig(
mConfigKey, config, mUidMap, mPullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
timeBaseNs, currentTimeNs, mAllAtomMatchingTrackers, mAtomMatchingTrackerMap,
- mAllConditionTrackers, mConditionTrackerMap, mTagIds, newAtomMatchingTrackers,
- newAtomMatchingTrackerMap, newConditionTrackers, newConditionTrackerMap,
- mTrackerToConditionMap);
+ mAllConditionTrackers, mConditionTrackerMap, mAllMetricProducers, mMetricProducerMap,
+ mStateProtoHashes, mTagIds, newAtomMatchingTrackers, newAtomMatchingTrackerMap,
+ newConditionTrackers, newConditionTrackerMap, newMetricProducers, newMetricProducerMap,
+ mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
+ mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
+ mMetricIndexesWithActivation, newStateProtoHashes, mNoReportMetricIds);
mAllAtomMatchingTrackers = newAtomMatchingTrackers;
mAtomMatchingTrackerMap = newAtomMatchingTrackerMap;
mAllConditionTrackers = newConditionTrackers;
mConditionTrackerMap = newConditionTrackerMap;
+ mAllMetricProducers = newMetricProducers;
+ mMetricProducerMap = newMetricProducerMap;
+ mStateProtoHashes = newStateProtoHashes;
return mConfigValid;
}
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 39ae9a47f2bf..3d57cfe318c5 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -301,7 +301,6 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_BUCKET_DROP_REASON, dropEvent.reason);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_DROP_TIME,
(long long)(NanoToMillis(dropEvent.dropTimeNs)));
- ;
protoOutput->end(dropEventToken);
}
protoOutput->end(wrapperToken);
@@ -346,8 +345,11 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs,
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_NUM,
(long long)(getBucketNumFromEndTimeNs(bucket.mBucketEndNs)));
}
- // only write the condition timer value if the metric has a condition.
- if (mConditionTrackerIndex >= 0) {
+ // We only write the condition timer value if the metric has a
+ // condition and/or is sliced by state.
+ // If the metric is sliced by state, the condition timer value is
+ // also sliced by state to reflect time spent in that state.
+ if (mConditionTrackerIndex >= 0 || !mSlicedStateAtoms.empty()) {
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_TRUE_NS,
(long long)bucket.mConditionTrueNs);
}
@@ -454,6 +456,8 @@ void ValueMetricProducer::onActiveStateChangedLocked(const int64_t& eventTimeNs)
// Let condition timer know of new active state.
mConditionTimer.onConditionChanged(mIsActive, eventTimeNs);
+
+ updateCurrentSlicedBucketConditionTimers(mIsActive, eventTimeNs);
}
void ValueMetricProducer::onConditionChangedLocked(const bool condition,
@@ -476,6 +480,8 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition,
invalidateCurrentBucket(eventTimeNs, BucketDropReason::EVENT_IN_WRONG_BUCKET);
mCondition = ConditionState::kUnknown;
mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
+
+ updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs);
return;
}
@@ -517,6 +523,29 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition,
flushIfNeededLocked(eventTimeNs);
mConditionTimer.onConditionChanged(mCondition, eventTimeNs);
+
+ updateCurrentSlicedBucketConditionTimers(mCondition, eventTimeNs);
+}
+
+void ValueMetricProducer::updateCurrentSlicedBucketConditionTimers(bool newCondition,
+ int64_t eventTimeNs) {
+ if (mSlicedStateAtoms.empty()) {
+ return;
+ }
+
+ // Utilize the current state key of each DimensionsInWhat key to determine
+ // which condition timers to update.
+ //
+ // Assumes that the MetricDimensionKey exists in `mCurrentSlicedBucket`.
+ bool inPulledData;
+ for (const auto& [dimensionInWhatKey, dimensionInWhatInfo] : mCurrentBaseInfo) {
+ // If the new condition is true, turn ON the condition timer only if
+ // the DimensionInWhat key was present in the pulled data.
+ inPulledData = dimensionInWhatInfo.hasCurrentState;
+ mCurrentSlicedBucket[MetricDimensionKey(dimensionInWhatKey,
+ dimensionInWhatInfo.currentState)]
+ .conditionTimer.onConditionChanged(newCondition && inPulledData, eventTimeNs);
+ }
}
void ValueMetricProducer::prepareFirstBucketLocked() {
@@ -618,8 +647,8 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log
// 2. A superset of the current mStateChangePrimaryKey
// was not found in the new pulled data (i.e. not in mMatchedDimensionInWhatKeys)
// then we need to reset the base.
- for (auto& slice : mCurrentSlicedBucket) {
- const auto& whatKey = slice.first.getDimensionKeyInWhat();
+ for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) {
+ const auto& whatKey = metricDimensionKey.getDimensionKeyInWhat();
bool presentInPulledData =
mMatchedMetricDimensionKeys.find(whatKey) != mMatchedMetricDimensionKeys.end();
if (!presentInPulledData && whatKey.contains(mStateChangePrimaryKey.second)) {
@@ -627,6 +656,12 @@ void ValueMetricProducer::accumulateEvents(const std::vector<std::shared_ptr<Log
for (auto& baseInfo : it->second.baseInfos) {
baseInfo.hasBase = false;
}
+ // Set to false when DimensionInWhat key is not present in a pull.
+ // Used in onMatchedLogEventInternalLocked() to ensure the condition
+ // timer is turned on the next pull when data is present.
+ it->second.hasCurrentState = false;
+ // Turn OFF condition timer for keys not present in pulled data.
+ currentValueBucket.conditionTimer.onConditionChanged(false, eventElapsedTimeNs);
}
}
mMatchedMetricDimensionKeys.clear();
@@ -789,21 +824,26 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(
return;
}
- DimensionsInWhatInfo& dimensionsInWhatInfo = mCurrentBaseInfo[whatKey];
+ const auto& returnVal =
+ mCurrentBaseInfo.emplace(whatKey, DimensionsInWhatInfo(getUnknownStateKey()));
+ DimensionsInWhatInfo& dimensionsInWhatInfo = returnVal.first->second;
+ const HashableDimensionKey oldStateKey = dimensionsInWhatInfo.currentState;
vector<BaseInfo>& baseInfos = dimensionsInWhatInfo.baseInfos;
if (baseInfos.size() < mFieldMatchers.size()) {
VLOG("Resizing number of intervals to %d", (int)mFieldMatchers.size());
baseInfos.resize(mFieldMatchers.size());
}
+ // Ensure we turn on the condition timer in the case where dimensions
+ // were missing on a previous pull due to a state change.
+ bool stateChange = oldStateKey != stateKey;
if (!dimensionsInWhatInfo.hasCurrentState) {
- dimensionsInWhatInfo.currentState = getUnknownStateKey();
+ stateChange = true;
dimensionsInWhatInfo.hasCurrentState = true;
}
// We need to get the intervals stored with the previous state key so we can
// close these value intervals.
- const auto oldStateKey = dimensionsInWhatInfo.currentState;
vector<Interval>& intervals =
mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)].intervals;
if (intervals.size() < mFieldMatchers.size()) {
@@ -916,6 +956,17 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(
interval.sampleSize += 1;
}
+ // State change.
+ if (!mSlicedStateAtoms.empty() && stateChange) {
+ // Turn OFF the condition timer for the previous state key.
+ mCurrentSlicedBucket[MetricDimensionKey(whatKey, oldStateKey)]
+ .conditionTimer.onConditionChanged(false, eventTimeNs);
+
+ // Turn ON the condition timer for the new state key.
+ mCurrentSlicedBucket[MetricDimensionKey(whatKey, stateKey)]
+ .conditionTimer.onConditionChanged(true, eventTimeNs);
+ }
+
// Only trigger the tracker if all intervals are correct and we have not skipped the bucket due
// to MULTIPLE_BUCKETS_SKIPPED.
if (useAnomalyDetection && !multipleBucketsSkipped(calcBucketsForwardCount(eventTimeNs))) {
@@ -990,12 +1041,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
if (!mCurrentBucketIsSkipped) {
bool bucketHasData = false;
// The current bucket is large enough to keep.
- for (const auto& slice : mCurrentSlicedBucket) {
- PastValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second.intervals);
- bucket.mConditionTrueNs = conditionTrueDuration;
+ for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) {
+ PastValueBucket bucket =
+ buildPartialBucket(bucketEndTime, currentValueBucket.intervals);
+ if (!mSlicedStateAtoms.empty()) {
+ bucket.mConditionTrueNs =
+ currentValueBucket.conditionTimer.newBucketStart(bucketEndTime);
+ } else {
+ bucket.mConditionTrueNs = conditionTrueDuration;
+ }
// it will auto create new vector of ValuebucketInfo if the key is not found.
if (bucket.valueIndex.size() > 0) {
- auto& bucketList = mPastBuckets[slice.first];
+ auto& bucketList = mPastBuckets[metricDimensionKey];
bucketList.push_back(bucket);
bucketHasData = true;
}
@@ -1023,11 +1080,18 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs,
buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA));
mSkippedBuckets.emplace_back(bucketInGap);
}
-
appendToFullBucket(eventTimeNs > fullBucketEndTimeNs);
initCurrentSlicedBucket(nextBucketStartTimeNs);
// Update the condition timer again, in case we skipped buckets.
mConditionTimer.newBucketStart(nextBucketStartTimeNs);
+
+ // NOTE: Update the condition timers in `mCurrentSlicedBucket` only when slicing
+ // by state. Otherwise, the "global" condition timer will be used.
+ if (!mSlicedStateAtoms.empty()) {
+ for (auto& [metricDimensionKey, currentValueBucket] : mCurrentSlicedBucket) {
+ currentValueBucket.conditionTimer.newBucketStart(nextBucketStartTimeNs);
+ }
+ }
mCurrentBucketNum += numBucketsForward;
}
@@ -1069,6 +1133,17 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs)
interval.seenNewData = false;
}
+ if (obsolete && !mSlicedStateAtoms.empty()) {
+ // When slicing by state, only delete the MetricDimensionKey when the
+ // state key in the MetricDimensionKey is not the current state key.
+ const HashableDimensionKey& dimensionInWhatKey = it->first.getDimensionKeyInWhat();
+ const auto& currentBaseInfoItr = mCurrentBaseInfo.find(dimensionInWhatKey);
+
+ if ((currentBaseInfoItr != mCurrentBaseInfo.end()) &&
+ (it->first.getStateValuesKey() == currentBaseInfoItr->second.currentState)) {
+ obsolete = false;
+ }
+ }
if (obsolete) {
it = mCurrentSlicedBucket.erase(it);
} else {
@@ -1104,7 +1179,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) {
// Accumulate partial buckets with current value and then send to anomaly tracker.
if (mCurrentFullBucket.size() > 0) {
for (const auto& slice : mCurrentSlicedBucket) {
- if (hitFullBucketGuardRailLocked(slice.first)) {
+ if (hitFullBucketGuardRailLocked(slice.first) || slice.second.intervals.empty()) {
continue;
}
// TODO: fix this when anomaly can accept double values
@@ -1125,7 +1200,7 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) {
// Skip aggregating the partial buckets since there's no previous partial bucket.
for (const auto& slice : mCurrentSlicedBucket) {
for (auto& tracker : mAnomalyTrackers) {
- if (tracker != nullptr) {
+ if (tracker != nullptr && !slice.second.intervals.empty()) {
// TODO: fix this when anomaly can accept double values
auto& interval = slice.second.intervals[0];
if (interval.hasValue) {
@@ -1139,10 +1214,12 @@ void ValueMetricProducer::appendToFullBucket(const bool isFullBucketReached) {
} else {
// Accumulate partial bucket.
for (const auto& slice : mCurrentSlicedBucket) {
- // TODO: fix this when anomaly can accept double values
- auto& interval = slice.second.intervals[0];
- if (interval.hasValue) {
- mCurrentFullBucket[slice.first] += interval.value.long_value;
+ if (!slice.second.intervals.empty()) {
+ // TODO: fix this when anomaly can accept double values
+ auto& interval = slice.second.intervals[0];
+ if (interval.hasValue) {
+ mCurrentFullBucket[slice.first] += interval.value.long_value;
+ }
}
}
}
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 4b2599bdb517..67de214e655c 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -193,8 +193,14 @@ private:
// Internal state of an ongoing aggregation bucket.
typedef struct CurrentValueBucket {
+ // If the `MetricDimensionKey` state key is the current state key, then
+ // the condition timer will be updated later (e.g. condition/state/active
+ // state change) with the correct condition and time.
+ CurrentValueBucket() : intervals(), conditionTimer(ConditionTimer(false, 0)) {}
// Value information for each value field of the metric.
std::vector<Interval> intervals;
+ // Tracks how long the condition is true.
+ ConditionTimer conditionTimer;
} CurrentValueBucket;
// Holds base information for diffing values from one value field.
@@ -206,7 +212,10 @@ private:
} BaseInfo;
// State key and base information for a specific DimensionsInWhat key.
- typedef struct {
+ typedef struct DimensionsInWhatInfo {
+ DimensionsInWhatInfo(const HashableDimensionKey& stateKey)
+ : baseInfos(), currentState(stateKey), hasCurrentState(false) {
+ }
std::vector<BaseInfo> baseInfos;
// Last seen state value(s).
HashableDimensionKey currentState;
@@ -252,6 +261,10 @@ private:
// Reset diff base and mHasGlobalBase
void resetBase();
+ // Updates the condition timers in the current sliced bucket when there is a
+ // condition change or an active state change.
+ void updateCurrentSlicedBucketConditionTimers(bool newCondition, int64_t eventTimeNs);
+
static const size_t kBucketSize = sizeof(PastValueBucket{});
const size_t mDimensionSoftLimit;
@@ -337,6 +350,11 @@ private:
FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase);
FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures);
+ FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions);
+ FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange);
+ FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange);
+ FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket);
+ FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary);
FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenOneConditionFailed);
FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenInitialPullFailed);
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
index bd60b6bfcb8e..d32f5a947d12 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.cpp
@@ -21,8 +21,11 @@
#include "external/StatsPullerManager.h"
#include "hash.h"
+#include "matchers/EventMatcherWizard.h"
#include "metrics_manager_util.h"
+using google::protobuf::MessageLite;
+
namespace android {
namespace os {
namespace statsd {
@@ -394,6 +397,402 @@ bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
return true;
}
+// Returns true if any matchers in the metric activation were replaced.
+bool metricActivationDepsChange(const StatsdConfig& config,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const int64_t metricId, const set<int64_t>& replacedMatchers) {
+ const auto& metricActivationIt = metricToActivationMap.find(metricId);
+ if (metricActivationIt == metricToActivationMap.end()) {
+ return false;
+ }
+ const MetricActivation& metricActivation = config.metric_activation(metricActivationIt->second);
+ for (int i = 0; i < metricActivation.event_activation_size(); i++) {
+ const EventActivation& activation = metricActivation.event_activation(i);
+ if (replacedMatchers.find(activation.atom_matcher_id()) != replacedMatchers.end()) {
+ return true;
+ }
+ if (activation.has_deactivation_atom_matcher_id()) {
+ if (replacedMatchers.find(activation.deactivation_atom_matcher_id()) !=
+ replacedMatchers.end()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool determineMetricUpdateStatus(
+ const StatsdConfig& config, const MessageLite& metric, const int64_t metricId,
+ const MetricType metricType, const set<int64_t>& matcherDependencies,
+ const set<int64_t>& conditionDependencies,
+ const ::google::protobuf::RepeatedField<int64_t>& stateDependencies,
+ const ::google::protobuf::RepeatedPtrField<MetricConditionLink>& conditionLinks,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const set<int64_t>& replacedMatchers, const set<int64_t>& replacedConditions,
+ const set<int64_t>& replacedStates, UpdateStatus& updateStatus) {
+ // Check if new metric
+ const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId);
+ if (oldMetricProducerIt == oldMetricProducerMap.end()) {
+ updateStatus = UPDATE_NEW;
+ return true;
+ }
+
+ // This is an existing metric, check if it has changed.
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metricId, metricToActivationMap, metricHash)) {
+ return false;
+ }
+ const sp<MetricProducer> oldMetricProducer = oldMetricProducers[oldMetricProducerIt->second];
+ if (oldMetricProducer->getMetricType() != metricType ||
+ oldMetricProducer->getProtoHash() != metricHash) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ // Take intersections of the matchers/predicates/states that the metric
+ // depends on with those that have been replaced. If a metric depends on any
+ // replaced component, it too must be replaced.
+ set<int64_t> intersection;
+ set_intersection(matcherDependencies.begin(), matcherDependencies.end(),
+ replacedMatchers.begin(), replacedMatchers.end(),
+ inserter(intersection, intersection.begin()));
+ if (intersection.size() > 0) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+ set_intersection(conditionDependencies.begin(), conditionDependencies.end(),
+ replacedConditions.begin(), replacedConditions.end(),
+ inserter(intersection, intersection.begin()));
+ if (intersection.size() > 0) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+ set_intersection(stateDependencies.begin(), stateDependencies.end(), replacedStates.begin(),
+ replacedStates.end(), inserter(intersection, intersection.begin()));
+ if (intersection.size() > 0) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ for (const auto& metricConditionLink : conditionLinks) {
+ if (replacedConditions.find(metricConditionLink.condition()) != replacedConditions.end()) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+ }
+
+ if (metricActivationDepsChange(config, metricToActivationMap, metricId, replacedMatchers)) {
+ updateStatus = UPDATE_REPLACE;
+ return true;
+ }
+
+ updateStatus = UPDATE_PRESERVE;
+ return true;
+}
+
+bool determineAllMetricUpdateStatuses(const StatsdConfig& config,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const set<int64_t>& replacedMatchers,
+ const set<int64_t>& replacedConditions,
+ const set<int64_t>& replacedStates,
+ vector<UpdateStatus>& metricsToUpdate) {
+ int metricIndex = 0;
+ for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) {
+ const CountMetric& metric = config.count_metric(i);
+ set<int64_t> conditionDependencies;
+ if (metric.has_condition()) {
+ conditionDependencies.insert(metric.condition());
+ }
+ if (!determineMetricUpdateStatus(
+ config, metric, metric.id(), METRIC_TYPE_COUNT, {metric.what()},
+ conditionDependencies, metric.slice_by_state(), metric.links(),
+ oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, replacedStates,
+ metricsToUpdate[metricIndex])) {
+ return false;
+ }
+ }
+ for (int i = 0; i < config.duration_metric_size(); i++, metricIndex++) {
+ const DurationMetric& metric = config.duration_metric(i);
+ set<int64_t> conditionDependencies({metric.what()});
+ if (metric.has_condition()) {
+ conditionDependencies.insert(metric.condition());
+ }
+ if (!determineMetricUpdateStatus(
+ config, metric, metric.id(), METRIC_TYPE_DURATION, /*matcherDependencies=*/{},
+ conditionDependencies, metric.slice_by_state(), metric.links(),
+ oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, replacedStates,
+ metricsToUpdate[metricIndex])) {
+ return false;
+ }
+ }
+ for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
+ const EventMetric& metric = config.event_metric(i);
+ set<int64_t> conditionDependencies;
+ if (metric.has_condition()) {
+ conditionDependencies.insert(metric.condition());
+ }
+ if (!determineMetricUpdateStatus(
+ config, metric, metric.id(), METRIC_TYPE_EVENT, {metric.what()},
+ conditionDependencies, ::google::protobuf::RepeatedField<int64_t>(),
+ metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, replacedStates,
+ metricsToUpdate[metricIndex])) {
+ return false;
+ }
+ }
+
+ for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) {
+ const GaugeMetric& metric = config.gauge_metric(i);
+ set<int64_t> conditionDependencies;
+ if (metric.has_condition()) {
+ conditionDependencies.insert(metric.condition());
+ }
+ set<int64_t> matcherDependencies({metric.what()});
+ if (metric.has_trigger_event()) {
+ matcherDependencies.insert(metric.trigger_event());
+ }
+ if (!determineMetricUpdateStatus(
+ config, metric, metric.id(), METRIC_TYPE_GAUGE, matcherDependencies,
+ conditionDependencies, ::google::protobuf::RepeatedField<int64_t>(),
+ metric.links(), oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ replacedMatchers, replacedConditions, replacedStates,
+ metricsToUpdate[metricIndex])) {
+ return false;
+ }
+ }
+ // TODO: determine update status for value metrics.
+ return true;
+}
+
+// Called when a metric is preserved during a config update. Finds the metric in oldMetricProducers
+// and calls onConfigUpdated to update all indices.
+optional<sp<MetricProducer>> updateMetric(
+ const StatsdConfig& config, const int configIndex, const int metricIndex,
+ const int64_t metricId, const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ const auto& oldMetricProducerIt = oldMetricProducerMap.find(metricId);
+ if (oldMetricProducerIt == oldMetricProducerMap.end()) {
+ ALOGE("Could not find Metric %lld in the previous config, but expected it "
+ "to be there",
+ (long long)metricId);
+ return nullopt;
+ }
+ const int oldIndex = oldMetricProducerIt->second;
+ sp<MetricProducer> producer = oldMetricProducers[oldIndex];
+ if (!producer->onConfigUpdated(config, configIndex, metricIndex, allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap,
+ matcherWizard, allConditionTrackers, conditionTrackerMap, wizard,
+ metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
+ activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
+ return nullopt;
+ }
+ return {producer};
+}
+
+bool updateMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const set<int64_t>& replacedMatchers,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const set<int64_t>& replacedConditions,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const vector<ConditionState>& initialConditionCache,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ const set<int64_t>& replacedStates,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ unordered_map<int64_t, int>& newMetricProducerMap,
+ vector<sp<MetricProducer>>& newMetricProducers,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ set<int64_t>& noReportMetricIds,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
+ sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchingTrackers);
+ const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
+ config.event_metric_size() + config.gauge_metric_size() +
+ config.value_metric_size();
+ newMetricProducers.reserve(allMetricsCount);
+
+ // Construct map from metric id to metric activation index. The map will be used to determine
+ // the metric activation corresponding to a metric.
+ unordered_map<int64_t, int> metricToActivationMap;
+ for (int i = 0; i < config.metric_activation_size(); i++) {
+ const MetricActivation& metricActivation = config.metric_activation(i);
+ int64_t metricId = metricActivation.metric_id();
+ if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) {
+ ALOGE("Metric %lld has multiple MetricActivations", (long long)metricId);
+ return false;
+ }
+ metricToActivationMap.insert({metricId, i});
+ }
+
+ vector<UpdateStatus> metricsToUpdate(allMetricsCount, UPDATE_UNKNOWN);
+ if (!determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap, replacedMatchers,
+ replacedConditions, replacedStates, metricsToUpdate)) {
+ return false;
+ }
+
+ // Now, perform the update. Must iterate the metric types in the same order
+ int metricIndex = 0;
+ for (int i = 0; i < config.count_metric_size(); i++, metricIndex++) {
+ const CountMetric& metric = config.count_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
+ optional<sp<MetricProducer>> producer;
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ producer = updateMetric(
+ config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ case UPDATE_REPLACE:
+ case UPDATE_NEW: {
+ producer = createCountMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, currentTimeNs, metric, metricIndex,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
+ allStateGroupMaps, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ if (!producer) {
+ return false;
+ }
+ newMetricProducers.push_back(producer.value());
+ }
+ for (int i = 0; i < config.event_metric_size(); i++, metricIndex++) {
+ newMetricProducerMap[config.event_metric(i).id()] = metricIndex;
+ const EventMetric& metric = config.event_metric(i);
+ optional<sp<MetricProducer>> producer;
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ producer = updateMetric(
+ config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ case UPDATE_REPLACE:
+ case UPDATE_NEW: {
+ producer = createEventMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, metric, metricIndex, allAtomMatchingTrackers,
+ newAtomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
+ initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ if (!producer) {
+ return false;
+ }
+ newMetricProducers.push_back(producer.value());
+ }
+ for (int i = 0; i < config.gauge_metric_size(); i++, metricIndex++) {
+ const GaugeMetric& metric = config.gauge_metric(i);
+ newMetricProducerMap[metric.id()] = metricIndex;
+ optional<sp<MetricProducer>> producer;
+ switch (metricsToUpdate[metricIndex]) {
+ case UPDATE_PRESERVE: {
+ producer = updateMetric(
+ config, i, metricIndex, metric.id(), allAtomMatchingTrackers,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, matcherWizard,
+ allConditionTrackers, conditionTrackerMap, wizard, oldMetricProducerMap,
+ oldMetricProducers, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ break;
+ }
+ case UPDATE_REPLACE:
+ case UPDATE_NEW: {
+ producer = createGaugeMetricProducerAndUpdateMetadata(
+ key, config, timeBaseNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, newAtomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
+ metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation);
+ break;
+ }
+ default: {
+ ALOGE("Metric \"%lld\" update state is unknown. This should never happen",
+ (long long)metric.id());
+ return false;
+ }
+ }
+ if (!producer) {
+ return false;
+ }
+ newMetricProducers.push_back(producer.value());
+ }
+ // TODO: perform update for value, duration metric.
+
+ const set<int> atomsAllowedFromAnyUid(config.whitelisted_atom_ids().begin(),
+ config.whitelisted_atom_ids().end());
+ for (int i = 0; i < allMetricsCount; i++) {
+ sp<MetricProducer> producer = newMetricProducers[i];
+ // Register metrics to StateTrackers
+ for (int atomId : producer->getSlicedStateAtoms()) {
+ // Register listener for atoms that use allowed_log_sources.
+ // Using atoms allowed from any uid as a sliced state atom is not allowed.
+ // Redo this check for all metrics in case the atoms allowed from any uid changed.
+ if (atomsAllowedFromAnyUid.find(atomId) != atomsAllowedFromAnyUid.end()) {
+ return false;
+ // Preserved metrics should've already registered.`
+ } else if (metricsToUpdate[i] != UPDATE_PRESERVE) {
+ StateManager::getInstance().registerListener(atomId, producer);
+ }
+ }
+ }
+
+ return true;
+}
+
bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
const sp<StatsPullerManager>& pullerManager,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -403,15 +802,28 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const
const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
const vector<sp<ConditionTracker>>& oldConditionTrackers,
const unordered_map<int64_t, int>& oldConditionTrackerMap,
- set<int>& allTagIds,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const map<int64_t, uint64_t>& oldStateProtoHashes, set<int>& allTagIds,
vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
vector<sp<ConditionTracker>>& newConditionTrackers,
unordered_map<int64_t, int>& newConditionTrackerMap,
- unordered_map<int, vector<int>>& trackerToConditionMap) {
+ vector<sp<MetricProducer>>& newMetricProducers,
+ unordered_map<int64_t, int>& newMetricProducerMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& trackerToConditionMap,
+ unordered_map<int, vector<int>>& activationTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationTrackerToMetricMap,
+ vector<int>& metricsWithActivation,
+ map<int64_t, uint64_t>& newStateProtoHashes,
+ set<int64_t>& noReportMetricIds) {
set<int64_t> replacedMatchers;
set<int64_t> replacedConditions;
vector<ConditionState> conditionCache;
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
if (!updateAtomMatchingTrackers(config, uidMap, oldAtomMatchingTrackerMap,
oldAtomMatchingTrackers, allTagIds, newAtomMatchingTrackerMap,
@@ -430,6 +842,31 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const
}
VLOG("updateConditions succeeded");
+ // Share with metrics_manager_util,
+ if (!initStates(config, stateAtomIdMap, allStateGroupMaps, newStateProtoHashes)) {
+ ALOGE("initStates failed");
+ return false;
+ }
+
+ set<int64_t> replacedStates;
+ for (const auto& [stateId, stateHash] : oldStateProtoHashes) {
+ const auto& it = newStateProtoHashes.find(stateId);
+ if (it != newStateProtoHashes.end() && it->second != stateHash) {
+ replacedStates.insert(stateId);
+ }
+ }
+ if (!updateMetrics(key, config, timeBaseNs, currentTimeNs, pullerManager,
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+ newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps,
+ replacedStates, oldMetricProducerMap, oldMetricProducers,
+ newMetricProducerMap, newMetricProducers, conditionToMetricMap,
+ trackerToMetricMap, noReportMetricIds, activationTrackerToMetricMap,
+ deactivationTrackerToMetricMap, metricsWithActivation)) {
+ ALOGE("initMetricProducers failed");
+ return false;
+ }
+
return true;
}
diff --git a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
index 7ba684a65e88..34d7e9c7de9e 100644
--- a/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
+++ b/cmds/statsd/src/metrics/parsing_utils/config_update_utils.h
@@ -22,6 +22,7 @@
#include "condition/ConditionTracker.h"
#include "external/StatsPullerManager.h"
#include "matchers/AtomMatchingTracker.h"
+#include "metrics/MetricProducer.h"
namespace android {
namespace os {
@@ -112,7 +113,7 @@ bool determineConditionUpdateStatus(const StatsdConfig& config, const int condit
// [trackerToConditionMap]: contains the mapping from the index of an atom matcher
// to indices of condition trackers that use the matcher
// [conditionCache]: stores the current conditions for each ConditionTracker
-// [replacedConditions]: set of matcher ids that have changed and have been replaced
+// [replacedConditions]: set of condition ids that have changed and have been replaced
bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
const std::set<int64_t>& replacedMatchers,
@@ -124,6 +125,70 @@ bool updateConditions(const ConfigKey& key, const StatsdConfig& config,
std::vector<ConditionState>& conditionCache,
std::set<int64_t>& replacedConditions);
+// Function to determine the update status (preserve/replace/new) of all metrics in the config.
+// [config]: the input StatsdConfig
+// [oldMetricProducerMap]: metric id to index mapping in the existing MetricsManager
+// [oldMetricProducers]: stores the existing MetricProducers
+// [metricToActivationMap]: map from metric id to metric activation index
+// [replacedMatchers]: set of replaced matcher ids. metrics using these matchers must be replaced
+// [replacedConditions]: set of replaced conditions. metrics using these conditions must be replaced
+// [replacedStates]: set of replaced state ids. metrics using these states must be replaced
+// output:
+// [metricsToUpdate]: update status of each metric. Will be changed from UPDATE_UNKNOWN
+// Returns whether the function was successful or not.
+bool determineAllMetricUpdateStatuses(const StatsdConfig& config,
+ const unordered_map<int64_t, int>& oldMetricProducerMap,
+ const vector<sp<MetricProducer>>& oldMetricProducers,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const set<int64_t>& replacedMatchers,
+ const set<int64_t>& replacedConditions,
+ const set<int64_t>& replacedStates,
+ vector<UpdateStatus>& metricsToUpdate);
+
+// Update MetricProducers.
+// input:
+// [key]: the config key that this config belongs to
+// [config]: the input config
+// [timeBaseNs]: start time base for all metrics
+// [currentTimeNs]: time of the config update
+// [atomMatchingTrackerMap]: AtomMatchingTracker name to index mapping from previous step.
+// [replacedMatchers]: ids of replaced matchers. Metrics depending on these must also be replaced
+// [allAtomMatchingTrackers]: stores the sp of the atom matchers.
+// [conditionTrackerMap]: condition name to index mapping
+// [replacedConditions]: set of condition ids that have changed and have been replaced
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+// state group ids for all states
+// output:
+// [allMetricProducers]: contains the list of sp to the MetricProducers created.
+// [conditionToMetricMap]: contains the mapping from condition tracker index to
+// the list of MetricProducer index
+// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
+bool updateMetrics(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const std::set<int64_t>& replacedMatchers,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::set<int64_t>& replacedConditions,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::vector<ConditionState>& initialConditionCache,
+ const std::unordered_map<int64_t, int>& stateAtomIdMap,
+ const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps,
+ const std::set<int64_t>& replacedStates,
+ const std::unordered_map<int64_t, int>& oldMetricProducerMap,
+ const std::vector<sp<MetricProducer>>& oldMetricProducers,
+ std::unordered_map<int64_t, int>& newMetricProducerMap,
+ std::vector<sp<MetricProducer>>& newMetricProducers,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::set<int64_t>& noReportMetricIds,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
// Updates the existing MetricsManager from a new StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp<UidMap>& uidMap,
@@ -135,12 +200,24 @@ bool updateStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const
const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
const std::vector<sp<ConditionTracker>>& oldConditionTrackers,
const std::unordered_map<int64_t, int>& oldConditionTrackerMap,
+ const std::vector<sp<MetricProducer>>& oldMetricProducers,
+ const std::unordered_map<int64_t, int>& oldMetricProducerMap,
+ const std::map<int64_t, uint64_t>& oldStateProtoHashes,
std::set<int>& allTagIds,
std::vector<sp<AtomMatchingTracker>>& newAtomMatchingTrackers,
std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
std::vector<sp<ConditionTracker>>& newConditionTrackers,
std::unordered_map<int64_t, int>& newConditionTrackerMap,
- std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+ std::vector<sp<MetricProducer>>& newMetricProducers,
+ std::unordered_map<int64_t, int>& newMetricProducerMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+ std::unordered_map<int, std::vector<int>>& activationTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation,
+ std::map<int64_t, uint64_t>& newStateProtoHashes,
+ std::set<int64_t>& noReportMetricIds);
} // namespace statsd
} // namespace os
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index 3f40c90d515a..34e265c3b2ea 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -61,20 +61,6 @@ bool hasLeafNode(const FieldMatcher& matcher) {
return true;
}
-bool getMetricProtoHash(const MessageLite& metric, const int64_t id, const bool hasActivation,
- const uint64_t activationHash, uint64_t& metricHash) {
- string serializedMetric;
- if (!metric.SerializeToString(&serializedMetric)) {
- ALOGE("Unable to serialize metric %lld", (long long)id);
- return false;
- }
- metricHash = Hash64(serializedMetric);
- if (hasActivation) {
- metricHash = Hash64(to_string(metricHash).append(to_string(activationHash)));
- }
- return true;
-}
-
} // namespace
sp<AtomMatchingTracker> createAtomMatchingTracker(const AtomMatcher& logMatcher, const int index,
@@ -120,43 +106,45 @@ sp<ConditionTracker> createConditionTracker(
}
}
-bool handleMetricWithAtomMatchingTrackers(
- const int64_t what, const int metricIndex, const bool usedForDimension,
- const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
- const unordered_map<int64_t, int>& atomMatchingTrackerMap,
- unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
- auto logTrackerIt = atomMatchingTrackerMap.find(what);
- if (logTrackerIt == atomMatchingTrackerMap.end()) {
- ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)what);
+bool getMetricProtoHash(const StatsdConfig& config, const MessageLite& metric, const int64_t id,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ uint64_t& metricHash) {
+ string serializedMetric;
+ if (!metric.SerializeToString(&serializedMetric)) {
+ ALOGE("Unable to serialize metric %lld", (long long)id);
return false;
}
- if (usedForDimension &&
- allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
- ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
- "the \"what\" can only about one atom type.",
- (long long)what);
- return false;
+ metricHash = Hash64(serializedMetric);
+
+ // Combine with activation hash, if applicable
+ const auto& metricActivationIt = metricToActivationMap.find(id);
+ if (metricActivationIt != metricToActivationMap.end()) {
+ string serializedActivation;
+ const MetricActivation& activation = config.metric_activation(metricActivationIt->second);
+ if (!activation.SerializeToString(&serializedActivation)) {
+ ALOGE("Unable to serialize metric activation for metric %lld", (long long)id);
+ return false;
+ }
+ metricHash = Hash64(to_string(metricHash).append(to_string(Hash64(serializedActivation))));
}
- logTrackerIndex = logTrackerIt->second;
- auto& metric_list = trackerToMetricMap[logTrackerIndex];
- metric_list.push_back(metricIndex);
return true;
}
-bool handlePullMetricTriggerWithAtomMatchingTrackers(
- const int64_t trigger, const int metricIndex,
+bool handleMetricWithAtomMatchingTrackers(
+ const int64_t matcherId, const int metricIndex, const bool enforceOneAtom,
const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
const unordered_map<int64_t, int>& atomMatchingTrackerMap,
- unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
- auto logTrackerIt = atomMatchingTrackerMap.find(trigger);
+ unordered_map<int, vector<int>>& trackerToMetricMap, int& logTrackerIndex) {
+ auto logTrackerIt = atomMatchingTrackerMap.find(matcherId);
if (logTrackerIt == atomMatchingTrackerMap.end()) {
- ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)trigger);
+ ALOGW("cannot find the AtomMatcher \"%lld\" in config", (long long)matcherId);
return false;
}
- if (allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
- ALOGE("AtomMatcher \"%lld\" has more than one tag ids."
- "Trigger can only be one atom type.",
- (long long)trigger);
+ if (enforceOneAtom && allAtomMatchingTrackers[logTrackerIt->second]->getAtomIds().size() > 1) {
+ ALOGE("AtomMatcher \"%lld\" has more than one tag ids. When a metric has dimension, "
+ "the \"what\" can only be about one atom type. trigger_event matchers can also only "
+ "be about one atom type.",
+ (long long)matcherId);
return false;
}
logTrackerIndex = logTrackerIt->second;
@@ -170,8 +158,8 @@ bool handleMetricWithConditions(
const unordered_map<int64_t, int>& conditionTrackerMap,
const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>&
links,
- vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
- unordered_map<int, std::vector<int>>& conditionToMetricMap) {
+ const vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
+ unordered_map<int, vector<int>>& conditionToMetricMap) {
auto condition_it = conditionTrackerMap.find(condition);
if (condition_it == conditionTrackerMap.end()) {
ALOGW("cannot find Predicate \"%lld\" in the config", (long long)condition);
@@ -243,31 +231,21 @@ bool handleMetricWithStateLink(const FieldMatcher& stateMatcher,
bool handleMetricActivation(
const StatsdConfig& config, const int64_t metricId, const int metricIndex,
const unordered_map<int64_t, int>& metricToActivationMap,
- const unordered_map<int64_t, int>& atomMatchingTrackerMap, bool& hasActivation,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
vector<int>& metricsWithActivation,
unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
- unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
- uint64_t& activationHash) {
+ unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap) {
// Check if metric has an associated activation
auto itr = metricToActivationMap.find(metricId);
if (itr == metricToActivationMap.end()) {
- hasActivation = false;
return true;
}
- hasActivation = true;
int activationIndex = itr->second;
const MetricActivation& metricActivation = config.metric_activation(activationIndex);
- string serializedActivation;
- if (!metricActivation.SerializeToString(&serializedActivation)) {
- ALOGE("Unable to serialize metric activation for metric %lld", (long long)metricId);
- return false;
- }
- activationHash = Hash64(serializedActivation);
-
for (int i = 0; i < metricActivation.event_activation_size(); i++) {
const EventActivation& activation = metricActivation.event_activation(i);
@@ -303,6 +281,318 @@ bool handleMetricActivation(
return true;
}
+// Validates a metricActivation and populates state.
+// Fills the new event activation/deactivation maps, preserving the existing activations
+// Returns false if there are errors.
+bool handleMetricActivationOnConfigUpdate(
+ const StatsdConfig& config, const int64_t metricId, const int metricIndex,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const unordered_map<int, shared_ptr<Activation>>& oldEventActivationMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation,
+ unordered_map<int, shared_ptr<Activation>>& newEventActivationMap,
+ unordered_map<int, vector<shared_ptr<Activation>>>& newEventDeactivationMap) {
+ // Check if metric has an associated activation.
+ const auto& itr = metricToActivationMap.find(metricId);
+ if (itr == metricToActivationMap.end()) {
+ return true;
+ }
+
+ int activationIndex = itr->second;
+ const MetricActivation& metricActivation = config.metric_activation(activationIndex);
+
+ for (int i = 0; i < metricActivation.event_activation_size(); i++) {
+ const int64_t activationMatcherId = metricActivation.event_activation(i).atom_matcher_id();
+
+ const auto& newActivationIt = newAtomMatchingTrackerMap.find(activationMatcherId);
+ if (newActivationIt == newAtomMatchingTrackerMap.end()) {
+ ALOGE("Atom matcher not found in new config for event activation.");
+ return false;
+ }
+ int newActivationMatcherIndex = newActivationIt->second;
+
+ // Find the old activation struct and copy it over.
+ const auto& oldActivationIt = oldAtomMatchingTrackerMap.find(activationMatcherId);
+ if (oldActivationIt == oldAtomMatchingTrackerMap.end()) {
+ ALOGE("Atom matcher not found in existing config for event activation.");
+ return false;
+ }
+ int oldActivationMatcherIndex = oldActivationIt->second;
+ const auto& oldEventActivationIt = oldEventActivationMap.find(oldActivationMatcherIndex);
+ if (oldEventActivationIt == oldEventActivationMap.end()) {
+ ALOGE("Could not find existing event activation to update");
+ return false;
+ }
+ newEventActivationMap.emplace(newActivationMatcherIndex, oldEventActivationIt->second);
+ activationAtomTrackerToMetricMap[newActivationMatcherIndex].push_back(metricIndex);
+
+ if (metricActivation.event_activation(i).has_deactivation_atom_matcher_id()) {
+ const int64_t deactivationMatcherId =
+ metricActivation.event_activation(i).deactivation_atom_matcher_id();
+ const auto& newDeactivationIt = newAtomMatchingTrackerMap.find(deactivationMatcherId);
+ if (newDeactivationIt == newAtomMatchingTrackerMap.end()) {
+ ALOGE("Deactivation atom matcher not found in new config for event activation.");
+ return false;
+ }
+ int newDeactivationMatcherIndex = newDeactivationIt->second;
+ newEventDeactivationMap[newDeactivationMatcherIndex].push_back(
+ oldEventActivationIt->second);
+ deactivationAtomTrackerToMetricMap[newDeactivationMatcherIndex].push_back(metricIndex);
+ }
+ }
+
+ metricsWithActivation.push_back(metricIndex);
+ return true;
+}
+
+optional<sp<MetricProducer>> createCountMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!metric.has_id() || !metric.has_what()) {
+ ALOGW("cannot find metric id or \"what\" in CountMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+ metric.has_dimensions_in_what(),
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullopt;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullopt;
+ }
+ } else {
+ if (metric.links_size() > 0) {
+ ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullopt;
+ }
+ }
+
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return nullopt;
+ }
+ } else {
+ if (metric.state_link_size() > 0) {
+ ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state");
+ return nullopt;
+ }
+ }
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+ atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ eventActivationMap, eventDeactivationMap)) {
+ return nullopt;
+ }
+
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ return nullopt;
+ }
+
+ return {new CountMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, timeBaseNs, currentTimeNs, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap)};
+}
+
+optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const EventMetric& metric, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!metric.has_id() || !metric.has_what()) {
+ ALOGW("cannot find the metric name or what in config");
+ return nullopt;
+ }
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullopt;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullopt;
+ }
+ } else {
+ if (metric.links_size() > 0) {
+ ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullopt;
+ }
+ }
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+ atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ eventActivationMap, eventDeactivationMap);
+ if (!success) return nullptr;
+
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ return nullopt;
+ }
+
+ return {new EventMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, timeBaseNs, eventActivationMap,
+ eventDeactivationMap)};
+}
+
+optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const GaugeMetric& metric, const int metricIndex,
+ const vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ vector<sp<ConditionTracker>>& allConditionTrackers,
+ const unordered_map<int64_t, int>& conditionTrackerMap,
+ const vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
+ if (!metric.has_id() || !metric.has_what()) {
+ ALOGW("cannot find metric id or \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
+ return nullopt;
+ }
+
+ if ((!metric.gauge_fields_filter().has_include_all() ||
+ (metric.gauge_fields_filter().include_all() == false)) &&
+ !hasLeafNode(metric.gauge_fields_filter().fields())) {
+ ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
+ return nullopt;
+ }
+ if ((metric.gauge_fields_filter().has_include_all() &&
+ metric.gauge_fields_filter().include_all() == true) &&
+ hasLeafNode(metric.gauge_fields_filter().fields())) {
+ ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
+ return nullopt;
+ }
+
+ int trackerIndex;
+ if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
+ metric.has_dimensions_in_what(),
+ allAtomMatchingTrackers, atomMatchingTrackerMap,
+ trackerToMetricMap, trackerIndex)) {
+ return nullopt;
+ }
+
+ sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
+ // For GaugeMetric atom, it should be simple matcher with one tagId.
+ if (atomMatcher->getAtomIds().size() != 1) {
+ return nullopt;
+ }
+ int atomTagId = *(atomMatcher->getAtomIds().begin());
+ int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
+
+ int triggerTrackerIndex;
+ int triggerAtomId = -1;
+ if (metric.has_trigger_event()) {
+ if (pullTagId == -1) {
+ ALOGW("Pull atom not specified for trigger");
+ return nullopt;
+ }
+ // trigger_event should be used with FIRST_N_SAMPLES
+ if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
+ ALOGW("Gauge Metric with trigger event must have sampling type FIRST_N_SAMPLES");
+ return nullopt;
+ }
+ if (!handleMetricWithAtomMatchingTrackers(metric.trigger_event(), metricIndex,
+ /*enforceOneAtom=*/true, allAtomMatchingTrackers,
+ atomMatchingTrackerMap, trackerToMetricMap,
+ triggerTrackerIndex)) {
+ return nullopt;
+ }
+ sp<AtomMatchingTracker> triggerAtomMatcher =
+ allAtomMatchingTrackers.at(triggerTrackerIndex);
+ triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
+ }
+
+ if (!metric.has_trigger_event() && pullTagId != -1 &&
+ metric.sampling_type() == GaugeMetric::FIRST_N_SAMPLES) {
+ ALOGW("FIRST_N_SAMPLES is only for pushed event or pull_on_trigger");
+ return nullopt;
+ }
+
+ int conditionIndex = -1;
+ if (metric.has_condition()) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
+ return nullopt;
+ }
+ } else {
+ if (metric.links_size() > 0) {
+ ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
+ return nullopt;
+ }
+ }
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ if (!handleMetricActivation(config, metric.id(), metricIndex, metricToActivationMap,
+ atomMatchingTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation,
+ eventActivationMap, eventDeactivationMap)) {
+ return nullopt;
+ }
+
+ uint64_t metricHash;
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
+ return nullopt;
+ }
+
+ return {new GaugeMetricProducer(key, metric, conditionIndex, initialConditionCache, wizard,
+ metricHash, trackerIndex, matcherWizard, pullTagId,
+ triggerAtomId, atomTagId, timeBaseNs, currentTimeNs,
+ pullerManager, eventActivationMap, eventDeactivationMap)};
+}
+
bool initAtomMatchingTrackers(const StatsdConfig& config, const sp<UidMap>& uidMap,
unordered_map<int64_t, int>& atomMatchingTrackerMap,
vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
@@ -448,70 +738,20 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
// Build MetricProducers for each metric defined in config.
// build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
- const CountMetric& metric = config.count_metric(i);
- if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in CountMetric \"%lld\"", (long long)metric.id());
- return false;
- }
-
int metricIndex = allMetricProducers.size();
+ const CountMetric& metric = config.count_metric(i);
metricMap.insert({metric.id(), metricIndex});
- int trackerIndex;
- if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
- metric.has_dimensions_in_what(),
- allAtomMatchingTrackers, atomMatchingTrackerMap,
- trackerToMetricMap, trackerIndex)) {
+ optional<sp<MetricProducer>> producer = createCountMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, currentTimeNs, metric, metricIndex,
+ allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, stateAtomIdMap,
+ allStateGroupMaps, metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation);
+ if (!producer) {
return false;
}
-
- int conditionIndex = -1;
- if (metric.has_condition()) {
- if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
- metric.links(), allConditionTrackers, conditionIndex,
- conditionToMetricMap)) {
- return false;
- }
- } else {
- if (metric.links_size() > 0) {
- ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
- return false;
- }
- }
-
- std::vector<int> slicedStateAtoms;
- unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
- if (metric.slice_by_state_size() > 0) {
- if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
- allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
- return false;
- }
- } else {
- if (metric.state_link_size() > 0) {
- ALOGW("CountMetric has a MetricStateLink but doesn't have a slice_by_state");
- return false;
- }
- }
-
- bool hasActivation = false;
- unordered_map<int, shared_ptr<Activation>> eventActivationMap;
- unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- uint64_t activationHash;
- bool success = handleMetricActivation(
- config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
- hasActivation, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap, activationHash);
- if (!success) return false;
-
- uint64_t metricHash;
- if (!getMetricProtoHash(metric, metric.id(), hasActivation, activationHash, metricHash)) {
- return false;
- }
-
- sp<MetricProducer> countProducer = new CountMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- timeBaseTimeNs, currentTimeNs, eventActivationMap, eventDeactivationMap,
- slicedStateAtoms, stateGroupMap);
- allMetricProducers.push_back(countProducer);
+ allMetricProducers.push_back(producer.value());
}
// build DurationMetricProducer
@@ -608,18 +848,16 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
}
}
- bool hasActivation = false;
unordered_map<int, shared_ptr<Activation>> eventActivationMap;
unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- uint64_t activationHash;
bool success = handleMetricActivation(
config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
- hasActivation, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap, activationHash);
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation, eventActivationMap, eventDeactivationMap);
if (!success) return false;
uint64_t metricHash;
- if (!getMetricProtoHash(metric, metric.id(), hasActivation, activationHash, metricHash)) {
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
return false;
}
@@ -637,52 +875,16 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
int metricIndex = allMetricProducers.size();
const EventMetric& metric = config.event_metric(i);
metricMap.insert({metric.id(), metricIndex});
- if (!metric.has_id() || !metric.has_what()) {
- ALOGW("cannot find the metric name or what in config");
- return false;
- }
- int trackerIndex;
- if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex, false,
- allAtomMatchingTrackers, atomMatchingTrackerMap,
- trackerToMetricMap, trackerIndex)) {
- return false;
- }
-
- int conditionIndex = -1;
- if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
- return false;
- }
- } else {
- if (metric.links_size() > 0) {
- ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
- return false;
- }
- }
-
- bool hasActivation = false;
- unordered_map<int, shared_ptr<Activation>> eventActivationMap;
- unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- uint64_t activationHash;
- bool success = handleMetricActivation(
- config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
- hasActivation, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap, activationHash);
- if (!success) return false;
-
- uint64_t metricHash;
- if (!getMetricProtoHash(metric, metric.id(), hasActivation, activationHash, metricHash)) {
+ optional<sp<MetricProducer>> producer = createEventMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, metric, metricIndex, allAtomMatchingTrackers,
+ atomMatchingTrackerMap, allConditionTrackers, conditionTrackerMap,
+ initialConditionCache, wizard, metricToActivationMap, trackerToMetricMap,
+ conditionToMetricMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation);
+ if (!producer) {
return false;
}
-
- sp<MetricProducer> eventMetric = new EventMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- timeBaseTimeNs, eventActivationMap, eventDeactivationMap);
-
- allMetricProducers.push_back(eventMetric);
+ allMetricProducers.push_back(producer.value());
}
// build ValueMetricProducer
@@ -759,18 +961,16 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
}
}
- bool hasActivation = false;
unordered_map<int, shared_ptr<Activation>> eventActivationMap;
unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- uint64_t activationHash;
bool success = handleMetricActivation(
config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
- hasActivation, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap, activationHash);
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation, eventActivationMap, eventDeactivationMap);
if (!success) return false;
uint64_t metricHash;
- if (!getMetricProtoHash(metric, metric.id(), hasActivation, activationHash, metricHash)) {
+ if (!getMetricProtoHash(config, metric, metric.id(), metricToActivationMap, metricHash)) {
return false;
}
@@ -784,105 +984,20 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t
// Gauge metrics.
for (int i = 0; i < config.gauge_metric_size(); i++) {
- const GaugeMetric& metric = config.gauge_metric(i);
- if (!metric.has_what()) {
- ALOGW("cannot find \"what\" in GaugeMetric \"%lld\"", (long long)metric.id());
- return false;
- }
-
- if ((!metric.gauge_fields_filter().has_include_all() ||
- (metric.gauge_fields_filter().include_all() == false)) &&
- !hasLeafNode(metric.gauge_fields_filter().fields())) {
- ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
- return false;
- }
- if ((metric.gauge_fields_filter().has_include_all() &&
- metric.gauge_fields_filter().include_all() == true) &&
- hasLeafNode(metric.gauge_fields_filter().fields())) {
- ALOGW("Incorrect field filter setting in GaugeMetric %lld", (long long)metric.id());
- return false;
- }
-
int metricIndex = allMetricProducers.size();
+ const GaugeMetric& metric = config.gauge_metric(i);
metricMap.insert({metric.id(), metricIndex});
- int trackerIndex;
- if (!handleMetricWithAtomMatchingTrackers(metric.what(), metricIndex,
- metric.has_dimensions_in_what(),
- allAtomMatchingTrackers, atomMatchingTrackerMap,
- trackerToMetricMap, trackerIndex)) {
+ optional<sp<MetricProducer>> producer = createGaugeMetricProducerAndUpdateMetadata(
+ key, config, timeBaseTimeNs, currentTimeNs, pullerManager, metric, metricIndex,
+ allAtomMatchingTrackers, atomMatchingTrackerMap, allConditionTrackers,
+ conditionTrackerMap, initialConditionCache, wizard, matcherWizard,
+ metricToActivationMap, trackerToMetricMap, conditionToMetricMap,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation);
+ if (!producer) {
return false;
}
-
- sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
- // For GaugeMetric atom, it should be simple matcher with one tagId.
- if (atomMatcher->getAtomIds().size() != 1) {
- return false;
- }
- int atomTagId = *(atomMatcher->getAtomIds().begin());
- int pullTagId = pullerManager->PullerForMatcherExists(atomTagId) ? atomTagId : -1;
-
- int triggerTrackerIndex;
- int triggerAtomId = -1;
- if (metric.has_trigger_event()) {
- if (pullTagId == -1) {
- ALOGW("Pull atom not specified for trigger");
- return false;
- }
- // event_trigger should be used with FIRST_N_SAMPLES
- if (metric.sampling_type() != GaugeMetric::FIRST_N_SAMPLES) {
- return false;
- }
- if (!handlePullMetricTriggerWithAtomMatchingTrackers(
- metric.trigger_event(), metricIndex, allAtomMatchingTrackers,
- atomMatchingTrackerMap, trackerToMetricMap, triggerTrackerIndex)) {
- return false;
- }
- sp<AtomMatchingTracker> triggerAtomMatcher =
- allAtomMatchingTrackers.at(triggerTrackerIndex);
- triggerAtomId = *(triggerAtomMatcher->getAtomIds().begin());
- }
-
- if (!metric.has_trigger_event() && pullTagId != -1 &&
- metric.sampling_type() == GaugeMetric::FIRST_N_SAMPLES) {
- ALOGW("FIRST_N_SAMPLES is only for pushed event or pull_on_trigger");
- return false;
- }
-
- int conditionIndex = -1;
- if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
- return false;
- }
- } else {
- if (metric.links_size() > 0) {
- ALOGW("metrics has a MetricConditionLink but doesn't have a condition");
- return false;
- }
- }
-
- bool hasActivation = false;
- unordered_map<int, shared_ptr<Activation>> eventActivationMap;
- unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
- uint64_t activationHash;
- bool success = handleMetricActivation(
- config, metric.id(), metricIndex, metricToActivationMap, atomMatchingTrackerMap,
- hasActivation, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
- metricsWithActivation, eventActivationMap, eventDeactivationMap, activationHash);
- if (!success) return false;
-
- uint64_t metricHash;
- if (!getMetricProtoHash(metric, metric.id(), hasActivation, activationHash, metricHash)) {
- return false;
- }
-
- sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
- key, metric, conditionIndex, initialConditionCache, wizard, metricHash,
- trackerIndex, matcherWizard, pullTagId, triggerAtomId, atomTagId, timeBaseTimeNs,
- currentTimeNs, pullerManager, eventActivationMap, eventDeactivationMap);
- allMetricProducers.push_back(gaugeProducer);
+ allMetricProducers.push_back(producer.value());
}
for (int i = 0; i < config.no_report_metric_size(); ++i) {
const auto no_report_metric = config.no_report_metric(i);
diff --git a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
index 4979c3051133..f909aff48faf 100644
--- a/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/parsing_utils/metrics_manager_util.h
@@ -30,7 +30,7 @@ namespace android {
namespace os {
namespace statsd {
-// Helper functions for creating individual config components from StatsdConfig.
+// Helper functions for creating, validating, and updating config components from StatsdConfig.
// Should only be called from metrics_manager_util and config_update_utils.
// Create a AtomMatchingTracker.
@@ -53,6 +53,101 @@ sp<ConditionTracker> createConditionTracker(
const ConfigKey& key, const Predicate& predicate, const int index,
const unordered_map<int64_t, int>& atomMatchingTrackerMap);
+// Get the hash of a metric, combining the activation if the metric has one.
+bool getMetricProtoHash(const StatsdConfig& config, const google::protobuf::MessageLite& metric,
+ const int64_t id,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ uint64_t& metricHash);
+
+// 1. Validates matcher existence
+// 2. Enforces matchers with dimensions and those used for trigger_event are about one atom
+// 3. Gets matcher index and updates tracker to metric map
+bool handleMetricWithAtomMatchingTrackers(
+ const int64_t matcherId, const int metricIndex, const bool enforceOneAtom,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap, int& logTrackerIndex);
+
+// 1. Validates condition existence, including those in links
+// 2. Gets condition index and updates condition to metric map
+bool handleMetricWithConditions(
+ const int64_t condition, const int metricIndex,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const ::google::protobuf::RepeatedPtrField<::android::os::statsd::MetricConditionLink>&
+ links,
+ const std::vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap);
+
+// Validates a metricActivation and populates state.
+// Fills the new event activation/deactivation maps, preserving the existing activations.
+// Returns false if there are errors.
+bool handleMetricActivationOnConfigUpdate(
+ const StatsdConfig& config, const int64_t metricId, const int metricIndex,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ const std::unordered_map<int64_t, int>& oldAtomMatchingTrackerMap,
+ const std::unordered_map<int64_t, int>& newAtomMatchingTrackerMap,
+ const std::unordered_map<int, shared_ptr<Activation>>& oldEventActivationMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation,
+ std::unordered_map<int, shared_ptr<Activation>>& newEventActivationMap,
+ std::unordered_map<int, std::vector<shared_ptr<Activation>>>& newEventDeactivationMap);
+
+// Creates a CountMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createCountMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const CountMetric& metric, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& stateAtomIdMap,
+ const std::unordered_map<int64_t, std::unordered_map<int, int64_t>>& allStateGroupMaps,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
+// Creates an EventMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createEventMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const EventMetric& metric, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
+// Creates a GaugeMetricProducer and updates the vectors/maps used by MetricsManager with
+// the appropriate indices. Returns an sp to the producer, or nullopt if there was an error.
+optional<sp<MetricProducer>> createGaugeMetricProducerAndUpdateMetadata(
+ const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseNs,
+ const int64_t currentTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const GaugeMetric& metric, const int metricIndex,
+ const std::vector<sp<AtomMatchingTracker>>& allAtomMatchingTrackers,
+ const std::unordered_map<int64_t, int>& atomMatchingTrackerMap,
+ std::vector<sp<ConditionTracker>>& allConditionTrackers,
+ const std::unordered_map<int64_t, int>& conditionTrackerMap,
+ const std::vector<ConditionState>& initialConditionCache, const sp<ConditionWizard>& wizard,
+ const sp<EventMatcherWizard>& matcherWizard,
+ const std::unordered_map<int64_t, int>& metricToActivationMap,
+ std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
+
// Helper functions for MetricsManager to initialize from StatsdConfig.
// *Note*: only initStatsdConfig() should be called from outside.
// All other functions are intermediate
@@ -154,10 +249,10 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, const sp
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
- unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
- unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
std::unordered_map<int64_t, int>& alertTrackerMap,
- vector<int>& metricsWithActivation,
+ std::vector<int>& metricsWithActivation,
std::map<int64_t, uint64_t>& stateProtoHashes,
std::set<int64_t>& noReportMetricIds);
diff --git a/cmds/statsd/tests/condition/ConditionTimer_test.cpp b/cmds/statsd/tests/condition/ConditionTimer_test.cpp
index ea02cd3a5ee1..46dc9a9d381f 100644
--- a/cmds/statsd/tests/condition/ConditionTimer_test.cpp
+++ b/cmds/statsd/tests/condition/ConditionTimer_test.cpp
@@ -35,11 +35,11 @@ TEST(ConditionTimerTest, TestTimer_Inital_False) {
EXPECT_EQ(0, timer.mTimerNs);
timer.onConditionChanged(true, ct_start_time + 5);
- EXPECT_EQ(ct_start_time + 5, timer.mLastConditionTrueTimestampNs);
+ EXPECT_EQ(ct_start_time + 5, timer.mLastConditionChangeTimestampNs);
EXPECT_EQ(true, timer.mCondition);
EXPECT_EQ(95, timer.newBucketStart(ct_start_time + 100));
- EXPECT_EQ(ct_start_time + 100, timer.mLastConditionTrueTimestampNs);
+ EXPECT_EQ(ct_start_time + 100, timer.mLastConditionChangeTimestampNs);
EXPECT_EQ(true, timer.mCondition);
}
@@ -51,7 +51,7 @@ TEST(ConditionTimerTest, TestTimer_Inital_True) {
EXPECT_EQ(ct_start_time - time_base, timer.newBucketStart(ct_start_time));
EXPECT_EQ(true, timer.mCondition);
EXPECT_EQ(0, timer.mTimerNs);
- EXPECT_EQ(ct_start_time, timer.mLastConditionTrueTimestampNs);
+ EXPECT_EQ(ct_start_time, timer.mLastConditionChangeTimestampNs);
timer.onConditionChanged(false, ct_start_time + 5);
EXPECT_EQ(5, timer.mTimerNs);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b166cc1fe04e..6cf4192b41fd 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -93,6 +93,13 @@ static void assertPastBucketValuesSingleKey(
}
}
+static void assertConditionTimer(const ConditionTimer& conditionTimer, bool condition,
+ int64_t timerNs, int64_t lastConditionTrueTimestampNs) {
+ EXPECT_EQ(condition, conditionTimer.mCondition);
+ EXPECT_EQ(timerNs, conditionTimer.mTimerNs);
+ EXPECT_EQ(lastConditionTrueTimestampNs, conditionTimer.mLastConditionChangeTimestampNs);
+}
+
} // anonymous namespace
class ValueMetricProducerTestHelper {
@@ -3967,33 +3974,37 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
// Screen state change to ON.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5));
return true;
}))
// Screen state change to OFF.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9));
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 9));
return true;
}))
// Screen state change to ON.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21));
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21));
return true;
}))
// Dump report requested.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30));
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30));
return true;
}));
@@ -4025,12 +4036,13 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
// Bucket status after screen state change kStateUnknown->ON.
auto screenEvent = CreateScreenStateChangedEvent(
- bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4040,19 +4052,29 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_EQ(0, it->second.intervals.size());
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Bucket status after screen state change ON->OFF.
- screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4061,13 +4083,23 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
EXPECT_TRUE(itBase->second.hasCurrentState);
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, OFF}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_EQ(0, it->second.intervals.size());
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
// Value for dimension, state key {{}, ON}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(4, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4076,9 +4108,11 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Bucket status after screen state change OFF->ON.
- screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
StateManager::getInstance().onLogEvent(*screenEvent);
ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
@@ -4098,6 +4132,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(12, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 15 * NS_PER_SEC);
// Value for dimension, state key {{}, ON}
it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4106,6 +4142,8 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(4, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, true, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 15 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4114,37 +4152,46 @@ TEST(ValueMetricProducerTest, TestSlicedState) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Start dump report and check output.
ProtoOutputStream output;
std::set<string> strSet;
- valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true,
- NO_TIME_CONSTRAINTS, &strSet, &output);
+ valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
StatsLogReport report = outputStreamToProto(&output);
EXPECT_TRUE(report.has_value_metrics());
ASSERT_EQ(3, report.value_metrics().data_size());
+ // {{}, kStateUnknown}
auto data = report.value_metrics().data(0);
ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */, data.slice_by_state(0).value());
+ EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {{}, ON}
data = report.value_metrics().data(1);
ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
EXPECT_EQ(13, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {{}, OFF}
data = report.value_metrics().data(2);
ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size());
EXPECT_EQ(12, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+ EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
}
/*
@@ -4169,9 +4216,10 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
// Screen state change to ON.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 5 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5 * NS_PER_SEC, 5));
return true;
}))
// Screen state change to VR has no pull because it is in the same
@@ -4183,17 +4231,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
// Screen state change to OFF.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 15 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 15, 21));
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 15 * NS_PER_SEC, 21));
return true;
}))
// Dump report requested.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC);
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 50, 30));
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 30));
return true;
}));
@@ -4236,12 +4286,13 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
// Bucket status after screen state change kStateUnknown->ON.
auto screenEvent = CreateScreenStateChangedEvent(
- bucketStartTimeNs + 5, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ bucketStartTimeNs + 5 * NS_PER_SEC, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4251,20 +4302,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(screenOnGroup.group_id(),
itBase->second.currentState.getValues()[0].mValue.long_value);
+ // Value for dimension, state key {{}, ON GROUP}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(screenOnGroup.group_id(),
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Bucket status after screen state change ON->VR.
// Both ON and VR are in the same state group, so the base should not change.
- screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_VR);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4274,20 +4334,29 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(screenOnGroup.group_id(),
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, ON GROUP}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(screenOnGroup.group_id(),
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Bucket status after screen state change VR->ON.
// Both ON and VR are in the same state group, so the base should not change.
- screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12,
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4297,19 +4366,28 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(screenOnGroup.group_id(),
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, ON GROUP}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(screenOnGroup.group_id(),
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 5 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Bucket status after screen state change VR->OFF.
- screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15,
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 15 * NS_PER_SEC,
android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4319,13 +4397,22 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(screenOffGroup.group_id(),
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, OFF GROUP}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(screenOffGroup.group_id(),
+ it->first.getStateValuesKey().getValues()[0].mValue.long_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 15 * NS_PER_SEC);
// Value for dimension, state key {{}, ON GROUP}
+ it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(screenOnGroup.group_id(),
it->first.getStateValuesKey().getValues()[0].mValue.long_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(16, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 15 * NS_PER_SEC);
// Value for dimension, state key {{}, kStateUnknown}
it++;
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4334,37 +4421,46 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 5 * NS_PER_SEC,
+ bucketStartTimeNs + 5 * NS_PER_SEC);
// Start dump report and check output.
ProtoOutputStream output;
std::set<string> strSet;
- valueProducer->onDumpReport(bucketStartTimeNs + 50, true /* include recent buckets */, true,
- NO_TIME_CONSTRAINTS, &strSet, &output);
+ valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
StatsLogReport report = outputStreamToProto(&output);
EXPECT_TRUE(report.has_value_metrics());
ASSERT_EQ(3, report.value_metrics().data_size());
+ // {{}, kStateUnknown}
auto data = report.value_metrics().data(0);
ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(2, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+ EXPECT_EQ(5 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {{}, ON GROUP}
data = report.value_metrics().data(1);
ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
EXPECT_EQ(16, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
EXPECT_EQ(screenOnGroup.group_id(), data.slice_by_state(0).group_id());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {{}, OFF GROUP}
data = report.value_metrics().data(2);
ASSERT_EQ(1, report.value_metrics().data(2).bucket_info_size());
EXPECT_EQ(9, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_group_id());
EXPECT_EQ(screenOffGroup.group_id(), data.slice_by_state(0).group_id());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
}
/*
@@ -4386,6 +4482,35 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
auto fieldsInState = stateLink->mutable_fields_in_state();
*fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+ /*
+ NOTE: "1" denotes uid 1 and "2" denotes uid 2.
+ bucket # 1 bucket # 2
+ 10 20 30 40 50 60 70 80 90 100 110 120 (seconds)
+ |------------------------------------------|---------------------------------|--
+
+ (kStateUnknown)
+ 1
+ |-------------|
+ 20
+
+ 2
+ |----------------------------|
+ 40
+
+ (FOREGROUND)
+ 1 1
+ |----------------------------|-------------| |------|
+ 40 20 10
+
+
+ (BACKGROUND)
+ 1
+ |------------|
+ 20
+ 2
+ |-------------|---------------------------------|
+ 20 50
+ */
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
// ValueMetricProducer initialized.
@@ -4400,64 +4525,64 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
// Uid 1 process state change from kStateUnknown -> Foreground
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC);
data->clear();
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 1 /*uid*/, 6));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC,
+ 1 /*uid*/, 6));
// This event should be skipped.
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20, 2 /*uid*/, 8));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC,
+ 2 /*uid*/, 8));
return true;
}))
// Uid 2 process state change from kStateUnknown -> Background
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40);
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC);
data->clear();
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 2 /*uid*/, 9));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC,
+ 2 /*uid*/, 9));
// This event should be skipped.
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40, 1 /*uid*/, 12));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC,
+ 1 /*uid*/, 12));
return true;
}))
// Uid 1 process state change from Foreground -> Background
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20);
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 20 * NS_PER_SEC);
data->clear();
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 1 /*uid*/, 13));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC,
+ 1 /*uid*/, 13));
// This event should be skipped.
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20, 2 /*uid*/, 11));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 20 * NS_PER_SEC,
+ 2 /*uid*/, 11));
return true;
}))
// Uid 1 process state change from Background -> Foreground
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40);
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 40 * NS_PER_SEC);
data->clear();
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 1 /*uid*/, 17));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC,
+ 1 /*uid*/, 17));
// This event should be skipped.
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40, 2 /*uid */, 15));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 40 * NS_PER_SEC,
+ 2 /*uid */, 15));
return true;
}))
// Dump report pull.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data) {
- EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50);
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC);
data->clear();
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 2 /*uid*/, 20));
- data->push_back(
- CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50, 1 /*uid*/, 21));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC,
+ 2 /*uid*/, 20));
+ data->push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 50 * NS_PER_SEC,
+ 1 /*uid*/, 21));
return true;
}));
@@ -4489,6 +4614,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
// Base for dimension key {uid 2}
it++;
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4505,12 +4631,14 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
// Bucket status after uid 1 process state change kStateUnknown -> Foreground.
- auto uidProcessEvent = CreateUidProcessStateChangedEvent(
- bucketStartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ auto uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
StateManager::getInstance().onLogEvent(*uidProcessEvent);
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {uid 1}.
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4528,8 +4656,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(3, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+ // Value for key {uid 1, FOREGROUND}.
+ it++;
+ ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
- // Base for dimension key {uid 2}
+ // Base for dimension key {uid 2}.
it++;
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
@@ -4538,22 +4676,42 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
itBase->second.currentState.getValues()[0].mValue.int_value);
- // Value for key {uid 2, kStateUnknown}
+ // Value for key {uid 2, kStateUnknown}.
ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
- EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
// Bucket status after uid 2 process state change kStateUnknown -> Background.
- uidProcessEvent = CreateUidProcessStateChangedEvent(
- bucketStartTimeNs + 40, 2 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucketStartTimeNs + 40 * NS_PER_SEC, 2 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
StateManager::getInstance().onLogEvent(*uidProcessEvent);
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
- // Base for dimension key {uid 1}.
+ ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension key {uid 2}.
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
+ EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {uid 2, BACKGROUND}.
+ ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 40 * NS_PER_SEC);
+
+ // Base for dimension key {uid 1}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
EXPECT_EQ(6, itBase->second.baseInfos[0].base.long_value);
EXPECT_TRUE(itBase->second.hasCurrentState);
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
@@ -4563,26 +4721,33 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
- EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(3, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
- // Base for dimension key {uid 2}
+ // Value for key {uid 1, FOREGROUND}.
it++;
- itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
- EXPECT_TRUE(itBase->second.baseInfos[0].hasBase);
- EXPECT_EQ(9, itBase->second.baseInfos[0].base.long_value);
- EXPECT_TRUE(itBase->second.hasCurrentState);
- ASSERT_EQ(1, itBase->second.currentState.getValues().size());
- EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
- itBase->second.currentState.getValues()[0].mValue.int_value);
+ ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
// Value for key {uid 2, kStateUnknown}
+ it++;
ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
- EXPECT_EQ(-1, it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 40 * NS_PER_SEC,
+ bucketStartTimeNs + 40 * NS_PER_SEC);
// Pull at end of first bucket.
vector<shared_ptr<LogEvent>> allData;
@@ -4612,6 +4777,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
// Base for dimension key {uid 1}
it++;
@@ -4629,6 +4796,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+ EXPECT_EQ(20 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
// Value for key {uid 1, FOREGROUND}
it++;
@@ -4638,6 +4807,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
// Value for key {uid 2, kStateUnknown}
it++;
@@ -4647,13 +4818,16 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* kStateTracker::kUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC);
+ EXPECT_EQ(40 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
// Bucket status after uid 1 process state change from Foreground -> Background.
- uidProcessEvent = CreateUidProcessStateChangedEvent(
- bucket2StartTimeNs + 20, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 20 * NS_PER_SEC, 1 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
StateManager::getInstance().onLogEvent(*uidProcessEvent);
- ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size());
ASSERT_EQ(4UL, valueProducer->mPastBuckets.size());
ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
// Base for dimension key {uid 2}.
@@ -4672,6 +4846,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+
// Base for dimension key {uid 1}
it++;
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
@@ -4688,6 +4864,17 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {uid 1, BACKGROUND}
+ it++;
+ ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 20 * NS_PER_SEC);
+
// Value for key {uid 1, FOREGROUND}
it++;
ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4697,6 +4884,9 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(3, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucket2StartTimeNs + 20 * NS_PER_SEC);
+
// Value for key {uid 2, kStateUnknown}
it++;
ASSERT_EQ(1, it->first.getDimensionKeyInWhat().getValues().size());
@@ -4705,10 +4895,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC);
// Bucket status after uid 1 process state change Background->Foreground.
- uidProcessEvent = CreateUidProcessStateChangedEvent(
- bucket2StartTimeNs + 40, 1 /* uid */, android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
StateManager::getInstance().onLogEvent(*uidProcessEvent);
ASSERT_EQ(5UL, valueProducer->mCurrentSlicedBucket.size());
@@ -4729,6 +4921,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
// Base for dimension key {uid 1}
it++;
@@ -4746,6 +4939,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
// Value for key {uid 1, BACKGROUND}
it++;
@@ -4756,6 +4950,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(4, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucket2StartTimeNs + 40 * NS_PER_SEC);
// Value for key {uid 1, FOREGROUND}
it++;
@@ -4766,6 +4962,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(3, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC,
+ bucket2StartTimeNs + 40 * NS_PER_SEC);
// Value for key {uid 2, kStateUnknown}
it++;
@@ -4774,17 +4972,20 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 40 * NS_PER_SEC);
// Start dump report and check output.
ProtoOutputStream output;
std::set<string> strSet;
- valueProducer->onDumpReport(bucket2StartTimeNs + 50, true /* include recent buckets */, true,
- NO_TIME_CONSTRAINTS, &strSet, &output);
+ valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
StatsLogReport report = outputStreamToProto(&output);
EXPECT_TRUE(report.has_value_metrics());
ASSERT_EQ(5, report.value_metrics().data_size());
+ // {uid 1, BACKGROUND}
auto data = report.value_metrics().data(0);
ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(4, report.value_metrics().data(0).bucket_info(0).values(0).value_long());
@@ -4792,14 +4993,18 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
data.slice_by_state(0).value());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {uid 2, kStateUnknown}
data = report.value_metrics().data(1);
ASSERT_EQ(1, report.value_metrics().data(1).bucket_info_size());
EXPECT_EQ(2, report.value_metrics().data(1).bucket_info(0).values(0).value_long());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {uid 1, FOREGROUND}
data = report.value_metrics().data(2);
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -4808,14 +5013,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
ASSERT_EQ(2, report.value_metrics().data(2).bucket_info_size());
EXPECT_EQ(4, report.value_metrics().data(2).bucket_info(0).values(0).value_long());
EXPECT_EQ(7, report.value_metrics().data(2).bucket_info(1).values(0).value_long());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
+ // {uid 1, kStateUnknown}
data = report.value_metrics().data(3);
ASSERT_EQ(1, report.value_metrics().data(3).bucket_info_size());
EXPECT_EQ(3, report.value_metrics().data(3).bucket_info(0).values(0).value_long());
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
EXPECT_EQ(-1 /*StateTracker::kStateUnknown*/, data.slice_by_state(0).value());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ // {uid 2, BACKGROUND}
data = report.value_metrics().data(4);
EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
EXPECT_TRUE(data.slice_by_state(0).has_value());
@@ -4824,6 +5034,1630 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) {
ASSERT_EQ(2, report.value_metrics().data(4).bucket_info_size());
EXPECT_EQ(6, report.value_metrics().data(4).bucket_info(0).values(0).value_long());
EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
+}
+
+/*
+ * Test slicing condition_true_nanos by state for metric that slices by state when data is not
+ * present in pulled data during a state change.
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataInStateChange) {
+ // Set up ValueMetricProducer.
+ ValueMetric metric =
+ ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE");
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ /*
+ NOTE: "-" means that the data was not present in the pulled data.
+
+ bucket # 1
+ 10 20 30 40 50 60 (seconds)
+ |-------------------------------------------------------|--
+ x (kStateUnknown)
+ |-----------|
+ 10
+
+ x x (ON)
+ |---------------------| |-----------|
+ 20 10
+
+ - (OFF)
+ */
+ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+ // ValueMetricProducer initialized.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
+ return true;
+ }))
+ // Battery saver mode state changed to ON.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5));
+ return true;
+ }))
+ // Battery saver mode state changed to OFF but data for dimension key {} is not present
+ // in the pulled data.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC);
+ data->clear();
+ return true;
+ }))
+ // Battery saver mode state changed to ON.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC, 7));
+ return true;
+ }))
+ // Dump report pull.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15));
+ return true;
+ }));
+
+ StateManager::getInstance().clear();
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithState(
+ pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {});
+ EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+ // Set up StateManager and check that StateTrackers are initialized.
+ StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED,
+ valueProducer);
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ util::BATTERY_SAVER_MODE_STATE_CHANGED));
+
+ // Bucket status after metric initialized.
+ ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension key {}
+ auto it = valueProducer->mCurrentSlicedBucket.begin();
+ auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, kStateUnknown}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
+
+ // Bucket status after battery saver mode ON event.
+ unique_ptr<LogEvent> batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+
+ // Base for dimension key {}
+
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Bucket status after battery saver mode OFF event which is not present
+ // in the pulled data.
+ unique_ptr<LogEvent> batterySaverOffEvent =
+ CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOffEvent);
+
+ // Base for dimension key {}
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_FALSE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Bucket status after battery saver mode ON event.
+ batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 40 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+
+ // Base for dimension key {}
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 40 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Start dump report and check output.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(2, report.value_metrics().data_size());
+
+ // {{}, kStateUnknown}
+ ValueMetricData data = report.value_metrics().data(0);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+
+ // {{}, ON}
+ data = report.value_metrics().data(1);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+}
+
+/*
+ * Test for metric that slices by state when data is not present in pulled data
+ * during an event and then a flush occurs for the current bucket. With the new
+ * condition timer behavior, a "new" MetricDimensionKey is inserted into
+ * `mCurrentSlicedBucket` before intervals are closed/added to that new
+ * MetricDimensionKey.
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithMissingDataThenFlushBucket) {
+ // Set up ValueMetricProducer.
+ ValueMetric metric =
+ ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE");
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ /*
+ NOTE: "-" means that the data was not present in the pulled data.
+
+ bucket # 1
+ 10 20 30 40 50 60 (seconds)
+ |-------------------------------------------------------|--
+ - (kStateUnknown)
+
+ - (ON)
+ */
+ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+ // ValueMetricProducer initialized but data for dimension key {} is not present
+ // in the pulled data..
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+ data->clear();
+ return true;
+ }))
+ // Battery saver mode state changed to ON but data for dimension key {} is not present
+ // in the pulled data.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
+ data->clear();
+ return true;
+ }))
+ // Dump report pull.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 15));
+ return true;
+ }));
+
+ StateManager::getInstance().clear();
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithState(
+ pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {});
+ EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+ // Set up StateManager and check that StateTrackers are initialized.
+ StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED,
+ valueProducer);
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ util::BATTERY_SAVER_MODE_STATE_CHANGED));
+
+ // Bucket status after metric initialized.
+ ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size());
+
+ // Bucket status after battery saver mode ON event which is not present
+ // in the pulled data.
+ unique_ptr<LogEvent> batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+
+ ASSERT_EQ(0UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
+
+ // Start dump report and check output.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(0, report.value_metrics().data_size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+}
+
+TEST(ValueMetricProducerTest, TestSlicedStateWithNoPullOnBucketBoundary) {
+ // Set up ValueMetricProducer.
+ ValueMetric metric =
+ ValueMetricProducerTestHelper::createMetricWithState("BATTERY_SAVER_MODE_STATE");
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ /*
+ bucket # 1 bucket # 2
+ 10 20 30 40 50 60 70 80 90 100 110 120 (seconds)
+ |------------------------------------|---------------------------|--
+ x (kStateUnknown)
+ |-----|
+ 10
+ x x (ON)
+ |-----| |-----------|
+ 10 20
+ x (OFF)
+ |------------------------|
+ 40
+ */
+ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+ // ValueMetricProducer initialized.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs, 3));
+ return true;
+ }))
+ // Battery saver mode state changed to ON.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 5));
+ return true;
+ }))
+ // Battery saver mode state changed to OFF.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 7));
+ return true;
+ }))
+ // Battery saver mode state changed to ON.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 10));
+ return true;
+ }))
+ // Dump report pull.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 15));
+ return true;
+ }));
+
+ StateManager::getInstance().clear();
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithState(
+ pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {});
+ EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+ // Set up StateManager and check that StateTrackers are initialized.
+ StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED,
+ valueProducer);
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ util::BATTERY_SAVER_MODE_STATE_CHANGED));
+
+ // Bucket status after metric initialized.
+ ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ auto it = valueProducer->mCurrentSlicedBucket.begin();
+ auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for dimension, state key {{}, kStateUnknown}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs);
+
+ // Bucket status after battery saver mode ON event.
+ unique_ptr<LogEvent> batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Bucket status after battery saver mode OFF event.
+ unique_ptr<LogEvent> batterySaverOffEvent =
+ CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 20 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOffEvent);
+
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, OFF}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{}, ON}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Bucket status after battery saver mode ON event.
+ batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucket2StartTimeNs + 30 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, OFF}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC,
+ bucket2StartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{}, ON}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Start dump report and check output.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(3, report.value_metrics().data_size());
+
+ // {{}, kStateUnknown}
+ ValueMetricData data = report.value_metrics().data(0);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+
+ // {{}, ON}
+ data = report.value_metrics().data(1);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
+
+ // {{}, OFF}
+ data = report.value_metrics().data(2);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+}
+
+/*
+ * Test slicing condition_true_nanos by state for metric that slices by state when data is not
+ * present in pulled data during a condition change.
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithDataMissingInConditionChange) {
+ // Set up ValueMetricProducer.
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState(
+ "BATTERY_SAVER_MODE_STATE");
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ /*
+ NOTE: "-" means that the data was not present in the pulled data.
+
+ bucket # 1
+ 10 20 30 40 50 60 (seconds)
+ |-------------------------------------------------------|--
+
+ T F T (Condition)
+ x (ON)
+ |----------------------| -
+ 20
+ */
+ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+ // Battery saver mode state changed to ON.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC, 3));
+ return true;
+ }))
+ // Condition changed to false.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC);
+ data->clear();
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5));
+ return true;
+ }))
+ // Condition changed to true but data for dimension key {} is not present in the
+ // pulled data.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC);
+ data->clear();
+ return true;
+ }))
+ // Dump report pull.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 50 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateRepeatedValueLogEvent(
+ tagId, bucketStartTimeNs + 50 * NS_PER_SEC, 20));
+ return true;
+ }));
+
+ StateManager::getInstance().clear();
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithConditionAndState(
+ pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {},
+ ConditionState::kTrue);
+ EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+ // Set up StateManager and check that StateTrackers are initialized.
+ StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED,
+ valueProducer);
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ util::BATTERY_SAVER_MODE_STATE_CHANGED));
+
+ // Bucket status after battery saver mode ON event.
+ unique_ptr<LogEvent> batterySaverOnEvent =
+ CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC);
+ StateManager::getInstance().onLogEvent(*batterySaverOnEvent);
+ // Base for dimension key {}
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ auto it = valueProducer->mCurrentSlicedBucket.begin();
+ auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket status after condition change to false.
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 30 * NS_PER_SEC);
+ // Base for dimension key {}
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_TRUE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket status after condition change to true.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 40 * NS_PER_SEC);
+ // Base for dimension key {}
+ ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_FALSE(itBase->second.hasCurrentState);
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, ON}
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Start dump report and check output.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucketStartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(1, report.value_metrics().data_size());
+
+ // {{}, ON}
+ ValueMetricData data = report.value_metrics().data(0);
+ EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+}
+
+/*
+ * Test slicing condition_true_nanos by state for metric that slices by state with a primary field,
+ * condition, and has multiple dimensions.
+ */
+TEST(ValueMetricProducerTest, TestSlicedStateWithMultipleDimensions) {
+ // Set up ValueMetricProducer.
+ ValueMetric metric =
+ ValueMetricProducerTestHelper::createMetricWithConditionAndState("UID_PROCESS_STATE");
+ metric.mutable_dimensions_in_what()->set_field(tagId);
+ metric.mutable_dimensions_in_what()->add_child()->set_field(1);
+ metric.mutable_dimensions_in_what()->add_child()->set_field(3);
+
+ MetricStateLink* stateLink = metric.add_state_link();
+ stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+ auto fieldsInWhat = stateLink->mutable_fields_in_what();
+ *fieldsInWhat = CreateDimensions(tagId, {1 /* uid */});
+ auto fieldsInState = stateLink->mutable_fields_in_state();
+ *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+ /*
+ bucket # 1 bucket # 2
+ 10 20 30 40 50 60 70 80 90 100 110 120 (seconds)
+ |------------------------------------------|---------------------------------|--
+
+ T F T (Condition)
+ (FOREGROUND)
+ x {1, 14}
+ |------|
+ 10
+
+ x {1, 16}
+ |------|
+ 10
+ x {2, 8}
+ |-------------|
+ 20
+
+ (BACKGROUND)
+ x {1, 14}
+ |-------------| |----------|---------------------------------|
+ 20 15 50
+
+ x {1, 16}
+ |-------------| |----------|---------------------------------|
+ 20 15 50
+
+ x {2, 8}
+ |----------| |----------|-------------------|
+ 15 15 30
+ */
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+ // Uid 1 process state change from kStateUnknown -> Foreground
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC,
+ 1 /*uid*/, 3, 14 /*tag*/));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC,
+ 1 /*uid*/, 3, 16 /*tag*/));
+
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 10 * NS_PER_SEC,
+ 2 /*uid*/, 5, 8 /*tag*/));
+ return true;
+ }))
+ // Uid 1 process state change from Foreground -> Background
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC,
+ 1 /*uid*/, 5, 14 /*tag*/));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC,
+ 1 /*uid*/, 5, 16 /*tag*/));
+
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC,
+ 2 /*uid*/, 7, 8 /*tag*/));
+
+ return true;
+ }))
+ // Uid 2 process state change from kStateUnknown -> Background
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 25 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC,
+ 2 /*uid*/, 9, 8 /*tag*/));
+
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC,
+ 1 /*uid*/, 9, 14 /* tag */));
+
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 25 * NS_PER_SEC,
+ 1 /*uid*/, 9, 16 /* tag */));
+
+ return true;
+ }))
+ // Condition changed to false.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 40 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC,
+ 1 /*uid*/, 11, 14 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC,
+ 1 /*uid*/, 11, 16 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 40 * NS_PER_SEC,
+ 2 /*uid*/, 11, 8 /*tag*/));
+
+ return true;
+ }))
+ // Condition changed to true.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 45 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC,
+ 1 /*uid*/, 13, 14 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC,
+ 1 /*uid*/, 13, 16 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(tagId, bucketStartTimeNs + 45 * NS_PER_SEC,
+ 2 /*uid*/, 13, 8 /*tag*/));
+ return true;
+ }))
+ // Uid 2 process state change from Background -> Foreground
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 30 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /*uid*/, 18, 8 /*tag*/));
+
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 14 /* tag */));
+ // This event should be skipped.
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 30 * NS_PER_SEC, 1 /*uid*/, 18, 16 /* tag */));
+
+ return true;
+ }))
+ // Dump report pull.
+ .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+ vector<std::shared_ptr<LogEvent>>* data) {
+ EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 50 * NS_PER_SEC);
+ data->clear();
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 14 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 1 /*uid*/, 21, 16 /* tag */));
+ data->push_back(CreateThreeValueLogEvent(
+ tagId, bucket2StartTimeNs + 50 * NS_PER_SEC, 2 /*uid*/, 21, 8 /*tag*/));
+ return true;
+ }));
+
+ StateManager::getInstance().clear();
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithConditionAndState(
+ pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}, ConditionState::kTrue);
+ EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size());
+
+ // Set up StateManager and check that StateTrackers are initialized.
+ StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer);
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+ // Condition is true.
+ // Bucket status after uid 1 process state change kStateUnknown -> Foreground.
+ auto uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucketStartTimeNs + 10 * NS_PER_SEC, 1 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ StateManager::getInstance().onLogEvent(*uidProcessEvent);
+ ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(4UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension {uid 1, tag 16}.
+ auto it = valueProducer->mCurrentSlicedBucket.begin();
+ auto itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 10 * NS_PER_SEC);
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket status after uid 1 process state change Foreground -> Background.
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+ StateManager::getInstance().onLogEvent(*uidProcessEvent);
+ ASSERT_EQ(2UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(6UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension {uid 1, tag 16}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket status after uid 2 process state change kStateUnknown -> Background.
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucketStartTimeNs + 25 * NS_PER_SEC, 2 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_BACKGROUND);
+ StateManager::getInstance().onLogEvent(*uidProcessEvent);
+ ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension {uid 2, tag 8}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 2, uid 8}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 25 * NS_PER_SEC);
+
+ // Value for key {{uid 2, uid 8}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension {uid 1, tag 16}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket 1 status after condition change to false.
+ // All condition timers should be turned off.
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 40 * NS_PER_SEC);
+ ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension {uid 2, tag 8}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 2, uid 8}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 15 * NS_PER_SEC,
+ bucketStartTimeNs + 40 * NS_PER_SEC);
+
+ // Value for key {{uid 2, uid 8}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension {uid 1, tag 16}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 40 * NS_PER_SEC);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 40 * NS_PER_SEC);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket 1 status after condition change to true.
+ valueProducer->onConditionChanged(true, bucketStartTimeNs + 45 * NS_PER_SEC);
+ ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size());
+ ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension {uid 2, tag 8}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 2, uid 8}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 15 * NS_PER_SEC,
+ bucketStartTimeNs + 45 * NS_PER_SEC);
+
+ // Value for key {{uid 2, uid 8}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension {uid 1, tag 16}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 45 * NS_PER_SEC);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 20 * NS_PER_SEC,
+ bucketStartTimeNs + 45 * NS_PER_SEC);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Pull at end of first bucket.
+ vector<shared_ptr<LogEvent>> allData;
+ allData.push_back(
+ CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 14 /* tag */));
+ allData.push_back(
+ CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 1 /*uid*/, 13, 16 /* tag */));
+ allData.push_back(
+ CreateThreeValueLogEvent(tagId, bucket2StartTimeNs, 2 /*uid*/, 13, 8 /*tag*/));
+ valueProducer->onDataPulled(allData, /** succeeds */ true, bucket2StartTimeNs + 1);
+
+ // Buckets flushed after end of first bucket.
+ // All condition timers' behavior should rollover to bucket 2.
+ ASSERT_EQ(8UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(5UL, valueProducer->mPastBuckets.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size());
+ // Base for dimension {uid 2, tag 8}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 2, uid 8}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size());
+ EXPECT_EQ(30 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
+
+ // Value for key {{uid 2, uid 8}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension {uid 1, tag 16}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size());
+ EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size());
+ EXPECT_EQ(35 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+ ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size());
+ EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+ ASSERT_EQ(1, valueProducer->mPastBuckets[it->first].size());
+ EXPECT_EQ(10 * NS_PER_SEC, valueProducer->mPastBuckets[it->first][0].mConditionTrueNs);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Bucket 2 status after uid 2 process state change Background->Foreground.
+ uidProcessEvent =
+ CreateUidProcessStateChangedEvent(bucket2StartTimeNs + 30 * NS_PER_SEC, 2 /* uid */,
+ android::app::PROCESS_STATE_IMPORTANT_FOREGROUND);
+ StateManager::getInstance().onLogEvent(*uidProcessEvent);
+
+ ASSERT_EQ(9UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(3UL, valueProducer->mCurrentBaseInfo.size());
+ // Base for dimension {uid 2, tag 8}.
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 2, uid 8}, FOREGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{uid 2, uid 8}, BACKGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 30 * NS_PER_SEC,
+ bucket2StartTimeNs + 30 * NS_PER_SEC);
+
+ // Value for key {{uid 2, uid 8}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(8, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Base for dimension {uid 1, tag 16}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, uid 16}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+
+ // Base for dimension key {uid 1, tag 14}.
+ it++;
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ ASSERT_EQ(1, itBase->second.currentState.getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{uid 1, tag 14}, BACKGROUND}.
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+
+ // Value for key {{uid 1, uid 16}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 16}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(16, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Value for key {{uid 1, tag 14}, FOREGROUND}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(android::app::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+
+ // Value for key {{uid 1, tag 14}, kStateUnknown}.
+ it++;
+ ASSERT_EQ(2, it->first.getDimensionKeyInWhat().getValues().size());
+ EXPECT_EQ(1, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
+ EXPECT_EQ(14, it->first.getDimensionKeyInWhat().getValues()[1].mValue.int_value);
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(-1 /* StateTracker::kStateUnknown */,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
+
+ // Start dump report and check output.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC,
+ true /* include recent buckets */, true, NO_TIME_CONSTRAINTS,
+ &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(6, report.value_metrics().data_size());
+
+ // {{uid 1, tag 14}, FOREGROUND}.
+ auto data = report.value_metrics().data(0);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+
+ // {{uid 1, tag 16}, BACKGROUND}.
+ data = report.value_metrics().data(1);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
+
+ // {{uid 1, tag 14}, BACKGROUND}.
+ data = report.value_metrics().data(2);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
+
+ // {{uid 1, tag 16}, FOREGROUND}.
+ data = report.value_metrics().data(3);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+
+ // {{uid 2, tag 8}, FOREGROUND}.
+ data = report.value_metrics().data(4);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+
+ // {{uid 2, tag 8}, BACKGROUND}.
+ data = report.value_metrics().data(5);
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ ASSERT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
}
TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
@@ -4894,15 +6728,23 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(BatterySaverModeStateChanged::ON,
itBase->second.currentState.getValues()[0].mValue.int_value);
- // Value for key {{}, -1}
- ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ // Value for key {{}, ON}
+ ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
std::unordered_map<MetricDimensionKey, ValueMetricProducer::CurrentValueBucket>::iterator it =
valueProducer->mCurrentSlicedBucket.begin();
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 20 * NS_PER_SEC);
+ // Value for key {{}, -1}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(-1 /*StateTracker::kUnknown*/,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
// Bucket status after battery saver mode OFF event.
unique_ptr<LogEvent> batterySaverOffEvent =
@@ -4917,15 +6759,27 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(BatterySaverModeStateChanged::OFF,
itBase->second.currentState.getValues()[0].mValue.int_value);
- // Value for key {{}, ON}
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ // Value for key {{}, OFF}
+ ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size());
it = valueProducer->mCurrentSlicedBucket.begin();
EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::OFF,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucketStartTimeNs + 30 * NS_PER_SEC);
+ // Value for key {{}, ON}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
EXPECT_EQ(BatterySaverModeStateChanged::ON,
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(2, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucketStartTimeNs + 30 * NS_PER_SEC);
+ // Value for key {{}, -1}
+ it++;
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
// Pull at end of first bucket.
vector<shared_ptr<LogEvent>> allData;
@@ -4944,6 +6798,15 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
ASSERT_EQ(1, itBase->second.currentState.getValues().size());
EXPECT_EQ(BatterySaverModeStateChanged::OFF,
itBase->second.currentState.getValues()[0].mValue.int_value);
+ // Value for key {{}, OFF}
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ assertConditionTimer(it->second.conditionTimer, true, 0, bucket2StartTimeNs);
+ // Value for key {{}, ON}
+ it++;
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC);
+ // Value for key {{}, -1}
+ it++;
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
// Bucket 2 status after condition change to false.
valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC);
@@ -4964,6 +6827,19 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
it->first.getStateValuesKey().getValues()[0].mValue.int_value);
EXPECT_TRUE(it->second.intervals[0].hasValue);
EXPECT_EQ(4, it->second.intervals[0].value.long_value);
+ assertConditionTimer(it->second.conditionTimer, false, 10 * NS_PER_SEC,
+ bucket2StartTimeNs + 10 * NS_PER_SEC);
+ // Value for key {{}, ON}
+ it++;
+ EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size());
+ ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size());
+ EXPECT_EQ(BatterySaverModeStateChanged::ON,
+ it->first.getStateValuesKey().getValues()[0].mValue.int_value);
+ EXPECT_FALSE(it->second.intervals[0].hasValue);
+ assertConditionTimer(it->second.conditionTimer, false, 0, bucketStartTimeNs + 30 * NS_PER_SEC);
+ // Value for key {{}, -1}
+ it++;
+ assertConditionTimer(it->second.conditionTimer, false, 0, 0);
// Start dump report and check output.
ProtoOutputStream output;
@@ -4982,6 +6858,7 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value());
ASSERT_EQ(1, data.bucket_info_size());
EXPECT_EQ(2, data.bucket_info(0).values(0).value_long());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
data = report.value_metrics().data(1);
EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id());
@@ -4990,6 +6867,8 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) {
ASSERT_EQ(2, data.bucket_info_size());
EXPECT_EQ(6, data.bucket_info(0).values(0).value_long());
EXPECT_EQ(4, data.bucket_info(1).values(0).value_long());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).condition_true_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).condition_true_nanos());
}
/*
diff --git a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
index 843d836a2c0b..a20be15d9541 100644
--- a/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
+++ b/cmds/statsd/tests/metrics/parsing_utils/config_update_utils_test.cpp
@@ -27,6 +27,7 @@
#include "src/condition/CombinationConditionTracker.h"
#include "src/condition/SimpleConditionTracker.h"
#include "src/matchers/CombinationAtomMatchingTracker.h"
+#include "src/metrics/GaugeMetricProducer.h"
#include "src/metrics/parsing_utils/metrics_manager_util.h"
#include "tests/statsd_test_util.h"
@@ -34,6 +35,8 @@ using namespace testing;
using android::sp;
using android::os::statsd::Predicate;
using std::map;
+using std::nullopt;
+using std::optional;
using std::set;
using std::unordered_map;
using std::vector;
@@ -61,11 +64,11 @@ vector<sp<MetricProducer>> oldMetricProducers;
unordered_map<int64_t, int> oldMetricProducerMap;
std::vector<sp<AnomalyTracker>> oldAnomalyTrackers;
std::vector<sp<AlarmTracker>> oldAlarmTrackers;
-unordered_map<int, std::vector<int>> conditionToMetricMap;
-unordered_map<int, std::vector<int>> trackerToMetricMap;
-unordered_map<int, std::vector<int>> trackerToConditionMap;
-unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
-unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+unordered_map<int, std::vector<int>> tmpConditionToMetricMap;
+unordered_map<int, std::vector<int>> tmpTrackerToMetricMap;
+unordered_map<int, std::vector<int>> tmpTrackerToConditionMap;
+unordered_map<int, std::vector<int>> tmpActivationAtomTrackerToMetricMap;
+unordered_map<int, std::vector<int>> tmpDeactivationAtomTrackerToMetricMap;
unordered_map<int64_t, int> alertTrackerMap;
vector<int> metricsWithActivation;
map<int64_t, uint64_t> oldStateHashes;
@@ -86,15 +89,16 @@ public:
oldMetricProducerMap.clear();
oldAnomalyTrackers.clear();
oldAlarmTrackers.clear();
- conditionToMetricMap.clear();
- trackerToMetricMap.clear();
- trackerToConditionMap.clear();
- activationAtomTrackerToMetricMap.clear();
- deactivationAtomTrackerToMetricMap.clear();
+ tmpConditionToMetricMap.clear();
+ tmpTrackerToMetricMap.clear();
+ tmpTrackerToConditionMap.clear();
+ tmpActivationAtomTrackerToMetricMap.clear();
+ tmpDeactivationAtomTrackerToMetricMap.clear();
alertTrackerMap.clear();
metricsWithActivation.clear();
oldStateHashes.clear();
noReportMetricIds.clear();
+ StateManager::getInstance().clear();
}
};
@@ -103,12 +107,68 @@ bool initConfig(const StatsdConfig& config) {
key, config, uidMap, pullerManager, anomalyAlarmMonitor, periodicAlarmMonitor,
timeBaseNs, timeBaseNs, allTagIds, oldAtomMatchingTrackers, oldAtomMatchingTrackerMap,
oldConditionTrackers, oldConditionTrackerMap, oldMetricProducers, oldMetricProducerMap,
- oldAnomalyTrackers, oldAlarmTrackers, conditionToMetricMap, trackerToMetricMap,
- trackerToConditionMap, activationAtomTrackerToMetricMap,
- deactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
+ oldAnomalyTrackers, oldAlarmTrackers, tmpConditionToMetricMap, tmpTrackerToMetricMap,
+ tmpTrackerToConditionMap, tmpActivationAtomTrackerToMetricMap,
+ tmpDeactivationAtomTrackerToMetricMap, alertTrackerMap, metricsWithActivation,
oldStateHashes, noReportMetricIds);
}
+EventMetric createEventMetric(string name, int64_t what, optional<int64_t> condition) {
+ EventMetric metric;
+ metric.set_id(StringToId(name));
+ metric.set_what(what);
+ if (condition) {
+ metric.set_condition(condition.value());
+ }
+ return metric;
+}
+
+CountMetric createCountMetric(string name, int64_t what, optional<int64_t> condition,
+ vector<int64_t> states) {
+ CountMetric metric;
+ metric.set_id(StringToId(name));
+ metric.set_what(what);
+ metric.set_bucket(TEN_MINUTES);
+ if (condition) {
+ metric.set_condition(condition.value());
+ }
+ for (const int64_t state : states) {
+ metric.add_slice_by_state(state);
+ }
+ return metric;
+}
+
+GaugeMetric createGaugeMetric(string name, int64_t what, GaugeMetric::SamplingType samplingType,
+ optional<int64_t> condition, optional<int64_t> triggerEvent) {
+ GaugeMetric metric;
+ metric.set_id(StringToId(name));
+ metric.set_what(what);
+ metric.set_bucket(TEN_MINUTES);
+ metric.set_sampling_type(samplingType);
+ if (condition) {
+ metric.set_condition(condition.value());
+ }
+ if (triggerEvent) {
+ metric.set_trigger_event(triggerEvent.value());
+ }
+ metric.mutable_gauge_fields_filter()->set_include_all(true);
+ return metric;
+}
+
+DurationMetric createDurationMetric(string name, int64_t what, optional<int64_t> condition,
+ vector<int64_t> states) {
+ DurationMetric metric;
+ metric.set_id(StringToId(name));
+ metric.set_what(what);
+ metric.set_bucket(TEN_MINUTES);
+ if (condition) {
+ metric.set_condition(condition.value());
+ }
+ for (const int64_t state : states) {
+ metric.add_slice_by_state(state);
+ }
+ return metric;
+}
} // anonymous namespace
TEST_F(ConfigUpdateTest, TestSimpleMatcherPreserve) {
@@ -660,7 +720,6 @@ TEST_F(ConfigUpdateTest, TestCombinationConditionDepsChange) {
TEST_F(ConfigUpdateTest, TestUpdateConditions) {
StatsdConfig config;
-
// Add atom matchers. These are mostly needed for initStatsdConfig
AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
int64_t matcher1Id = matcher1.id();
@@ -734,7 +793,7 @@ TEST_F(ConfigUpdateTest, TestUpdateConditions) {
set<int64_t> replacedMatchers;
replacedMatchers.insert(matcher6Id);
- // Change the condition of simple1 to true.
+ // Change the condition of simple1 to false.
ASSERT_EQ(oldConditionTrackers[0]->getConditionId(), simple1Id);
LogEvent event(/*uid=*/0, /*pid=*/0); // Empty event is fine since there are no dimensions.
// Mark the stop matcher as matched, condition should be false.
@@ -747,7 +806,7 @@ TEST_F(ConfigUpdateTest, TestUpdateConditions) {
EXPECT_EQ(tmpConditionCache[0], ConditionState::kFalse);
EXPECT_EQ(conditionChangeCache[0], true);
- // New combination matcher. Should have an initial condition of true since it is NOT(simple1).
+ // New combination predicate. Should have an initial condition of true since it is NOT(simple1).
Predicate combination4;
combination4.set_id(StringToId("COMBINATION4"));
combination4.mutable_combination()->set_operation(LogicalOperation::NOT);
@@ -888,6 +947,1552 @@ TEST_F(ConfigUpdateTest, TestUpdateConditions) {
UnorderedElementsAre(simple1Index, simple2Index));
EXPECT_THAT(combinationTracker1->mSlicedChildren, IsEmpty());
}
+
+TEST_F(ConfigUpdateTest, TestEventMetricPreserve) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestEventMetricActivationAdded) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ // Add a metric activation, which should change the proto, causing replacement.
+ MetricActivation* activation = config.add_metric_activation();
+ activation->set_metric_id(12345);
+ EventActivation* eventActivation = activation->add_event_activation();
+ eventActivation->set_atom_matcher_id(startMatcher.id());
+ eventActivation->set_ttl_seconds(5);
+
+ unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}};
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestEventMetricWhatChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestEventMetricConditionChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestMetricConditionLinkDepsChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ Predicate linkPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = linkPredicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+ // Doesn't make sense as a real metric definition, but suffices as a separate predicate
+ // From the one in the condition.
+ MetricConditionLink* link = metric->add_links();
+ link->set_condition(linkPredicate.id());
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{linkPredicate.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestEventMetricActivationDepsChange) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ EventMetric* metric = config.add_event_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+
+ MetricActivation* activation = config.add_metric_activation();
+ activation->set_metric_id(12345);
+ EventActivation* eventActivation = activation->add_event_activation();
+ eventActivation->set_atom_matcher_id(startMatcher.id());
+ eventActivation->set_ttl_seconds(5);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap = {{12345, 0}};
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {startMatcher.id()}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCountMetricPreserve) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+ State sliceState = CreateScreenState();
+ *config.add_state() = sliceState;
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+ metric->add_slice_by_state(sliceState.id());
+ metric->set_bucket(ONE_HOUR);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestCountMetricDefinitionChange) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+ metric->set_bucket(ONE_HOUR);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ // Change bucket size, which should change the proto, causing replacement.
+ metric->set_bucket(TEN_MINUTES);
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCountMetricWhatChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+ metric->set_bucket(ONE_HOUR);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCountMetricConditionChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->set_condition(predicate.id());
+ metric->set_bucket(ONE_HOUR);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestCountMetricStateChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ State sliceState = CreateScreenState();
+ *config.add_state() = sliceState;
+
+ CountMetric* metric = config.add_count_metric();
+ metric->set_id(12345);
+ metric->set_what(whatMatcher.id());
+ metric->add_slice_by_state(sliceState.id());
+ metric->set_bucket(ONE_HOUR);
+
+ // Create an initial config.
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{sliceState.id()}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestGaugeMetricPreserve) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ *config.add_gauge_metric() = createGaugeMetric(
+ "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt);
+
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestGaugeMetricDefinitionChange) {
+ StatsdConfig config;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ *config.add_gauge_metric() = createGaugeMetric(
+ "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt);
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Change split bucket on app upgrade, which should change the proto, causing replacement.
+ config.mutable_gauge_metric(0)->set_split_bucket_for_app_upgrade(false);
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestGaugeMetricWhatChanged) {
+ StatsdConfig config;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ *config.add_gauge_metric() = createGaugeMetric(
+ "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt);
+
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {whatMatcher.id()}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestGaugeMetricConditionChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+ AtomMatcher whatMatcher = CreateScreenBrightnessChangedAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ Predicate predicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = predicate;
+
+ *config.add_gauge_metric() = createGaugeMetric(
+ "GAUGE1", whatMatcher.id(), GaugeMetric::RANDOM_ONE_SAMPLE, predicate.id(), nullopt);
+
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{predicate.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestGaugeMetricTriggerEventChanged) {
+ StatsdConfig config;
+ AtomMatcher triggerEvent = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = triggerEvent;
+ AtomMatcher whatMatcher = CreateTemperatureAtomMatcher();
+ *config.add_atom_matcher() = whatMatcher;
+
+ *config.add_gauge_metric() = createGaugeMetric(
+ "GAUGE1", whatMatcher.id(), GaugeMetric::FIRST_N_SAMPLES, nullopt, triggerEvent.id());
+
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {triggerEvent.id()}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricPreserve) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+
+ Predicate what = CreateScreenIsOnPredicate();
+ *config.add_predicate() = what;
+ Predicate condition = CreateScreenIsOffPredicate();
+ *config.add_predicate() = condition;
+
+ State sliceState = CreateScreenState();
+ *config.add_state() = sliceState;
+
+ *config.add_duration_metric() =
+ createDurationMetric("DURATION1", what.id(), condition.id(), {sliceState.id()});
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_PRESERVE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricDefinitionChange) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+
+ Predicate what = CreateScreenIsOnPredicate();
+ *config.add_predicate() = what;
+
+ *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {});
+ EXPECT_TRUE(initConfig(config));
+
+ config.mutable_duration_metric(0)->set_aggregation_type(DurationMetric::MAX_SPARSE);
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(config, oldMetricProducerMap, oldMetricProducers,
+ metricToActivationMap, /*replacedMatchers*/ {},
+ /*replacedConditions=*/{}, /*replacedStates=*/{},
+ metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricWhatChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+
+ Predicate what = CreateScreenIsOnPredicate();
+ *config.add_predicate() = what;
+
+ *config.add_duration_metric() = createDurationMetric("DURATION1", what.id(), nullopt, {});
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{what.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricConditionChanged) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+
+ Predicate what = CreateScreenIsOnPredicate();
+ *config.add_predicate() = what;
+ Predicate condition = CreateScreenIsOffPredicate();
+ *config.add_predicate() = condition;
+
+ *config.add_duration_metric() = createDurationMetric("DURATION", what.id(), condition.id(), {});
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{condition.id()},
+ /*replacedStates=*/{}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestDurationMetricStateChange) {
+ StatsdConfig config;
+ AtomMatcher startMatcher = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = startMatcher;
+ AtomMatcher stopMatcher = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = stopMatcher;
+
+ Predicate what = CreateScreenIsOnPredicate();
+ *config.add_predicate() = what;
+
+ State sliceState = CreateScreenState();
+ *config.add_state() = sliceState;
+
+ *config.add_duration_metric() =
+ createDurationMetric("DURATION1", what.id(), nullopt, {sliceState.id()});
+ EXPECT_TRUE(initConfig(config));
+
+ unordered_map<int64_t, int> metricToActivationMap;
+ vector<UpdateStatus> metricsToUpdate(1, UPDATE_UNKNOWN);
+ EXPECT_TRUE(determineAllMetricUpdateStatuses(
+ config, oldMetricProducerMap, oldMetricProducers, metricToActivationMap,
+ /*replacedMatchers*/ {}, /*replacedConditions=*/{},
+ /*replacedStates=*/{sliceState.id()}, metricsToUpdate));
+ EXPECT_EQ(metricsToUpdate[0], UPDATE_REPLACE);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateEventMetrics) {
+ StatsdConfig config;
+
+ // Add atom matchers/predicates. These are mostly needed for initStatsdConfig
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher();
+ int64_t matcher5Id = matcher5.id();
+ *config.add_atom_matcher() = matcher5;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ Predicate predicate2 = CreateScheduledJobPredicate();
+ int64_t predicate2Id = predicate2.id();
+ *config.add_predicate() = predicate2;
+
+ // Add a few event metrics.
+ // Will be preserved.
+ EventMetric event1 = createEventMetric("EVENT1", matcher1Id, predicate2Id);
+ int64_t event1Id = event1.id();
+ *config.add_event_metric() = event1;
+
+ // Will be replaced.
+ EventMetric event2 = createEventMetric("EVENT2", matcher2Id, nullopt);
+ int64_t event2Id = event2.id();
+ *config.add_event_metric() = event2;
+
+ // Will be replaced.
+ EventMetric event3 = createEventMetric("EVENT3", matcher3Id, nullopt);
+ int64_t event3Id = event3.id();
+ *config.add_event_metric() = event3;
+
+ MetricActivation event3Activation;
+ event3Activation.set_metric_id(event3Id);
+ EventActivation* eventActivation = event3Activation.add_event_activation();
+ eventActivation->set_atom_matcher_id(matcher5Id);
+ eventActivation->set_ttl_seconds(5);
+ *config.add_metric_activation() = event3Activation;
+
+ // Will be replaced.
+ EventMetric event4 = createEventMetric("EVENT4", matcher4Id, predicate1Id);
+ int64_t event4Id = event4.id();
+ *config.add_event_metric() = event4;
+
+ // Will be deleted.
+ EventMetric event5 = createEventMetric("EVENT5", matcher5Id, nullopt);
+ int64_t event5Id = event5.id();
+ *config.add_event_metric() = event5;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+ sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), oldMetricProducers.size() + 1);
+
+ // Add a condition to event2, causing it to be replaced.
+ event2.set_condition(predicate1Id);
+
+ // Mark matcher 5 as replaced. Causes event3 to be replaced.
+ set<int64_t> replacedMatchers;
+ replacedMatchers.insert(matcher5Id);
+
+ // Mark predicate 1 as replaced. Causes event4 to be replaced.
+ set<int64_t> replacedConditions;
+ replacedConditions.insert(predicate1Id);
+
+ // Fake that predicate 2 is true.
+ ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id);
+ oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0);
+ EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue);
+
+ // New event metric. Should have an initial condition of true since it depends on predicate2.
+ EventMetric event6 = createEventMetric("EVENT6", matcher3Id, predicate2Id);
+ int64_t event6Id = event6.id();
+ MetricActivation event6Activation;
+ event6Activation.set_metric_id(event6Id);
+ eventActivation = event6Activation.add_event_activation();
+ eventActivation->set_atom_matcher_id(matcher5Id);
+ eventActivation->set_ttl_seconds(20);
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher5Index = 0;
+ newAtomMatchingTrackerMap[matcher5Id] = 0;
+ const int matcher4Index = 1;
+ newAtomMatchingTrackerMap[matcher4Id] = 1;
+ const int matcher3Index = 2;
+ newAtomMatchingTrackerMap[matcher3Id] = 2;
+ const int matcher2Index = 3;
+ newAtomMatchingTrackerMap[matcher2Id] = 3;
+ const int matcher1Index = 4;
+ newAtomMatchingTrackerMap[matcher1Id] = 4;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate2Index = 0;
+ newConditionTrackerMap[predicate2Id] = 0;
+ const int predicate1Index = 1;
+ newConditionTrackerMap[predicate1Id] = 1;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(2);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ // Fake that predicate2 is true.
+ vector<ConditionState> conditionCache = {ConditionState::kTrue, ConditionState::kUnknown};
+
+ StatsdConfig newConfig;
+ *newConfig.add_event_metric() = event6;
+ const int event6Index = 0;
+ *newConfig.add_event_metric() = event3;
+ const int event3Index = 1;
+ *newConfig.add_event_metric() = event1;
+ const int event1Index = 2;
+ *newConfig.add_event_metric() = event4;
+ const int event4Index = 3;
+ *newConfig.add_event_metric() = event2;
+ const int event2Index = 4;
+ *newConfig.add_metric_activation() = event3Activation;
+ *newConfig.add_metric_activation() = event6Activation;
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+ newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
+ /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+ newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {event1Id, event1Index}, {event2Id, event2Index}, {event3Id, event3Index},
+ {event4Id, event4Index}, {event6Id, event6Index},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 5);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(event1Id)],
+ newMetricProducers[newMetricProducerMap.at(event1Id)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event2Id)],
+ newMetricProducers[newMetricProducerMap.at(event2Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event3Id)],
+ newMetricProducers[newMetricProducerMap.at(event3Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(event4Id)],
+ newMetricProducers[newMetricProducerMap.at(event4Id)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 2);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(event2Index, event4Index));
+ const vector<int>& condition2Metrics = conditionToMetricMap[predicate2Index];
+ EXPECT_THAT(condition2Metrics, UnorderedElementsAre(event1Index, event6Index));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 4);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(event1Index));
+ const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
+ EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(event2Index));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(event3Index, event6Index));
+ const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+ EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(event4Index));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 1);
+ EXPECT_THAT(activationAtomTrackerToMetricMap[matcher5Index],
+ UnorderedElementsAre(event3Index, event6Index));
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 2);
+ EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(event3Index, event6Index));
+
+ // Verify tracker indices/ids/conditions are correct.
+ EXPECT_EQ(newMetricProducers[event1Index]->getMetricId(), event1Id);
+ EXPECT_EQ(newMetricProducers[event1Index]->mConditionTrackerIndex, predicate2Index);
+ EXPECT_EQ(newMetricProducers[event1Index]->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(newMetricProducers[event2Index]->getMetricId(), event2Id);
+ EXPECT_EQ(newMetricProducers[event2Index]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[event2Index]->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(newMetricProducers[event3Index]->getMetricId(), event3Id);
+ EXPECT_EQ(newMetricProducers[event3Index]->mConditionTrackerIndex, -1);
+ EXPECT_EQ(newMetricProducers[event3Index]->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(newMetricProducers[event4Index]->getMetricId(), event4Id);
+ EXPECT_EQ(newMetricProducers[event4Index]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[event4Index]->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(newMetricProducers[event6Index]->getMetricId(), event6Id);
+ EXPECT_EQ(newMetricProducers[event6Index]->mConditionTrackerIndex, predicate2Index);
+ EXPECT_EQ(newMetricProducers[event6Index]->mCondition, ConditionState::kTrue);
+
+ sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
+ EXPECT_NE(newConditionWizard, oldConditionWizard);
+ EXPECT_EQ(newConditionWizard->getStrongCount(), newMetricProducers.size() + 1);
+ oldMetricProducers.clear();
+ // Only reference to the old wizard should be the one in the test.
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateCountMetrics) {
+ StatsdConfig config;
+
+ // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig.
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ AtomMatcher matcher5 = CreateBatterySaverModeStartAtomMatcher();
+ int64_t matcher5Id = matcher5.id();
+ *config.add_atom_matcher() = matcher5;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ State state1 = CreateScreenStateWithOnOffMap(0x123, 0x321);
+ int64_t state1Id = state1.id();
+ *config.add_state() = state1;
+
+ State state2 = CreateScreenState();
+ int64_t state2Id = state2.id();
+ *config.add_state() = state2;
+
+ // Add a few count metrics.
+ // Will be preserved.
+ CountMetric count1 = createCountMetric("COUNT1", matcher1Id, predicate1Id, {state1Id});
+ int64_t count1Id = count1.id();
+ *config.add_count_metric() = count1;
+
+ // Will be replaced.
+ CountMetric count2 = createCountMetric("COUNT2", matcher2Id, nullopt, {});
+ int64_t count2Id = count2.id();
+ *config.add_count_metric() = count2;
+
+ // Will be replaced.
+ CountMetric count3 = createCountMetric("COUNT3", matcher3Id, nullopt, {});
+ int64_t count3Id = count3.id();
+ *config.add_count_metric() = count3;
+
+ // Will be replaced.
+ CountMetric count4 = createCountMetric("COUNT4", matcher4Id, nullopt, {state2Id});
+ int64_t count4Id = count4.id();
+ *config.add_count_metric() = count4;
+
+ // Will be deleted.
+ CountMetric count5 = createCountMetric("COUNT5", matcher5Id, nullopt, {});
+ int64_t count5Id = count5.id();
+ *config.add_count_metric() = count5;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Change bucket size of count2, causing it to be replaced.
+ count2.set_bucket(ONE_HOUR);
+
+ // Mark matcher 3 as replaced. Causes count3 to be replaced.
+ set<int64_t> replacedMatchers;
+ replacedMatchers.insert(matcher3Id);
+
+ // Mark state 2 as replaced and change the state to be about a different atom.
+ // Causes count4 to be replaced.
+ set<int64_t> replacedStates;
+ replacedStates.insert(state2Id);
+ state2.set_atom_id(util::BATTERY_SAVER_MODE_STATE_CHANGED);
+
+ // Fake that predicate 1 is true for count metric 1.
+ ASSERT_EQ(oldMetricProducers[0]->getMetricId(), count1Id);
+ oldMetricProducers[0]->onConditionChanged(true, /*timestamp=*/0);
+ EXPECT_EQ(oldMetricProducers[0]->mCondition, ConditionState::kTrue);
+
+ EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 1);
+ // Tell the StateManager that the screen is on.
+ unique_ptr<LogEvent> event =
+ CreateScreenStateChangedEvent(0, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ StateManager::getInstance().onLogEvent(*event);
+
+ // New count metric. Should have an initial condition of true since it depends on predicate1.
+ CountMetric count6 = createCountMetric("EVENT6", matcher2Id, predicate1Id, {state1Id});
+ int64_t count6Id = count6.id();
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher5Index = 0;
+ newAtomMatchingTrackerMap[matcher5Id] = 0;
+ const int matcher4Index = 1;
+ newAtomMatchingTrackerMap[matcher4Id] = 1;
+ const int matcher3Index = 2;
+ newAtomMatchingTrackerMap[matcher3Id] = 2;
+ const int matcher2Index = 3;
+ newAtomMatchingTrackerMap[matcher2Id] = 3;
+ const int matcher1Index = 4;
+ newAtomMatchingTrackerMap[matcher1Id] = 4;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate1Index = 0;
+ newConditionTrackerMap[predicate1Id] = 0;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(1);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ // Fake that predicate1 is true for all new metrics.
+ vector<ConditionState> conditionCache = {ConditionState::kTrue};
+
+ StatsdConfig newConfig;
+ *newConfig.add_count_metric() = count6;
+ const int count6Index = 0;
+ *newConfig.add_count_metric() = count3;
+ const int count3Index = 1;
+ *newConfig.add_count_metric() = count1;
+ const int count1Index = 2;
+ *newConfig.add_count_metric() = count4;
+ const int count4Index = 3;
+ *newConfig.add_count_metric() = count2;
+ const int count2Index = 4;
+
+ *newConfig.add_state() = state1;
+ *newConfig.add_state() = state2;
+
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
+ map<int64_t, uint64_t> stateProtoHashes;
+ EXPECT_TRUE(initStates(newConfig, stateAtomIdMap, allStateGroupMaps, stateProtoHashes));
+ EXPECT_EQ(stateAtomIdMap[state2Id], util::BATTERY_SAVER_MODE_STATE_CHANGED);
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{},
+ newConditionTrackers, conditionCache, stateAtomIdMap, allStateGroupMaps, replacedStates,
+ oldMetricProducerMap, oldMetricProducers, newMetricProducerMap, newMetricProducers,
+ conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {count1Id, count1Index}, {count2Id, count2Index}, {count3Id, count3Index},
+ {count4Id, count4Index}, {count6Id, count6Index},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 5);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(count1Id)],
+ newMetricProducers[newMetricProducerMap.at(count1Id)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count2Id)],
+ newMetricProducers[newMetricProducerMap.at(count2Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count3Id)],
+ newMetricProducers[newMetricProducerMap.at(count3Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(count4Id)],
+ newMetricProducers[newMetricProducerMap.at(count4Id)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 1);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(count1Index, count6Index));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 4);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(count1Index));
+ const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
+ EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(count2Index, count6Index));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(count3Index));
+ const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+ EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(count4Index));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 0);
+
+ // Verify tracker indices/ids/conditions/states are correct.
+ EXPECT_EQ(newMetricProducers[count1Index]->getMetricId(), count1Id);
+ EXPECT_EQ(newMetricProducers[count1Index]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[count1Index]->mCondition, ConditionState::kTrue);
+ EXPECT_THAT(newMetricProducers[count1Index]->getSlicedStateAtoms(),
+ UnorderedElementsAre(util::SCREEN_STATE_CHANGED));
+ EXPECT_EQ(newMetricProducers[count2Index]->getMetricId(), count2Id);
+ EXPECT_EQ(newMetricProducers[count2Index]->mConditionTrackerIndex, -1);
+ EXPECT_EQ(newMetricProducers[count2Index]->mCondition, ConditionState::kTrue);
+ EXPECT_TRUE(newMetricProducers[count2Index]->getSlicedStateAtoms().empty());
+ EXPECT_EQ(newMetricProducers[count3Index]->getMetricId(), count3Id);
+ EXPECT_EQ(newMetricProducers[count3Index]->mConditionTrackerIndex, -1);
+ EXPECT_EQ(newMetricProducers[count3Index]->mCondition, ConditionState::kTrue);
+ EXPECT_TRUE(newMetricProducers[count3Index]->getSlicedStateAtoms().empty());
+ EXPECT_EQ(newMetricProducers[count4Index]->getMetricId(), count4Id);
+ EXPECT_EQ(newMetricProducers[count4Index]->mConditionTrackerIndex, -1);
+ EXPECT_EQ(newMetricProducers[count4Index]->mCondition, ConditionState::kTrue);
+ EXPECT_THAT(newMetricProducers[count4Index]->getSlicedStateAtoms(),
+ UnorderedElementsAre(util::BATTERY_SAVER_MODE_STATE_CHANGED));
+ EXPECT_EQ(newMetricProducers[count6Index]->getMetricId(), count6Id);
+ EXPECT_EQ(newMetricProducers[count6Index]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[count6Index]->mCondition, ConditionState::kTrue);
+ EXPECT_THAT(newMetricProducers[count6Index]->getSlicedStateAtoms(),
+ UnorderedElementsAre(util::SCREEN_STATE_CHANGED));
+
+ oldMetricProducers.clear();
+ // Ensure that the screen state StateTracker did not get deleted and replaced.
+ EXPECT_EQ(StateManager::getInstance().getStateTrackersCount(), 2);
+ FieldValue screenState;
+ StateManager::getInstance().getStateValue(util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY,
+ &screenState);
+ EXPECT_EQ(screenState.mValue.int_value, android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateGaugeMetrics) {
+ StatsdConfig config;
+
+ // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig.
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateTemperatureAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ AtomMatcher matcher5 = CreateSimpleAtomMatcher("SubsystemSleep", util::SUBSYSTEM_SLEEP_STATE);
+ int64_t matcher5Id = matcher5.id();
+ *config.add_atom_matcher() = matcher5;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ // Add a few gauge metrics.
+ // Will be preserved.
+ GaugeMetric gauge1 = createGaugeMetric("GAUGE1", matcher4Id, GaugeMetric::FIRST_N_SAMPLES,
+ predicate1Id, matcher1Id);
+ int64_t gauge1Id = gauge1.id();
+ *config.add_gauge_metric() = gauge1;
+
+ // Will be replaced.
+ GaugeMetric gauge2 =
+ createGaugeMetric("GAUGE2", matcher1Id, GaugeMetric::FIRST_N_SAMPLES, nullopt, nullopt);
+ int64_t gauge2Id = gauge2.id();
+ *config.add_gauge_metric() = gauge2;
+
+ // Will be replaced.
+ GaugeMetric gauge3 = createGaugeMetric("GAUGE3", matcher5Id, GaugeMetric::FIRST_N_SAMPLES,
+ nullopt, matcher3Id);
+ int64_t gauge3Id = gauge3.id();
+ *config.add_gauge_metric() = gauge3;
+
+ // Will be replaced.
+ GaugeMetric gauge4 = createGaugeMetric("GAUGE4", matcher3Id, GaugeMetric::RANDOM_ONE_SAMPLE,
+ predicate1Id, nullopt);
+ int64_t gauge4Id = gauge4.id();
+ *config.add_gauge_metric() = gauge4;
+
+ // Will be deleted.
+ GaugeMetric gauge5 =
+ createGaugeMetric("GAUGE5", matcher2Id, GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, {});
+ int64_t gauge5Id = gauge5.id();
+ *config.add_gauge_metric() = gauge5;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+ sp<EventMatcherWizard> oldMatcherWizard =
+ static_cast<GaugeMetricProducer*>(oldMetricProducers[0].get())->mEventMatcherWizard;
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 6);
+
+ // Change gauge2, causing it to be replaced.
+ gauge2.set_max_num_gauge_atoms_per_bucket(50);
+
+ // Mark matcher 3 as replaced. Causes gauge3 and gauge4 to be replaced.
+ set<int64_t> replacedMatchers = {matcher3Id};
+
+ // New gauge metric.
+ GaugeMetric gauge6 = createGaugeMetric("GAUGE6", matcher5Id, GaugeMetric::FIRST_N_SAMPLES,
+ predicate1Id, matcher3Id);
+ int64_t gauge6Id = gauge6.id();
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher5Index = 0;
+ newAtomMatchingTrackerMap[matcher5Id] = 0;
+ const int matcher4Index = 1;
+ newAtomMatchingTrackerMap[matcher4Id] = 1;
+ const int matcher3Index = 2;
+ newAtomMatchingTrackerMap[matcher3Id] = 2;
+ const int matcher2Index = 3;
+ newAtomMatchingTrackerMap[matcher2Id] = 3;
+ const int matcher1Index = 4;
+ newAtomMatchingTrackerMap[matcher1Id] = 4;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(5);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate1Index = 0;
+ newConditionTrackerMap[predicate1Id] = 0;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(1);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ // Say that predicate1 is unknown since the initial condition never changed.
+ vector<ConditionState> conditionCache = {ConditionState::kUnknown};
+
+ StatsdConfig newConfig;
+ *newConfig.add_gauge_metric() = gauge6;
+ const int gauge6Index = 0;
+ *newConfig.add_gauge_metric() = gauge3;
+ const int gauge3Index = 1;
+ *newConfig.add_gauge_metric() = gauge1;
+ const int gauge1Index = 2;
+ *newConfig.add_gauge_metric() = gauge4;
+ const int gauge4Index = 3;
+ *newConfig.add_gauge_metric() = gauge2;
+ const int gauge2Index = 4;
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{},
+ newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
+ /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+ newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {gauge1Id, gauge1Index}, {gauge2Id, gauge2Index}, {gauge3Id, gauge3Index},
+ {gauge4Id, gauge4Index}, {gauge6Id, gauge6Index},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 5);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(gauge1Id)],
+ newMetricProducers[newMetricProducerMap.at(gauge1Id)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge2Id)],
+ newMetricProducers[newMetricProducerMap.at(gauge2Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge3Id)],
+ newMetricProducers[newMetricProducerMap.at(gauge3Id)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gauge4Id)],
+ newMetricProducers[newMetricProducerMap.at(gauge4Id)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 1);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(gauge1Index, gauge4Index, gauge6Index));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 4);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(gauge1Index, gauge2Index));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gauge3Index, gauge4Index, gauge6Index));
+ const vector<int>& matcher4Metrics = trackerToMetricMap[matcher4Index];
+ EXPECT_THAT(matcher4Metrics, UnorderedElementsAre(gauge1Index));
+ const vector<int>& matcher5Metrics = trackerToMetricMap[matcher5Index];
+ EXPECT_THAT(matcher5Metrics, UnorderedElementsAre(gauge3Index, gauge6Index));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 0);
+
+ // Verify tracker indices/ids/conditions/states are correct.
+ GaugeMetricProducer* gaugeProducer1 =
+ static_cast<GaugeMetricProducer*>(newMetricProducers[gauge1Index].get());
+ EXPECT_EQ(gaugeProducer1->getMetricId(), gauge1Id);
+ EXPECT_EQ(gaugeProducer1->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(gaugeProducer1->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(gaugeProducer1->mWhatMatcherIndex, matcher4Index);
+ GaugeMetricProducer* gaugeProducer2 =
+ static_cast<GaugeMetricProducer*>(newMetricProducers[gauge2Index].get());
+ EXPECT_EQ(gaugeProducer2->getMetricId(), gauge2Id);
+ EXPECT_EQ(gaugeProducer2->mConditionTrackerIndex, -1);
+ EXPECT_EQ(gaugeProducer2->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(gaugeProducer2->mWhatMatcherIndex, matcher1Index);
+ GaugeMetricProducer* gaugeProducer3 =
+ static_cast<GaugeMetricProducer*>(newMetricProducers[gauge3Index].get());
+ EXPECT_EQ(gaugeProducer3->getMetricId(), gauge3Id);
+ EXPECT_EQ(gaugeProducer3->mConditionTrackerIndex, -1);
+ EXPECT_EQ(gaugeProducer3->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(gaugeProducer3->mWhatMatcherIndex, matcher5Index);
+ GaugeMetricProducer* gaugeProducer4 =
+ static_cast<GaugeMetricProducer*>(newMetricProducers[gauge4Index].get());
+ EXPECT_EQ(gaugeProducer4->getMetricId(), gauge4Id);
+ EXPECT_EQ(gaugeProducer4->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(gaugeProducer4->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(gaugeProducer4->mWhatMatcherIndex, matcher3Index);
+ GaugeMetricProducer* gaugeProducer6 =
+ static_cast<GaugeMetricProducer*>(newMetricProducers[gauge6Index].get());
+ EXPECT_EQ(gaugeProducer6->getMetricId(), gauge6Id);
+ EXPECT_EQ(gaugeProducer6->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(gaugeProducer6->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(gaugeProducer6->mWhatMatcherIndex, matcher5Index);
+
+ sp<EventMatcherWizard> newMatcherWizard = gaugeProducer1->mEventMatcherWizard;
+ EXPECT_NE(newMatcherWizard, oldMatcherWizard);
+ EXPECT_EQ(newMatcherWizard->getStrongCount(), 6);
+ oldMetricProducers.clear();
+ // Only reference to the old wizard should be the one in the test.
+ EXPECT_EQ(oldMatcherWizard->getStrongCount(), 1);
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateMetricActivations) {
+ StatsdConfig config;
+ // Add atom matchers
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateStartScheduledJobAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ AtomMatcher matcher4 = CreateFinishScheduledJobAtomMatcher();
+ int64_t matcher4Id = matcher4.id();
+ *config.add_atom_matcher() = matcher4;
+
+ // Add an event metric with multiple activations.
+ EventMetric event1 = createEventMetric("EVENT1", matcher1Id, nullopt);
+ int64_t event1Id = event1.id();
+ *config.add_event_metric() = event1;
+
+ int64_t matcher2TtlSec = 2, matcher3TtlSec = 3, matcher4TtlSec = 4;
+ MetricActivation metricActivation;
+ metricActivation.set_metric_id(event1Id);
+ EventActivation* activation = metricActivation.add_event_activation();
+ activation->set_atom_matcher_id(matcher2Id);
+ activation->set_ttl_seconds(matcher2TtlSec);
+ activation->set_activation_type(ACTIVATE_IMMEDIATELY);
+ activation->set_deactivation_atom_matcher_id(matcher1Id);
+ activation = metricActivation.add_event_activation();
+ activation->set_atom_matcher_id(matcher3Id);
+ activation->set_ttl_seconds(matcher3TtlSec);
+ activation->set_activation_type(ACTIVATE_ON_BOOT);
+ activation->set_deactivation_atom_matcher_id(matcher1Id);
+ activation = metricActivation.add_event_activation();
+ activation->set_atom_matcher_id(matcher4Id);
+ activation->set_ttl_seconds(matcher4TtlSec);
+ activation->set_activation_type(ACTIVATE_IMMEDIATELY);
+ activation->set_deactivation_atom_matcher_id(matcher2Id);
+ *config.add_metric_activation() = metricActivation;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Activate some of the event activations.
+ ASSERT_EQ(oldMetricProducers[0]->getMetricId(), event1Id);
+ int64_t matcher2StartNs = 12345;
+ oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher2Id], matcher2StartNs);
+ int64_t matcher3StartNs = 23456;
+ oldMetricProducers[0]->activate(oldAtomMatchingTrackerMap[matcher3Id], matcher3StartNs);
+ EXPECT_TRUE(oldMetricProducers[0]->isActive());
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher4Index = 0;
+ newAtomMatchingTrackerMap[matcher4Id] = 0;
+ const int matcher3Index = 1;
+ newAtomMatchingTrackerMap[matcher3Id] = 1;
+ const int matcher2Index = 2;
+ newAtomMatchingTrackerMap[matcher2Id] = 2;
+ const int matcher1Index = 3;
+ newAtomMatchingTrackerMap[matcher1Id] = 3;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(4);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+ set<int64_t> replacedMatchers;
+
+ unordered_map<int64_t, int> newConditionTrackerMap;
+ vector<sp<ConditionTracker>> newConditionTrackers;
+ set<int64_t> replacedConditions;
+ vector<ConditionState> conditionCache;
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ EXPECT_TRUE(updateMetrics(
+ key, config, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, replacedConditions,
+ newConditionTrackers, conditionCache, /*stateAtomIdMap=*/{}, /*allStateGroupMaps=*/{},
+ /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+ newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 3);
+ EXPECT_THAT(activationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0));
+ EXPECT_THAT(activationAtomTrackerToMetricMap[matcher3Index], UnorderedElementsAre(0));
+ EXPECT_THAT(activationAtomTrackerToMetricMap[matcher4Index], UnorderedElementsAre(0));
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 2);
+ EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher1Index], UnorderedElementsAre(0, 0));
+ EXPECT_THAT(deactivationAtomTrackerToMetricMap[matcher2Index], UnorderedElementsAre(0));
+ ASSERT_EQ(metricsWithActivation.size(), 1);
+ EXPECT_THAT(metricsWithActivation, UnorderedElementsAre(0));
+
+ // Verify mEventActivation and mEventDeactivation map of the producer.
+ sp<MetricProducer> producer = newMetricProducers[0];
+ EXPECT_TRUE(producer->isActive());
+ ASSERT_EQ(producer->mEventActivationMap.size(), 3);
+ shared_ptr<Activation> matcher2Activation = producer->mEventActivationMap[matcher2Index];
+ EXPECT_EQ(matcher2Activation->ttl_ns, matcher2TtlSec * NS_PER_SEC);
+ EXPECT_EQ(matcher2Activation->activationType, ACTIVATE_IMMEDIATELY);
+ EXPECT_EQ(matcher2Activation->state, kActive);
+ EXPECT_EQ(matcher2Activation->start_ns, matcher2StartNs);
+ shared_ptr<Activation> matcher3Activation = producer->mEventActivationMap[matcher3Index];
+ EXPECT_EQ(matcher3Activation->ttl_ns, matcher3TtlSec * NS_PER_SEC);
+ EXPECT_EQ(matcher3Activation->activationType, ACTIVATE_ON_BOOT);
+ EXPECT_EQ(matcher3Activation->state, kActiveOnBoot);
+ shared_ptr<Activation> matcher4Activation = producer->mEventActivationMap[matcher4Index];
+ EXPECT_EQ(matcher4Activation->ttl_ns, matcher4TtlSec * NS_PER_SEC);
+ EXPECT_EQ(matcher4Activation->activationType, ACTIVATE_IMMEDIATELY);
+ EXPECT_EQ(matcher4Activation->state, kNotActive);
+
+ ASSERT_EQ(producer->mEventDeactivationMap.size(), 2);
+ EXPECT_THAT(producer->mEventDeactivationMap[matcher1Index],
+ UnorderedElementsAre(matcher2Activation, matcher3Activation));
+ EXPECT_THAT(producer->mEventDeactivationMap[matcher2Index],
+ UnorderedElementsAre(matcher4Activation));
+}
+
+TEST_F(ConfigUpdateTest, TestUpdateMetricsMultipleTypes) {
+ StatsdConfig config;
+ // Add atom matchers/predicates/states. These are mostly needed for initStatsdConfig
+ AtomMatcher matcher1 = CreateScreenTurnedOnAtomMatcher();
+ int64_t matcher1Id = matcher1.id();
+ *config.add_atom_matcher() = matcher1;
+
+ AtomMatcher matcher2 = CreateScreenTurnedOffAtomMatcher();
+ int64_t matcher2Id = matcher2.id();
+ *config.add_atom_matcher() = matcher2;
+
+ AtomMatcher matcher3 = CreateTemperatureAtomMatcher();
+ int64_t matcher3Id = matcher3.id();
+ *config.add_atom_matcher() = matcher3;
+
+ Predicate predicate1 = CreateScreenIsOnPredicate();
+ int64_t predicate1Id = predicate1.id();
+ *config.add_predicate() = predicate1;
+
+ // Add a few count metrics.
+ // Will be preserved.
+ CountMetric countMetric = createCountMetric("COUNT1", matcher1Id, predicate1Id, {});
+ int64_t countMetricId = countMetric.id();
+ *config.add_count_metric() = countMetric;
+
+ // Will be replaced since matcher2 is replaced.
+ EventMetric eventMetric = createEventMetric("EVENT1", matcher2Id, nullopt);
+ int64_t eventMetricId = eventMetric.id();
+ *config.add_event_metric() = eventMetric;
+
+ // Will be replaced because the definition changes - a predicate is added.
+ GaugeMetric gaugeMetric = createGaugeMetric("GAUGE1", matcher3Id,
+ GaugeMetric::RANDOM_ONE_SAMPLE, nullopt, nullopt);
+ int64_t gaugeMetricId = gaugeMetric.id();
+ *config.add_gauge_metric() = gaugeMetric;
+
+ EXPECT_TRUE(initConfig(config));
+
+ // Used later to ensure the condition wizard is replaced. Get it before doing the update.
+ sp<ConditionWizard> oldConditionWizard = oldMetricProducers[0]->mWizard;
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), 4);
+
+ // Mark matcher 2 as replaced. Causes eventMetric to be replaced.
+ set<int64_t> replacedMatchers;
+ replacedMatchers.insert(matcher2Id);
+
+ // Add predicate1 as a predicate on gaugeMetric, causing it to be replaced.
+ gaugeMetric.set_condition(predicate1Id);
+
+ // Map the matchers and predicates in reverse order to force the indices to change.
+ std::unordered_map<int64_t, int> newAtomMatchingTrackerMap;
+ const int matcher3Index = 0;
+ newAtomMatchingTrackerMap[matcher3Id] = 0;
+ const int matcher2Index = 1;
+ newAtomMatchingTrackerMap[matcher2Id] = 1;
+ const int matcher1Index = 2;
+ newAtomMatchingTrackerMap[matcher1Id] = 2;
+ // Use the existing matchers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<AtomMatchingTracker>> newAtomMatchingTrackers(3);
+ std::reverse_copy(oldAtomMatchingTrackers.begin(), oldAtomMatchingTrackers.end(),
+ newAtomMatchingTrackers.begin());
+
+ std::unordered_map<int64_t, int> newConditionTrackerMap;
+ const int predicate1Index = 0;
+ newConditionTrackerMap[predicate1Id] = 0;
+ // Use the existing conditionTrackers. A bit hacky, but saves code and we don't rely on them.
+ vector<sp<ConditionTracker>> newConditionTrackers(1);
+ std::reverse_copy(oldConditionTrackers.begin(), oldConditionTrackers.end(),
+ newConditionTrackers.begin());
+ vector<ConditionState> conditionCache = {ConditionState::kUnknown};
+
+ StatsdConfig newConfig;
+ *newConfig.add_count_metric() = countMetric;
+ const int countMetricIndex = 0;
+ *newConfig.add_event_metric() = eventMetric;
+ const int eventMetricIndex = 1;
+ *newConfig.add_gauge_metric() = gaugeMetric;
+ const int gaugeMetricIndex = 2;
+
+ // Output data structures to validate.
+ unordered_map<int64_t, int> newMetricProducerMap;
+ vector<sp<MetricProducer>> newMetricProducers;
+ unordered_map<int, vector<int>> conditionToMetricMap;
+ unordered_map<int, vector<int>> trackerToMetricMap;
+ set<int64_t> noReportMetricIds;
+ unordered_map<int, vector<int>> activationAtomTrackerToMetricMap;
+ unordered_map<int, vector<int>> deactivationAtomTrackerToMetricMap;
+ vector<int> metricsWithActivation;
+ EXPECT_TRUE(updateMetrics(
+ key, newConfig, /*timeBaseNs=*/123, /*currentTimeNs=*/12345, new StatsPullerManager(),
+ oldAtomMatchingTrackerMap, newAtomMatchingTrackerMap, replacedMatchers,
+ newAtomMatchingTrackers, newConditionTrackerMap, /*replacedConditions=*/{},
+ newConditionTrackers, conditionCache, /*stateAtomIdMap*/ {}, /*allStateGroupMaps=*/{},
+ /*replacedStates=*/{}, oldMetricProducerMap, oldMetricProducers, newMetricProducerMap,
+ newMetricProducers, conditionToMetricMap, trackerToMetricMap, noReportMetricIds,
+ activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+ metricsWithActivation));
+
+ unordered_map<int64_t, int> expectedMetricProducerMap = {
+ {countMetricId, countMetricIndex},
+ {eventMetricId, eventMetricIndex},
+ {gaugeMetricId, gaugeMetricIndex},
+ };
+ EXPECT_THAT(newMetricProducerMap, ContainerEq(expectedMetricProducerMap));
+
+ // Make sure preserved metrics are the same.
+ ASSERT_EQ(newMetricProducers.size(), 3);
+ EXPECT_EQ(oldMetricProducers[oldMetricProducerMap.at(countMetricId)],
+ newMetricProducers[newMetricProducerMap.at(countMetricId)]);
+
+ // Make sure replaced metrics are different.
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(eventMetricId)],
+ newMetricProducers[newMetricProducerMap.at(eventMetricId)]);
+ EXPECT_NE(oldMetricProducers[oldMetricProducerMap.at(gaugeMetricId)],
+ newMetricProducers[newMetricProducerMap.at(gaugeMetricId)]);
+
+ // Verify the conditionToMetricMap.
+ ASSERT_EQ(conditionToMetricMap.size(), 1);
+ const vector<int>& condition1Metrics = conditionToMetricMap[predicate1Index];
+ EXPECT_THAT(condition1Metrics, UnorderedElementsAre(countMetricIndex, gaugeMetricIndex));
+
+ // Verify the trackerToMetricMap.
+ ASSERT_EQ(trackerToMetricMap.size(), 3);
+ const vector<int>& matcher1Metrics = trackerToMetricMap[matcher1Index];
+ EXPECT_THAT(matcher1Metrics, UnorderedElementsAre(countMetricIndex));
+ const vector<int>& matcher2Metrics = trackerToMetricMap[matcher2Index];
+ EXPECT_THAT(matcher2Metrics, UnorderedElementsAre(eventMetricIndex));
+ const vector<int>& matcher3Metrics = trackerToMetricMap[matcher3Index];
+ EXPECT_THAT(matcher3Metrics, UnorderedElementsAre(gaugeMetricIndex));
+
+ // Verify event activation/deactivation maps.
+ ASSERT_EQ(activationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(deactivationAtomTrackerToMetricMap.size(), 0);
+ ASSERT_EQ(metricsWithActivation.size(), 0);
+
+ // Verify tracker indices/ids/conditions are correct.
+ EXPECT_EQ(newMetricProducers[countMetricIndex]->getMetricId(), countMetricId);
+ EXPECT_EQ(newMetricProducers[countMetricIndex]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[countMetricIndex]->mCondition, ConditionState::kUnknown);
+ EXPECT_EQ(newMetricProducers[eventMetricIndex]->getMetricId(), eventMetricId);
+ EXPECT_EQ(newMetricProducers[eventMetricIndex]->mConditionTrackerIndex, -1);
+ EXPECT_EQ(newMetricProducers[eventMetricIndex]->mCondition, ConditionState::kTrue);
+ EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->getMetricId(), gaugeMetricId);
+ EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mConditionTrackerIndex, predicate1Index);
+ EXPECT_EQ(newMetricProducers[gaugeMetricIndex]->mCondition, ConditionState::kUnknown);
+
+ sp<ConditionWizard> newConditionWizard = newMetricProducers[0]->mWizard;
+ EXPECT_NE(newConditionWizard, oldConditionWizard);
+ EXPECT_EQ(newConditionWizard->getStrongCount(), 4);
+ oldMetricProducers.clear();
+ // Only reference to the old wizard should be the one in the test.
+ EXPECT_EQ(oldConditionWizard->getStrongCount(), 1);
+}
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 25729abf9e05..e3139eb798f7 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -20,6 +20,7 @@ package android.accessibilityservice;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
@@ -28,11 +29,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
@@ -85,13 +88,16 @@ public final class AccessibilityGestureEvent implements Parcelable {
/** @hide */
@IntDef(prefix = { "GESTURE_" }, value = {
GESTURE_2_FINGER_SINGLE_TAP,
+ GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD,
GESTURE_2_FINGER_DOUBLE_TAP,
GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_2_FINGER_TRIPLE_TAP,
GESTURE_3_FINGER_SINGLE_TAP,
+ GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD,
GESTURE_3_FINGER_DOUBLE_TAP,
GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
GESTURE_3_FINGER_TRIPLE_TAP,
+ GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD,
GESTURE_DOUBLE_TAP,
GESTURE_DOUBLE_TAP_AND_HOLD,
GESTURE_SWIPE_UP,
@@ -180,15 +186,21 @@ public final class AccessibilityGestureEvent implements Parcelable {
private static String eventTypeToString(int eventType) {
switch (eventType) {
case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
+ case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD:
+ return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD";
case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
+ case GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD";
case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+ case GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD:
+ return "GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD";
case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index b5b0ce3a65f3..7c6d4484ee12 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -421,6 +421,15 @@ public abstract class AccessibilityService extends Service {
/** The user has performed a three-finger double tap and hold gesture on the touch screen. */
public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+ /** The user has performed a two-finger single-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43;
+
+ /** The user has performed a three-finger single-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44;
+
+ /** The user has performed a three-finger triple-tap and hold gesture on the touch screen. */
+ public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45;
+
/** The user has performed a two-finger double tap and hold gesture on the touch screen. */
public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 70ca49d67930..30efb48cc188 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -100,6 +100,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.Executor;
/**
* <p>
@@ -4752,31 +4753,43 @@ public class ActivityManager {
}
/**
- * Register with {@link HomeVisibilityObserver} with ActivityManager.
- * TODO: b/144351078 expose as SystemApi
+ * Register to be notified when the visibility of the home screen changes.
+ *
+ * @param executor The executor on which the listener should be called.
+ * @param listener The listener that is called when home visibility changes.
* @hide
*/
- public void registerHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
- Preconditions.checkNotNull(observer);
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ public void addHomeVisibilityListener(@NonNull Executor executor,
+ @NonNull HomeVisibilityListener listener) {
+ Preconditions.checkNotNull(listener);
+ Preconditions.checkNotNull(executor);
try {
- observer.init(mContext, this);
- getService().registerProcessObserver(observer.mObserver);
+ listener.init(mContext, executor, this);
+ getService().registerProcessObserver(listener.mObserver);
// Notify upon first registration.
- observer.onHomeVisibilityChanged(observer.mIsHomeActivityVisible);
+ executor.execute(() ->
+ listener.onHomeVisibilityChanged(listener.mIsHomeActivityVisible));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Unregister with {@link HomeVisibilityObserver} with ActivityManager.
- * TODO: b/144351078 expose as SystemApi
+ * Removes a listener that was previously added with {@link #addHomeVisibilityListener}.
+ *
+ * @param listener The listener that was previously added.
* @hide
*/
- public void unregisterHomeVisibilityObserver(@NonNull HomeVisibilityObserver observer) {
- Preconditions.checkNotNull(observer);
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ public void removeHomeVisibilityListener(@NonNull HomeVisibilityListener listener) {
+ Preconditions.checkNotNull(listener);
try {
- getService().unregisterProcessObserver(observer.mObserver);
+ getService().unregisterProcessObserver(listener.mObserver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4796,4 +4809,19 @@ public class ActivityManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Holds the AM lock for the specified amount of milliseconds.
+ * This is intended for use by the tests that need to imitate lock contention.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
+ public void holdLock(int durationMs) {
+ try {
+ getService().holdLock(durationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 04108ff0bdcc..1e70e40bb2ea 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -46,39 +46,20 @@ public abstract class ActivityManagerInternal {
// Access modes for handleIncomingUser.
- /**
- * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
- */
public static final int ALLOW_NON_FULL = 0;
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
- * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in the same profile
- * group.
+ * if in the same profile group.
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
*/
- public static final int ALLOW_NON_FULL_IN_PROFILE_OR_FULL = 1;
- /**
- * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
- * only.
- */
+ public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
public static final int ALLOW_FULL_ONLY = 2;
/**
* Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
- * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in the same profile group.
+ * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
* Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
*/
- public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL = 3;
- /**
- * Requires {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES},
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}, or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} if in same profile group,
- * otherwise {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. (so this is an extension
- * to {@link #ALLOW_NON_FULL})
- */
- public static final int ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL = 4;
+ public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
/**
* Verify that calling app has access to the given provider.
@@ -122,6 +103,11 @@ public abstract class ActivityManagerInternal {
IBinder whitelistToken, long duration);
/**
+ * Returns the flags set for a {@link PendingIntent}.
+ */
+ public abstract int getPendingIntentFlags(IIntentSender target);
+
+ /**
* Allows a {@link PendingIntent} to start activities from background.
*/
public abstract void setPendingIntentAllowBgActivityStarts(
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a61159a23c2c..3748a33568e2 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1284,10 +1284,10 @@ public class ActivityOptions {
}
/**
- * Sets the id of the display where activity should be launched.
- * An app can launch activities on public displays or private displays that are owned by the app
- * or where an app already has activities. Otherwise, trying to launch on a private display
- * or providing an invalid display id will result in an exception.
+ * Sets the id of the display where the activity should be launched.
+ * An app can launch activities on public displays or displays where the app already has
+ * activities. Otherwise, trying to launch on a private display or providing an invalid display
+ * id will result in an exception.
* <p>
* Setting launch display id will be ignored on devices that don't have
* {@link android.content.pm.PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}.
@@ -1910,6 +1910,8 @@ public class ActivityOptions {
public static final int TYPE_NOTIFICATION = 2;
/** Launched from lockscreen, including notification while the device is locked. */
public static final int TYPE_LOCKSCREEN = 3;
+ /** Launched from recents gesture handler. */
+ public static final int TYPE_RECENTS_ANIMATION = 4;
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
TYPE_LAUNCHER,
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 3e4d5eee34fe..d0a0e30bad18 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -211,23 +211,23 @@ public class ActivityTaskManager {
}
/**
- * Removes stacks in the windowing modes from the system if they are of activity type
+ * Removes root tasks in the windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException {
+ public void removeRootTasksInWindowingModes(@NonNull int[] windowingModes) {
try {
- getService().removeStacksInWindowingModes(windowingModes);
+ getService().removeRootTasksInWindowingModes(windowingModes);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- /** Removes stack of the activity types from the system. */
+ /** Removes root tasks of the activity types from the system. */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException {
+ public void removeRootTasksWithActivityTypes(@NonNull int[] activityTypes) {
try {
- getService().removeStacksWithActivityTypes(activityTypes);
+ getService().removeRootTasksWithActivityTypes(activityTypes);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -310,15 +310,15 @@ public class ActivityTaskManager {
}
/**
- * Moves the top activity in the input stackId to the pinned stack.
- * @param stackId Id of stack to move the top activity to pinned stack.
- * @param bounds Bounds to use for pinned stack.
- * @return True if the top activity of stack was successfully moved to the pinned stack.
+ * Moves the top activity in the input rootTaskId to the pinned root task.
+ * @param rootTaskId Id of root task to move the top activity to pinned root task.
+ * @param bounds Bounds to use for pinned root task.
+ * @return True if the top activity of root task was successfully moved to the pinned root task.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
+ public boolean moveTopActivityToPinnedRootTask(int rootTaskId, @NonNull Rect bounds) {
try {
- return getService().moveTopActivityToPinnedStack(stackId, bounds);
+ return getService().moveTopActivityToPinnedRootTask(rootTaskId, bounds);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -350,15 +350,15 @@ public class ActivityTaskManager {
}
/**
- * Move task to stack with given id.
+ * Move task to root task with given id.
* @param taskId Id of the task to move.
- * @param stackId Id of the stack for task moving.
+ * @param rootTaskId Id of the rootTask for task moving.
* @param toTop Whether the given task should shown to top of stack.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+ public void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop) {
try {
- getService().moveTaskToStack(taskId, stackId, toTop);
+ getService().moveTaskToRootTask(taskId, rootTaskId, toTop);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -380,13 +380,13 @@ public class ActivityTaskManager {
/**
* Resize docked stack & its task to given stack & task bounds.
- * @param stackBounds Bounds to resize stack.
+ * @param rootTaskBounds Bounds to resize stack.
* @param taskBounds Bounds to resize task.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public void resizeDockedStack(Rect stackBounds, Rect taskBounds) {
+ public void resizePrimarySplitScreen(@NonNull Rect rootTaskBounds, @NonNull Rect taskBounds) {
try {
- getService().resizeDockedStack(stackBounds, taskBounds, null, null, null);
+ getService().resizePrimarySplitScreen(rootTaskBounds, taskBounds, null, null, null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 582c7b602838..7a4a204990b8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -615,7 +615,7 @@ public final class ActivityThread extends ClientTransactionHandler {
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+ activity.mMainThread.handleActivityConfigurationChanged(this, overrideConfig,
newDisplayId);
};
}
@@ -3145,12 +3145,25 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ /**
+ * Returns {@code true} if the {@link android.app.ActivityManager.ProcessState} of the current
+ * process is cached.
+ */
+ @VisibleForTesting
+ public boolean isCachedProcessState() {
+ synchronized (mAppThread) {
+ return mLastProcessState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+ }
+ }
+
@Override
public void updateProcessState(int processState, boolean fromIpc) {
+ final boolean wasCached;
synchronized (mAppThread) {
if (mLastProcessState == processState) {
return;
}
+ wasCached = isCachedProcessState();
mLastProcessState = processState;
// Defer the top state for VM to avoid aggressive JIT compilation affecting activity
// launch time.
@@ -3167,6 +3180,24 @@ public final class ActivityThread extends ClientTransactionHandler {
+ (fromIpc ? " (from ipc" : ""));
}
}
+
+ // Handle the pending configuration if the process state is changed from cached to
+ // non-cached. Except the case where there is a launching activity because the
+ // LaunchActivityItem will handle it.
+ if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) {
+ final Configuration pendingConfig;
+ synchronized (mResourcesManager) {
+ pendingConfig = mPendingConfiguration;
+ }
+ if (pendingConfig == null) {
+ return;
+ }
+ if (Looper.myLooper() == mH.getLooper()) {
+ handleConfigurationChanged(pendingConfig);
+ } else {
+ sendMessage(H.CONFIGURATION_CHANGED, pendingConfig);
+ }
+ }
}
/** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
@@ -3272,12 +3303,6 @@ public final class ActivityThread extends ClientTransactionHandler {
sendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- @Override
- public void handleFixedRotationAdjustments(@NonNull IBinder token,
- @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
- handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */);
- }
-
/**
* Applies the rotation adjustments to override display information in resources belong to the
* provided token. If the token is activity token, the adjustments also apply to application
@@ -3287,51 +3312,39 @@ public final class ActivityThread extends ClientTransactionHandler {
* @param fixedRotationAdjustments The information to override the display adjustments of
* corresponding resources. If it is null, the exiting override
* will be cleared.
- * @param overrideConfig The override configuration of activity. It is used to override
- * application configuration. If it is non-null, it means the token is
- * confirmed as activity token. Especially when launching new activity,
- * {@link #mActivities} hasn't put the new token.
*/
- private void handleFixedRotationAdjustments(@NonNull IBinder token,
- @Nullable FixedRotationAdjustments fixedRotationAdjustments,
- @Nullable Configuration overrideConfig) {
- // The element of application configuration override is set only if the application
- // adjustments are needed, because activity already has its own override configuration.
- final Configuration[] appConfigOverride;
- final Consumer<DisplayAdjustments> override;
- if (fixedRotationAdjustments != null) {
- appConfigOverride = new Configuration[1];
- override = displayAdjustments -> {
- displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments);
- if (appConfigOverride[0] != null) {
- displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]);
- }
- };
- } else {
- appConfigOverride = null;
- override = null;
- }
+ @Override
+ public void handleFixedRotationAdjustments(@NonNull IBinder token,
+ @Nullable FixedRotationAdjustments fixedRotationAdjustments) {
+ final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
+ ? displayAdjustments -> displayAdjustments
+ .setFixedRotationAdjustments(fixedRotationAdjustments)
+ : null;
if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
// No resources are associated with the token.
return;
}
- if (overrideConfig == null) {
- final ActivityClientRecord r = mActivities.get(token);
- if (r == null) {
- // It is not an activity token. Nothing to do for application.
- return;
- }
- overrideConfig = r.overrideConfig;
- }
- if (appConfigOverride != null) {
- appConfigOverride[0] = overrideConfig;
+ if (mActivities.get(token) == null) {
+ // Nothing to do for application if it is not an activity token.
+ return;
}
- // Apply the last override to application resources for compatibility. Because the Resources
- // of Display can be from application, e.g.
- // applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
- // and the deprecated usage:
- // applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+ overrideApplicationDisplayAdjustments(token, override);
+ }
+
+ /**
+ * Applies the last override to application resources for compatibility. Because the Resources
+ * of Display can be from application, e.g.
+ * applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
+ * and the deprecated usage:
+ * applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
+ *
+ * @param token The owner and target of the override.
+ * @param override The display adjustments override for application resources. If it is null,
+ * the override of the token will be removed and pop the last one to use.
+ */
+ private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
+ @Nullable Consumer<DisplayAdjustments> override) {
final Consumer<DisplayAdjustments> appOverride;
if (mActiveRotationAdjustments == null) {
mActiveRotationAdjustments = new ArrayList<>(2);
@@ -3476,13 +3489,9 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
- final ActivityClientRecord r = mActivities.get(token);
+ public void handleStartActivity(ActivityClientRecord r,
+ PendingTransactionActions pendingActions) {
final Activity activity = r.activity;
- if (r.activity == null) {
- // TODO(lifecycler): What do we do in this case?
- return;
- }
if (!r.stopped) {
throw new IllegalStateException("Can't start activity that is not stopped.");
}
@@ -3564,8 +3573,13 @@ public final class ActivityThread extends ClientTransactionHandler {
// The rotation adjustments must be applied before creating the activity, so the activity
// can get the adjusted display info during creation.
if (r.mPendingFixedRotationAdjustments != null) {
- handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
- r.overrideConfig);
+ // The adjustments should have been set by handleLaunchActivity, so the last one is the
+ // override for activity resources.
+ if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) {
+ mResourcesManager.overrideTokenDisplayAdjustments(r.token,
+ mActiveRotationAdjustments.get(
+ mActiveRotationAdjustments.size() - 1).second);
+ }
r.mPendingFixedRotationAdjustments = null;
}
@@ -3604,6 +3618,13 @@ public final class ActivityThread extends ClientTransactionHandler {
mProfiler.startProfiling();
}
+ if (r.mPendingFixedRotationAdjustments != null) {
+ // The rotation adjustments must be applied before handling configuration, so process
+ // level display metrics can be adjusted.
+ overrideApplicationDisplayAdjustments(r.token, adjustments ->
+ adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments));
+ }
+
// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);
@@ -3688,12 +3709,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleNewIntent(IBinder token, List<ReferrerIntent> intents) {
- final ActivityClientRecord r = mActivities.get(token);
- if (r == null) {
- return;
- }
-
+ public void handleNewIntent(ActivityClientRecord r, List<ReferrerIntent> intents) {
checkAndBlockForNetworkAccess();
deliverNewIntents(r, intents);
}
@@ -3882,13 +3898,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handlePictureInPictureRequested(IBinder token) {
- final ActivityClientRecord r = mActivities.get(token);
- if (r == null) {
- Log.w(TAG, "Activity to request PIP to no longer exists");
- return;
- }
-
+ public void handlePictureInPictureRequested(ActivityClientRecord r) {
final boolean receivedByApp = r.activity.onPictureInPictureRequested();
if (!receivedByApp) {
// Previous recommendation was for apps to enter picture-in-picture in
@@ -4418,22 +4428,21 @@ public final class ActivityThread extends ClientTransactionHandler {
/**
* Resume the activity.
- * @param token Target activity token.
+ * @param r Target activity record.
* @param finalStateRequest Flag indicating if this is part of final state resolution for a
* transaction.
* @param reason Reason for performing the action.
*
- * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
+ * @return {@code true} that was resumed, {@code false} otherwise.
*/
@VisibleForTesting
- public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
+ public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
String reason) {
- final ActivityClientRecord r = mActivities.get(token);
if (localLOGV) {
Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
}
- if (r == null || r.activity.mFinished) {
- return null;
+ if (r.activity.mFinished) {
+ return false;
}
if (r.getLifecycleState() == ON_RESUME) {
if (!finalStateRequest) {
@@ -4447,7 +4456,7 @@ public final class ActivityThread extends ClientTransactionHandler {
// handle two resume requests for the final state. For cases other than this
// one, we don't expect it to happen.
}
- return null;
+ return false;
}
if (finalStateRequest) {
r.hideForNow = false;
@@ -4478,7 +4487,7 @@ public final class ActivityThread extends ClientTransactionHandler {
+ r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
- return r;
+ return true;
}
static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
@@ -4499,20 +4508,19 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
- String reason) {
+ public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
+ boolean isForward, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
- final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
- if (r == null) {
- // We didn't actually resume the activity, so skipping any follow-up actions.
+ // skip below steps for double-resume and r.mFinish = true case.
+ if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
- if (mActivitiesToBeDestroyed.containsKey(token)) {
+ if (mActivitiesToBeDestroyed.containsKey(r.token)) {
// Although the activity is resumed, it is going to be destroyed. So the following
// UI operations are unnecessary and also prevents exception because its token may
// be gone that window manager cannot recognize it. All necessary cleanup actions
@@ -4630,13 +4638,8 @@ public final class ActivityThread extends ClientTransactionHandler {
@Override
- public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
- ActivityClientRecord r = mActivities.get(token);
- if (r == null || r.activity == null) {
- Slog.w(TAG, "Not found target activity to report position change for token: " + token);
- return;
- }
-
+ public void handleTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
+ String reason) {
if (DEBUG_ORDER) {
Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
}
@@ -4669,23 +4672,20 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+ public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
- ActivityClientRecord r = mActivities.get(token);
- if (r != null) {
- if (userLeaving) {
- performUserLeavingActivity(r);
- }
+ if (userLeaving) {
+ performUserLeavingActivity(r);
+ }
- r.activity.mConfigChangeFlags |= configChanges;
- performPauseActivity(r, finished, reason, pendingActions);
+ r.activity.mConfigChangeFlags |= configChanges;
+ performPauseActivity(r, finished, reason, pendingActions);
- // Make sure any pending writes are now committed.
- if (r.isPreHoneycomb()) {
- QueuedWork.waitToFinish();
- }
- mSomeActivitiesChanged = true;
+ // Make sure any pending writes are now committed.
+ if (r.isPreHoneycomb()) {
+ QueuedWork.waitToFinish();
}
+ mSomeActivitiesChanged = true;
}
final void performUserLeavingActivity(ActivityClientRecord r) {
@@ -4782,8 +4782,11 @@ public final class ActivityThread extends ClientTransactionHandler {
r.setState(ON_PAUSE);
}
+ // TODO(b/127877792): Make LocalActivityManager call performStopActivityInner. We cannot remove
+ // this since it's a high usage hidden API.
/** Called from {@link LocalActivityManager}. */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 127877792,
+ publicAlternatives = "{@code N/A}")
final void performStopActivity(IBinder token, boolean saveState, String reason) {
ActivityClientRecord r = mActivities.get(token);
performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
@@ -4824,44 +4827,42 @@ public final class ActivityThread extends ClientTransactionHandler {
private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
boolean saveState, boolean finalStateRequest, String reason) {
if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
- if (r != null) {
- if (r.stopped) {
- if (r.activity.mFinished) {
- // If we are finishing, we won't call onResume() in certain
- // cases. So here we likewise don't want to call onStop()
- // if the activity isn't resumed.
- return;
- }
- if (!finalStateRequest) {
- final RuntimeException e = new RuntimeException(
- "Performing stop of activity that is already stopped: "
- + r.intent.getComponent().toShortString());
- Slog.e(TAG, e.getMessage(), e);
- Slog.e(TAG, r.getStateString());
- }
+ if (r.stopped) {
+ if (r.activity.mFinished) {
+ // If we are finishing, we won't call onResume() in certain
+ // cases. So here we likewise don't want to call onStop()
+ // if the activity isn't resumed.
+ return;
+ }
+ if (!finalStateRequest) {
+ final RuntimeException e = new RuntimeException(
+ "Performing stop of activity that is already stopped: "
+ + r.intent.getComponent().toShortString());
+ Slog.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, r.getStateString());
}
+ }
- // One must first be paused before stopped...
- performPauseActivityIfNeeded(r, reason);
+ // One must first be paused before stopped...
+ performPauseActivityIfNeeded(r, reason);
- if (info != null) {
- try {
- // First create a thumbnail for the activity...
- // For now, don't create the thumbnail here; we are
- // doing that by doing a screen snapshot.
- info.setDescription(r.activity.onCreateDescription());
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to save state of activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
+ if (info != null) {
+ try {
+ // First create a thumbnail for the activity...
+ // For now, don't create the thumbnail here; we are
+ // doing that by doing a screen snapshot.
+ info.setDescription(r.activity.onCreateDescription());
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to save state of activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
}
}
-
- callActivityOnStop(r, saveState, reason);
}
+
+ callActivityOnStop(r, saveState, reason);
}
/**
@@ -4928,9 +4929,8 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleStopActivity(IBinder token, int configChanges,
+ public void handleStopActivity(ActivityClientRecord r, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
- final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
@@ -4966,8 +4966,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void performRestartActivity(IBinder token, boolean start) {
- ActivityClientRecord r = mActivities.get(token);
+ public void performRestartActivity(ActivityClientRecord r, boolean start) {
if (r.stopped) {
r.activity.performRestart(start, "performRestartActivity");
if (start) {
@@ -5054,110 +5053,98 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleSendResult(IBinder token, List<ResultInfo> results, String reason) {
- ActivityClientRecord r = mActivities.get(token);
+ public void handleSendResult(ActivityClientRecord r, List<ResultInfo> results, String reason) {
if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
- if (r != null) {
- final boolean resumed = !r.paused;
- if (!r.activity.mFinished && r.activity.mDecor != null
- && r.hideForNow && resumed) {
- // We had hidden the activity because it started another
- // one... we have gotten a result back and we are not
- // paused, so make sure our window is visible.
- updateVisibility(r, true);
- }
- if (resumed) {
- try {
- // Now we are idle.
- r.activity.mCalled = false;
- mInstrumentation.callActivityOnPause(r.activity);
- if (!r.activity.mCalled) {
- throw new SuperNotCalledException(
- "Activity " + r.intent.getComponent().toShortString()
- + " did not call through to super.onPause()");
- }
- } catch (SuperNotCalledException e) {
- throw e;
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to pause activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
+ final boolean resumed = !r.paused;
+ if (!r.activity.mFinished && r.activity.mDecor != null
+ && r.hideForNow && resumed) {
+ // We had hidden the activity because it started another
+ // one... we have gotten a result back and we are not
+ // paused, so make sure our window is visible.
+ updateVisibility(r, true);
+ }
+ if (resumed) {
+ try {
+ // Now we are idle.
+ r.activity.mCalled = false;
+ mInstrumentation.callActivityOnPause(r.activity);
+ if (!r.activity.mCalled) {
+ throw new SuperNotCalledException(
+ "Activity " + r.intent.getComponent().toShortString()
+ + " did not call through to super.onPause()");
+ }
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException(
+ "Unable to pause activity "
+ + r.intent.getComponent().toShortString()
+ + ": " + e.toString(), e);
}
- }
- checkAndBlockForNetworkAccess();
- deliverResults(r, results, reason);
- if (resumed) {
- r.activity.performResume(false, reason);
}
}
+ checkAndBlockForNetworkAccess();
+ deliverResults(r, results, reason);
+ if (resumed) {
+ r.activity.performResume(false, reason);
+ }
}
/** Core implementation of activity destroy call. */
- ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
+ void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
- ActivityClientRecord r = mActivities.get(token);
Class<? extends Activity> activityClass = null;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
- if (r != null) {
- activityClass = r.activity.getClass();
- r.activity.mConfigChangeFlags |= configChanges;
- if (finishing) {
- r.activity.mFinished = true;
- }
+ activityClass = r.activity.getClass();
+ r.activity.mConfigChangeFlags |= configChanges;
+ if (finishing) {
+ r.activity.mFinished = true;
+ }
- performPauseActivityIfNeeded(r, "destroy");
+ performPauseActivityIfNeeded(r, "destroy");
- if (!r.stopped) {
- callActivityOnStop(r, false /* saveState */, "destroy");
- }
- if (getNonConfigInstance) {
- try {
- r.lastNonConfigurationInstances
- = r.activity.retainNonConfigurationInstances();
- } catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to retain activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
- }
- }
+ if (!r.stopped) {
+ callActivityOnStop(r, false /* saveState */, "destroy");
+ }
+ if (getNonConfigInstance) {
try {
- r.activity.mCalled = false;
- mInstrumentation.callActivityOnDestroy(r.activity);
- if (!r.activity.mCalled) {
- throw new SuperNotCalledException(
- "Activity " + safeToComponentShortString(r.intent) +
- " did not call through to super.onDestroy()");
- }
- if (r.window != null) {
- r.window.closeAllPanels();
- }
- } catch (SuperNotCalledException e) {
- throw e;
+ r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to destroy activity " + safeToComponentShortString(r.intent)
- + ": " + e.toString(), e);
+ throw new RuntimeException("Unable to retain activity "
+ + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
}
}
- r.setState(ON_DESTROY);
- mLastReportedWindowingMode.remove(r.activity.getActivityToken());
}
+ try {
+ r.activity.mCalled = false;
+ mInstrumentation.callActivityOnDestroy(r.activity);
+ if (!r.activity.mCalled) {
+ throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+ + " did not call through to super.onDestroy()");
+ }
+ if (r.window != null) {
+ r.window.closeAllPanels();
+ }
+ } catch (SuperNotCalledException e) {
+ throw e;
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(r.activity, e)) {
+ throw new RuntimeException("Unable to destroy activity "
+ + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
+ }
+ }
+ r.setState(ON_DESTROY);
+ mLastReportedWindowingMode.remove(r.activity.getActivityToken());
schedulePurgeIdler();
// updatePendingActivityConfiguration() reads from mActivities to update
// ActivityClientRecord which runs in a different thread. Protect modifications to
// mActivities to avoid race.
synchronized (mResourcesManager) {
- mActivities.remove(token);
+ mActivities.remove(r.token);
}
StrictMode.decrementExpectedActivityCount(activityClass);
- return r;
}
private static String safeToComponentShortString(Intent intent) {
@@ -5171,70 +5158,65 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
- ActivityClientRecord r = performDestroyActivity(token, finishing,
- configChanges, getNonConfigInstance, reason);
- if (r != null) {
- cleanUpPendingRemoveWindows(r, finishing);
- WindowManager wm = r.activity.getWindowManager();
- View v = r.activity.mDecor;
- if (v != null) {
- if (r.activity.mVisibleFromServer) {
- mNumVisibleActivities--;
- }
- IBinder wtoken = v.getWindowToken();
- if (r.activity.mWindowAdded) {
- if (r.mPreserveWindow) {
- // Hold off on removing this until the new activity's
- // window is being added.
- r.mPendingRemoveWindow = r.window;
- r.mPendingRemoveWindowManager = wm;
- // We can only keep the part of the view hierarchy that we control,
- // everything else must be removed, because it might not be able to
- // behave properly when activity is relaunching.
- r.window.clearContentView();
- } else {
- wm.removeViewImmediate(v);
- }
- }
- if (wtoken != null && r.mPendingRemoveWindow == null) {
- WindowManagerGlobal.getInstance().closeAll(wtoken,
- r.activity.getClass().getName(), "Activity");
- } else if (r.mPendingRemoveWindow != null) {
- // We're preserving only one window, others should be closed so app views
- // will be detached before the final tear down. It should be done now because
- // some components (e.g. WebView) rely on detach callbacks to perform receiver
- // unregister and other cleanup.
- WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
- r.activity.getClass().getName(), "Activity");
+ performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+ cleanUpPendingRemoveWindows(r, finishing);
+ WindowManager wm = r.activity.getWindowManager();
+ View v = r.activity.mDecor;
+ if (v != null) {
+ if (r.activity.mVisibleFromServer) {
+ mNumVisibleActivities--;
+ }
+ IBinder wtoken = v.getWindowToken();
+ if (r.activity.mWindowAdded) {
+ if (r.mPreserveWindow) {
+ // Hold off on removing this until the new activity's window is being added.
+ r.mPendingRemoveWindow = r.window;
+ r.mPendingRemoveWindowManager = wm;
+ // We can only keep the part of the view hierarchy that we control,
+ // everything else must be removed, because it might not be able to
+ // behave properly when activity is relaunching.
+ r.window.clearContentView();
+ } else {
+ wm.removeViewImmediate(v);
}
- r.activity.mDecor = null;
- }
- if (r.mPendingRemoveWindow == null) {
- // If we are delaying the removal of the activity window, then
- // we can't clean up all windows here. Note that we can't do
- // so later either, which means any windows that aren't closed
- // by the app will leak. Well we try to warning them a lot
- // about leaking windows, because that is a bug, so if they are
- // using this recreate facility then they get to live with leaks.
- WindowManagerGlobal.getInstance().closeAll(token,
- r.activity.getClass().getName(), "Activity");
}
-
- // Mocked out contexts won't be participating in the normal
- // process lifecycle, but if we're running with a proper
- // ApplicationContext we need to have it tear down things
- // cleanly.
- Context c = r.activity.getBaseContext();
- if (c instanceof ContextImpl) {
- ((ContextImpl) c).scheduleFinalCleanup(
+ if (wtoken != null && r.mPendingRemoveWindow == null) {
+ WindowManagerGlobal.getInstance().closeAll(wtoken,
+ r.activity.getClass().getName(), "Activity");
+ } else if (r.mPendingRemoveWindow != null) {
+ // We're preserving only one window, others should be closed so app views
+ // will be detached before the final tear down. It should be done now because
+ // some components (e.g. WebView) rely on detach callbacks to perform receiver
+ // unregister and other cleanup.
+ WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
r.activity.getClass().getName(), "Activity");
}
+ r.activity.mDecor = null;
+ }
+ if (r.mPendingRemoveWindow == null) {
+ // If we are delaying the removal of the activity window, then
+ // we can't clean up all windows here. Note that we can't do
+ // so later either, which means any windows that aren't closed
+ // by the app will leak. Well we try to warning them a lot
+ // about leaking windows, because that is a bug, so if they are
+ // using this recreate facility then they get to live with leaks.
+ WindowManagerGlobal.getInstance().closeAll(r.token,
+ r.activity.getClass().getName(), "Activity");
+ }
+
+ // Mocked out contexts won't be participating in the normal
+ // process lifecycle, but if we're running with a proper
+ // ApplicationContext we need to have it tear down things
+ // cleanly.
+ Context c = r.activity.getBaseContext();
+ if (c instanceof ContextImpl) {
+ ((ContextImpl) c).scheduleFinalCleanup(r.activity.getClass().getName(), "Activity");
}
if (finishing) {
try {
- ActivityTaskManager.getService().activityDestroyed(token);
+ ActivityTaskManager.getService().activityDestroyed(r.token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
@@ -5456,7 +5438,7 @@ public final class ActivityThread extends ClientTransactionHandler {
callActivityOnStop(r, true /* saveState */, reason);
}
- handleDestroyActivity(r.token, false, configChanges, true, reason);
+ handleDestroyActivity(r, false, configChanges, true, reason);
r.activity = null;
r.window = null;
@@ -5484,12 +5466,10 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
- public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+ public void reportRelaunch(ActivityClientRecord r, PendingTransactionActions pendingActions) {
try {
- ActivityTaskManager.getService().activityRelaunched(token);
- final ActivityClientRecord r = mActivities.get(token);
- if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
- && r.window != null) {
+ ActivityTaskManager.getService().activityRelaunched(r.token);
+ if (pendingActions.shouldReportRelaunchToWindowManager() && r.window != null) {
r.window.reportActivityRelaunched();
}
} catch (RemoteException e) {
@@ -5648,13 +5628,7 @@ public final class ActivityThread extends ClientTransactionHandler {
*/
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId) {
- if (activity == null) {
- throw new IllegalArgumentException("No activity provided.");
- }
final IBinder activityToken = activity.getActivityToken();
- if (activityToken == null) {
- throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
- }
// WindowConfiguration differences aren't considered as public, check it separately.
// multi-window / pip mode changes, if any, should be sent before the configuration
@@ -5745,6 +5719,12 @@ public final class ActivityThread extends ClientTransactionHandler {
@Override
public void handleConfigurationChanged(Configuration config) {
+ if (isCachedProcessState()) {
+ updatePendingConfiguration(config);
+ // If the process is in a cached state, delay the handling until the process is no
+ // longer cached.
+ return;
+ }
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = config.densityDpi;
handleConfigurationChanged(config, null /* compat */);
@@ -5787,7 +5767,15 @@ public final class ActivityThread extends ClientTransactionHandler {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
- mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
+ final Resources appResources = mInitialApplication.getResources();
+ if (appResources.hasOverrideDisplayAdjustments()) {
+ // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments,
+ // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated
+ // configuration also needs to set to the adjustments for consistency.
+ appResources.getDisplayAdjustments().getConfiguration().updateFrom(config);
+ }
+ mResourcesManager.applyConfigurationToResourcesLocked(config, compat,
+ appResources.getDisplayAdjustments());
updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
mResourcesManager.getConfiguration().getLocales());
@@ -5953,20 +5941,8 @@ public final class ActivityThread extends ClientTransactionHandler {
* processing any configurations older than {@code overrideConfig}.
*/
@Override
- public void updatePendingActivityConfiguration(IBinder activityToken,
+ public void updatePendingActivityConfiguration(ActivityClientRecord r,
Configuration overrideConfig) {
- final ActivityClientRecord r;
- synchronized (mResourcesManager) {
- r = mActivities.get(activityToken);
- }
-
- if (r == null) {
- if (DEBUG_CONFIGURATION) {
- Slog.w(TAG, "Not found target activity to update its pending config.");
- }
- return;
- }
-
synchronized (r) {
if (r.mPendingOverrideConfig != null
&& !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
@@ -5986,21 +5962,14 @@ public final class ActivityThread extends ClientTransactionHandler {
* if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
* a newer config than {@code overrideConfig}.
*
- * @param activityToken Target activity token.
+ * @param r Target activity record.
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
@Override
- public void handleActivityConfigurationChanged(IBinder activityToken,
+ public void handleActivityConfigurationChanged(ActivityClientRecord r,
@NonNull Configuration overrideConfig, int displayId) {
- ActivityClientRecord r = mActivities.get(activityToken);
- // Check input params.
- if (r == null || r.activity == null) {
- if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
- return;
- }
-
synchronized (r) {
if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) {
if (DEBUG_CONFIGURATION) {
@@ -7470,7 +7439,8 @@ public final class ActivityThread extends ClientTransactionHandler {
// We need to apply this change to the resources immediately, because upon returning
// the view hierarchy will be informed about it.
if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
- null /* compat */)) {
+ null /* compat */,
+ mInitialApplication.getResources().getDisplayAdjustments())) {
updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
mResourcesManager.getConfiguration().getLocales());
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 04f72f6dc71d..c5bc3564ceab 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -81,8 +81,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@@ -5021,8 +5019,7 @@ public class AppOpsManager {
* @hide
*/
public static double round(double value) {
- final BigDecimal decimalScale = new BigDecimal(value);
- return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+ return Math.floor(value + 0.5);
}
@Override
@@ -6741,14 +6738,10 @@ public class AppOpsManager {
*/
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(int code, int uid, @Mode int mode) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
mService.setUidMode(code, uid, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@@ -6766,7 +6759,11 @@ public class AppOpsManager {
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
- setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
+ try {
+ mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/** @hide */
@@ -6795,14 +6792,10 @@ public class AppOpsManager {
@TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(int code, int uid, String packageName, @Mode int mode) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@@ -6822,7 +6815,11 @@ public class AppOpsManager {
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(@NonNull String op, int uid, @Nullable String packageName,
@Mode int mode) {
- setMode(strOpToOp(op), uid, packageName, mode);
+ try {
+ mService.setMode(strOpToOp(op), uid, packageName, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -7298,14 +7295,10 @@ public class AppOpsManager {
* @hide
*/
public int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
return mService.checkOperationRaw(op, uid, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@@ -7344,21 +7337,48 @@ public class AppOpsManager {
}
/**
- * Make note of an application performing an operation. Note that you must pass
- * in both the uid and name of the application to be checked; this function will verify
- * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time.
+ * Make note of an application performing an operation and check if the application is allowed
+ * to perform it.
*
* <p>If this is a check that is not preceding the protected operation, use
* {@link #unsafeCheckOp} instead.
*
+ * <p>The identity of the package the app-op is noted for is specified by the
+ * {@code uid} and {@code packageName} parameters. If this is noted for a regular app both
+ * should be set and the package needs to be part of the uid. In the very rare case that an
+ * app-op is noted for an entity that does not have a package name, the package can be
+ * {@code null}. As it is possible that a single process contains more than one package the
+ * {@code packageName} should be {@link Context#getPackageName() read} from the context of the
+ * caller of the API (in the app process) that eventually triggers this check. If this op is
+ * not noted for a running process the {@code packageName} cannot be read from the context, but
+ * it should be clear which package the note is for.
+ *
+ * <p>If the {@code uid} and {@code packageName} do not match this return
+ * {@link #MODE_IGNORED}.
+ *
+ * <p>Beside the access check this method also records the access. While the access check is
+ * based on {@code uid} and/or {@code packageName} the access recording is done based on the
+ * {@code packageName} and {@code attributionTag}. The {@code attributionTag} should be
+ * {@link Context#getAttributionTag() read} from the same context the package name is read from.
+ * In the case the check is not related to an API call, the {@code attributionTag} should be
+ * {@code null}. Please note that e.g. registering a callback for later is still an API call and
+ * the code should store the attribution tag along the package name for being used in this
+ * method later.
+ *
+ * <p>The {@code message} parameter only needs to be set when this method is <ul>not</ul>
+ * called in a two-way binder call from the client. In this case the message is a free form text
+ * that is meant help the app developer determine what part of the app's code triggered the
+ * note. This message is passed back to the app in the
+ * {@link OnOpNotedCallback#onAsyncNoted(AsyncNotedAppOp)} callback. A good example of a useful
+ * message is including the {@link System#identityHashCode(Object)} of the listener that will
+ * receive data or the name of the manifest-receiver.
+ *
* @param op The operation to note. One of the OPSTR_* constants.
- * @param uid The user id of the application attempting to perform the operation.
+ * @param uid The uid of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or {@code
- * null} for default attribution
- * @param message A message describing the reason the op was noted
+ * @param attributionTag The {@link Context#createAttributionContext attribution tag} of the
+ * calling context or {@code null} for default attribution
+ * @param message A message describing why the op was noted
*
* @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
* {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
@@ -7366,33 +7386,16 @@ public class AppOpsManager {
*
* @throws SecurityException If the app has been configured to crash on this op.
*/
+ // For platform callers of this method, please read the package name parameter from
+ // Context#getOpPackageName.
+ // When noting a callback, the message can be computed using the #toReceiverId method.
public int noteOp(@NonNull String op, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message) {
return noteOp(strOpToOp(op), uid, packageName, attributionTag, message);
}
/**
- * Make note of an application performing an operation. Note that you must pass
- * in both the uid and name of the application to be checked; this function will verify
- * that these two match, and if not, return {@link #MODE_IGNORED}. If this call
- * succeeds, the last execution time of the operation for this app will be updated to
- * the current time.
- *
- * <p>If this is a check that is not preceding the protected operation, use
- * {@link #unsafeCheckOp} instead.
- *
- * @param op The operation to note. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or {@code
- * null} for default attribution
- * @param message A message describing the reason the op was noted
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- *
- * @throws SecurityException If the app has been configured to crash on this op.
+ * @see #noteOp(String, int, String, String, String
*
* @hide
*/
@@ -7430,16 +7433,7 @@ public class AppOpsManager {
* Like {@link #noteOp(String, int, String, String, String)} but instead of throwing a
* {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
- * @param op The operation to note. One of the OPSTR_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or {@code
- * null} for default attribution
- * @param message A message describing the reason the op was noted
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
+ * @see #noteOp(String, int, String, String, String)
*/
public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @Nullable String message) {
@@ -7447,19 +7441,7 @@ public class AppOpsManager {
}
/**
- * Like {@link #noteOp(String, int, String, String, String)} but instead of throwing a
- * {@link SecurityException} it returns {@link #MODE_ERRORED}.
- *
- * @param op The operation to note. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or {@code
- * null} for default attribution
- * @param message A message describing the reason the op was noted
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
+ * @see #noteOpNoThrow(String, int, String, String, String)
*
* @hide
*/
@@ -7477,20 +7459,8 @@ public class AppOpsManager {
}
}
- int mode;
- // Making the binder call "noteOperation" usually sets Binder.callingUid to the calling
- // processes UID. Hence clearing the calling UID is superfluous.
- // If the call is inside the system server though "noteOperation" is not a binder all,
- // it is only a method call. Hence Binder.callingUid might still be set to the app that
- // called the system server. This can lead to problems as not every app can see the
- // same appops the system server can see.
- long token = Binder.clearCallingIdentity();
- try {
- mode = mService.noteOperation(op, uid, packageName, attributionTag,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ int mode = mService.noteOperation(op, uid, packageName, attributionTag,
+ collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -7528,23 +7498,7 @@ public class AppOpsManager {
}
/**
- * Make note of an application performing an operation on behalf of another application when
- * handling an IPC. This function will verify that the calling uid and proxied package name
- * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
- * time of the operation for the proxied app and your app will be updated to the current time.
- *
- * @param op The operation to note. One of the OP_* constants.
- * @param proxiedPackageName The name of the application calling into the proxy application.
- * @param proxiedUid The uid of the proxied application
- * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
- * attribution tag} or {@code null} for default attribution
- * @param message A message describing the reason the op was noted
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
- * if it is not allowed and should be silently ignored (without causing the app to crash).
- *
- * @throws SecurityException If the proxy or proxied app has been configured to crash on this
- * op.
+ * @see #noteProxyOp(String, String, int, String, String)
*
* @hide
*/
@@ -7606,15 +7560,7 @@ public class AppOpsManager {
* Like {@link #noteProxyOp(String, String, int, String, String)} but instead
* of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
- * <p>This API requires package with the {@code proxiedPackageName} to belong to
- * {@code proxiedUid}.
- *
- * @param op The op to note
- * @param proxiedPackageName The package to note the op for
- * @param proxiedUid The uid the package belongs to
- * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
- * attribution tag} or {@code null} for default attribution
- * @param message A message describing the reason the op was noted
+ * @see #noteOpNoThrow(String, int, String, String, String)
*/
public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
@@ -7623,16 +7569,7 @@ public class AppOpsManager {
}
/**
- * Like {@link #noteProxyOp(int, String, int, String, String)} but instead
- * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
- *
- * @param op The op to note
- * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
- * noted for the "android" package
- * @param proxiedUid The uid the package belongs to
- * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
- * attribution tag} or {@code null} for default attribution
- * @param message A message describing the reason the op was noted
+ * @see #noteProxyOpNoThrow(String, String, int, String, String)
*
* @hide
*/
@@ -7653,17 +7590,10 @@ public class AppOpsManager {
}
}
- int mode;
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
- try {
- mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName,
- proxiedAttributionTag, myUid, mContext.getOpPackageName(),
- mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message,
- shouldCollectMessage);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName,
+ proxiedAttributionTag, myUid, mContext.getOpPackageName(),
+ mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -7713,8 +7643,6 @@ public class AppOpsManager {
*/
@UnsupportedAppUsage
public int checkOp(int op, int uid, String packageName) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
int mode = mService.checkOperation(op, uid, packageName);
if (mode == MODE_ERRORED) {
@@ -7723,27 +7651,24 @@ public class AppOpsManager {
return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
/**
* Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
* returns {@link #MODE_ERRORED}.
+ *
+ * @see #checkOp(int, int, String)
+ *
* @hide
*/
@UnsupportedAppUsage
public int checkOpNoThrow(int op, int uid, String packageName) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
int mode = mService.checkOperation(op, uid, packageName);
return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@@ -7866,6 +7791,10 @@ public class AppOpsManager {
/**
* Report that an application has started executing a long-running operation.
*
+ * <p>For more details how to determine the {@code callingPackageName},
+ * {@code callingAttributionTag}, and {@code message}, please check the description in
+ * {@link #noteOp(String, int, String, String, String)}
+ *
* @param op The operation to start. One of the OPSTR_* constants.
* @param uid The user id of the application attempting to perform the operation.
* @param packageName The name of the application attempting to perform the operation.
@@ -7886,22 +7815,7 @@ public class AppOpsManager {
}
/**
- * Report that an application has started executing a long-running operation.
- *
- * @param op The operation to start. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or
- * {@code null} for default attribution
- * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
- * @param message Description why op was started
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
- *
- * @throws SecurityException If the app has been configured to crash on this op or
- * the package is not in the passed in UID.
+ * @see #startOp(String, int, String, String, String)
*
* @hide
*/
@@ -7947,16 +7861,7 @@ public class AppOpsManager {
* Like {@link #startOp(String, int, String, String, String)} but instead of throwing a
* {@link SecurityException} it returns {@link #MODE_ERRORED}.
*
- * @param op The operation to start. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or
- * {@code null} for default attribution
- * @param message Description why op was started
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
+ * @see #startOp(String, int, String, String, String)
*/
public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
@NonNull String attributionTag, @Nullable String message) {
@@ -7964,20 +7869,7 @@ public class AppOpsManager {
}
/**
- * Like {@link #startOp(int, int, String, boolean, String, String)} but instead of throwing a
- * {@link SecurityException} it returns {@link #MODE_ERRORED}.
- *
- * @param op The operation to start. One of the OP_* constants.
- * @param uid The user id of the application attempting to perform the operation.
- * @param packageName The name of the application attempting to perform the operation.
- * @param attributionTag The {@link Context#createAttributionContext attribution tag} or
- * {@code null} for default attribution
- * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
- * @param message Description why op was started
- *
- * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
- * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
- * causing the app to crash).
+ * @see #startOpNoThrow(String, int, String, String, String)
*
* @hide
*/
@@ -7995,16 +7887,9 @@ public class AppOpsManager {
}
}
- int mode;
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
- try {
- mode = mService.startOperation(getClientId(), op, uid, packageName,
- attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC,
- message, shouldCollectMessage);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ int mode = mService.startOperation(getClientId(), op, uid, packageName,
+ attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message,
+ shouldCollectMessage);
if (mode == MODE_ALLOWED) {
if (collectionMode == COLLECT_SELF) {
@@ -8019,6 +7904,81 @@ public class AppOpsManager {
throw e.rethrowFromSystemServer();
}
}
+ /**
+ * Report that an application has started executing a long-running operation on behalf of
+ * another application when handling an IPC. This function will verify that the calling uid and
+ * proxied package name match, and if not, return {@link #MODE_IGNORED}.
+ *
+ * @param op The op to note
+ * @param proxiedUid The uid to note the op for {@code null}
+ * @param proxiedPackageName The package name the uid belongs to
+ * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
+ * attribution tag} or {@code null} for default attribution
+ * @param message A message describing the reason the op was noted
+ *
+ * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+ * if it is not allowed and should be silently ignored (without causing the app to crash).
+ *
+ * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+ * op.
+ */
+ public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
+ @Nullable String proxiedAttributionTag, @Nullable String message) {
+ final int mode = startProxyOpNoThrow(op, proxiedUid, proxiedPackageName,
+ proxiedAttributionTag, message);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+ + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
+ + " from uid " + proxiedUid + " not allowed to perform "
+ + sOpNames[strOpToOp(op)]);
+ }
+ return mode;
+ }
+
+ /**
+ *Like {@link #startProxyOp(String, int, String, String, String)} but instead
+ * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+ *
+ * @see #startProxyOp(String, int, String, String, String)
+ */
+ public int startProxyOpNoThrow(@NonNull String op, int proxiedUid,
+ @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag,
+ @Nullable String message) {
+ try {
+ int opInt = strOpToOp(op);
+
+ collectNoteOpCallsForValidation(opInt);
+ int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, opInt);
+ boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID;
+ if (collectionMode == COLLECT_ASYNC) {
+ if (message == null) {
+ // Set stack trace as default message
+ message = getFormattedStackTrace();
+ shouldCollectMessage = true;
+ }
+ }
+
+ int mode = mService.startProxyOperation(getClientId(), opInt, proxiedUid,
+ proxiedPackageName, proxiedAttributionTag, Process.myUid(),
+ mContext.getOpPackageName(), mContext.getAttributionTag(), false,
+ collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+
+ if (mode == MODE_ALLOWED) {
+ if (collectionMode == COLLECT_SELF) {
+ collectNotedOpForSelf(opInt, proxiedAttributionTag);
+ } else if (collectionMode == COLLECT_SYNC
+ // Only collect app-ops when the proxy is trusted
+ && mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1,
+ Process.myUid()) == PackageManager.PERMISSION_GRANTED) {
+ collectNotedOpSync(opInt, proxiedAttributionTag);
+ }
+ }
+
+ return mode;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* @deprecated Use {@link #finishOp(String, int, String, String)} instead
@@ -8058,23 +8018,38 @@ public class AppOpsManager {
}
/**
- * Report that an application is no longer performing an operation that had previously
- * been started with {@link #startOp(int, int, String, boolean, String, String)}. There is no
- * validation of input or result; the parameters supplied here must be the exact same ones
- * previously passed in when starting the operation.
+ * @see #finishOp(String, int, String, String)
*
* @hide
*/
public void finishOp(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
mService.finishOperation(getClientId(), op, uid, packageName, attributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Report that an application is no longer performing an operation that had previously
+ * been started with {@link #startProxyOp(String, int, String, String, String)}. There is no
+ * validation of input or result; the parameters supplied here must be the exact same ones
+ * previously passed in when starting the operation.
+ * @param op The operation which was started
+ * @param proxiedUid The uid the op was started on behalf of
+ * @param proxiedPackageName The package the op was started on behalf of
+ * @param proxiedAttributionTag The proxied {@link Context#createAttributionContext
+ * attribution tag} or {@code null} for default attribution
+ */
+ public void finishProxyOp(@NonNull String op, int proxiedUid,
+ @NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
+ try {
+ mService.finishProxyOperation(getClientId(), strOpToOp(op), proxiedUid,
+ proxiedPackageName, proxiedAttributionTag, Process.myUid(),
+ mContext.getOpPackageName(), mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
@@ -8522,7 +8497,7 @@ public class AppOpsManager {
public void opNoted(AsyncNotedAppOp op) {
Objects.requireNonNull(op);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
getAsyncNotedExecutor().execute(() -> onAsyncNoted(op));
} finally {
@@ -8666,14 +8641,10 @@ public class AppOpsManager {
// TODO: Uncomment below annotation once b/73559440 is fixed
// @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
public boolean isOperationActive(int code, int uid, String packageName) {
- // Clear calling UID to handle calls from inside the system server. See #noteOpNoThrow
- long token = Binder.clearCallingIdentity();
try {
return mService.isOperationActive(code, uid, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 3d966c7fbc38..7b25e25f3ff4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -16,13 +16,13 @@
package android.app;
-import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA256;
-import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA512;
-import static android.content.pm.Checksum.WHOLE_MD5;
-import static android.content.pm.Checksum.WHOLE_MERKLE_ROOT_4K_SHA256;
-import static android.content.pm.Checksum.WHOLE_SHA1;
-import static android.content.pm.Checksum.WHOLE_SHA256;
-import static android.content.pm.Checksum.WHOLE_SHA512;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
@@ -117,7 +117,6 @@ import dalvik.system.VMRuntime;
import libcore.util.EmptyArray;
-import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -150,10 +149,11 @@ public class ApplicationPackageManager extends PackageManager {
private static final int sDefaultFlags = GET_SHARED_LIBRARY_FILES;
/** Default set of checksums - includes all available checksums.
- * @see PackageManager#getChecksums */
+ * @see PackageManager#requestChecksums */
private static final int DEFAULT_CHECKSUMS =
- WHOLE_MERKLE_ROOT_4K_SHA256 | WHOLE_MD5 | WHOLE_SHA1 | WHOLE_SHA256 | WHOLE_SHA512
- | PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512;
+ TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256
+ | TYPE_WHOLE_SHA512 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256
+ | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
// Name of the resource which provides background permission button string
public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
@@ -994,14 +994,24 @@ public class ApplicationPackageManager extends PackageManager {
}
@Override
- public void getChecksums(@NonNull String packageName, boolean includeSplits,
- @Checksum.Kind int required, @Nullable List<Certificate> trustedInstallers,
+ public void requestChecksums(@NonNull String packageName, boolean includeSplits,
+ @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers,
@NonNull IntentSender statusReceiver)
- throws CertificateEncodingException, IOException, NameNotFoundException {
+ throws CertificateEncodingException, NameNotFoundException {
Objects.requireNonNull(packageName);
Objects.requireNonNull(statusReceiver);
- try {
- mPM.getChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
+ Objects.requireNonNull(trustedInstallers);
+ try {
+ if (trustedInstallers == TRUST_ALL) {
+ trustedInstallers = null;
+ } else if (trustedInstallers == TRUST_NONE) {
+ trustedInstallers = Collections.emptyList();
+ } else if (trustedInstallers.isEmpty()) {
+ throw new IllegalArgumentException(
+ "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty "
+ + "list of certificates.");
+ }
+ mPM.requestChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
encodeCertificates(trustedInstallers), statusReceiver, getUserId());
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 2df756e80fde..ac50676ff46b 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -15,6 +15,8 @@
*/
package android.app;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.PendingTransactionActions;
@@ -89,37 +91,38 @@ public abstract class ClientTransactionHandler {
public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
/** Destroy the activity. */
- public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
- boolean getNonConfigInstance, String reason);
+ public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
+ int configChanges, boolean getNonConfigInstance, String reason);
/** Pause the activity. */
- public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, PendingTransactionActions pendingActions, String reason);
+ public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
+ boolean userLeaving, int configChanges, PendingTransactionActions pendingActions,
+ String reason);
/**
* Resume the activity.
- * @param token Target activity token.
+ * @param r Target activity record.
* @param finalStateRequest Flag indicating if this call is handling final lifecycle state
* request for a transaction.
* @param isForward Flag indicating if next transition is forward.
* @param reason Reason for performing this operation.
*/
- public abstract void handleResumeActivity(IBinder token, boolean finalStateRequest,
- boolean isForward, String reason);
+ public abstract void handleResumeActivity(@NonNull ActivityClientRecord r,
+ boolean finalStateRequest, boolean isForward, String reason);
/**
* Notify the activity about top resumed state change.
- * @param token Target activity token.
+ * @param r Target activity record.
* @param isTopResumedActivity Current state of the activity, {@code true} if it's the
* topmost resumed activity in the system, {@code false} otherwise.
* @param reason Reason for performing this operation.
*/
- public abstract void handleTopResumedActivityChanged(IBinder token,
+ public abstract void handleTopResumedActivityChanged(@NonNull ActivityClientRecord r,
boolean isTopResumedActivity, String reason);
/**
* Stop the activity.
- * @param token Target activity token.
+ * @param r Target activity record.
* @param configChanges Activity configuration changes.
* @param pendingActions Pending actions to be used on this or later stages of activity
* transaction.
@@ -127,38 +130,40 @@ public abstract class ClientTransactionHandler {
* request for a transaction.
* @param reason Reason for performing this operation.
*/
- public abstract void handleStopActivity(IBinder token, int configChanges,
+ public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
/** Report that activity was stopped to server. */
public abstract void reportStop(PendingTransactionActions pendingActions);
/** Restart the activity after it was stopped. */
- public abstract void performRestartActivity(IBinder token, boolean start);
+ public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
/** Set pending activity configuration in case it will be updated by other transaction item. */
- public abstract void updatePendingActivityConfiguration(IBinder activityToken,
+ public abstract void updatePendingActivityConfiguration(@NonNull ActivityClientRecord r,
Configuration overrideConfig);
/** Deliver activity (override) configuration change. */
- public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+ public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
Configuration overrideConfig, int displayId);
/** Deliver result from another activity. */
- public abstract void handleSendResult(IBinder token, List<ResultInfo> results, String reason);
+ public abstract void handleSendResult(
+ @NonNull ActivityClientRecord r, List<ResultInfo> results, String reason);
/** Deliver new intent. */
- public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents);
+ public abstract void handleNewIntent(
+ @NonNull ActivityClientRecord r, List<ReferrerIntent> intents);
/** Request that an activity enter picture-in-picture. */
- public abstract void handlePictureInPictureRequested(IBinder token);
+ public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r);
/** Perform activity launch. */
- public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
+ public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent);
/** Perform activity start. */
- public abstract void handleStartActivity(IBinder token,
+ public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions);
/** Get package info. */
@@ -176,7 +181,7 @@ public abstract class ClientTransactionHandler {
* Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
* provided token.
*/
- public abstract ActivityThread.ActivityClientRecord getActivityClient(IBinder token);
+ public abstract ActivityClientRecord getActivityClient(IBinder token);
/**
* Prepare activity relaunch to update internal bookkeeping. This is used to track multiple
@@ -191,7 +196,7 @@ public abstract class ClientTransactionHandler {
* @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
* relaunch, or {@code null} if relaunch cancelled.
*/
- public abstract ActivityThread.ActivityClientRecord prepareRelaunchActivity(IBinder token,
+ public abstract ActivityClientRecord prepareRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, MergedConfiguration config, boolean preserveWindow);
@@ -200,14 +205,15 @@ public abstract class ClientTransactionHandler {
* @param r Activity client record prepared for relaunch.
* @param pendingActions Pending actions to be used on later stages of activity transaction.
* */
- public abstract void handleRelaunchActivity(ActivityThread.ActivityClientRecord r,
+ public abstract void handleRelaunchActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions);
/**
* Report that relaunch request was handled.
- * @param token Target activity token.
+ * @param r Target activity record.
* @param pendingActions Pending actions initialized on earlier stages of activity transaction.
* Used to check if we should report relaunch to WM.
* */
- public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions);
+ public abstract void reportRelaunch(@NonNull ActivityClientRecord r,
+ PendingTransactionActions pendingActions);
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index fef8d1005e29..e94fd452b7f8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -187,16 +187,6 @@ class ContextImpl extends Context {
private static final String XATTR_INODE_CODE_CACHE = "user.inode_code_cache";
/**
- * Special intent extra that critical system apps can use to hide the notification for a
- * foreground service. This extra should be placed in the intent passed into {@link
- * #startForegroundService(Intent)}.
- *
- * @hide
- */
- private static final String EXTRA_HIDDEN_FOREGROUND_SERVICE =
- "android.intent.extra.HIDDEN_FOREGROUND_SERVICE";
-
- /**
* Map from package name, to preference name, to cached preferences.
*/
@GuardedBy("ContextImpl.class")
@@ -1717,12 +1707,9 @@ class ContextImpl extends Context {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
- final boolean hideForegroundNotification = requireForeground
- && service.getBooleanExtra(EXTRA_HIDDEN_FOREGROUND_SERVICE, false);
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
- hideForegroundNotification,
getOpPackageName(), getAttributionTag(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
@@ -2413,7 +2400,6 @@ class ContextImpl extends Context {
context.setResources(createResources(mToken, mPackageInfo, mSplitName, overrideDisplayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
mResources.getLoaders()));
- context.mIsUiContext = isSelfOrOuterUiContext();
return context;
}
@@ -2442,6 +2428,11 @@ class ContextImpl extends Context {
// the display that would otherwise be inherited from mToken (or the global configuration if
// mToken is null).
context.mForceDisplayOverrideInResources = true;
+ // Note that even if a display context is derived from an UI context, it should not be
+ // treated as UI context because it does not handle configuration changes from the server
+ // side. If the context does need to handle configuration changes, please use
+ // Context#createWindowContext(int, Bundle).
+ context.mIsUiContext = false;
return context;
}
@@ -2806,6 +2797,7 @@ class ContextImpl extends Context {
mIsAssociatedWithDisplay = container.mIsAssociatedWithDisplay;
mIsSystemOrSystemUiContext = container.mIsSystemOrSystemUiContext;
mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources;
+ mIsUiContext = container.isSelfOrOuterUiContext();
} else {
mBasePackageName = packageInfo.mPackageName;
ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/HomeVisibilityObserver.java b/core/java/android/app/HomeVisibilityListener.java
index 8422c6f6f872..c6e5699c5fe2 100644
--- a/core/java/android/app/HomeVisibilityObserver.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -16,49 +16,56 @@
package android.app;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * An observer / callback to create and register by
- * {@link ActivityManager#registerHomeVisibilityObserver} so that it's triggered when
- * visibility of home page changes.
- * TODO: b/144351078 expose as SystemApi
+ * A listener that will be invoked when the visibility of the home screen changes.
+ * Register this callback via {@link ActivityManager#addHomeVisibilityListener}
* @hide
*/
-public abstract class HomeVisibilityObserver {
+// This is a single-method listener that needs a bunch of supporting code, so it can't be an
+// interface
+@SuppressLint("ListenerInterface")
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public abstract class HomeVisibilityListener {
private Context mContext;
private ActivityManager mActivityManager;
+ private Executor mExecutor;
/** @hide */
- IProcessObserver.Stub mObserver;
+ android.app.IProcessObserver.Stub mObserver;
/** @hide */
boolean mIsHomeActivityVisible;
/** @hide */
- void init(Context context, ActivityManager activityManager) {
+ void init(Context context, Executor executor, ActivityManager activityManager) {
mContext = context;
mActivityManager = activityManager;
mIsHomeActivityVisible = isHomeActivityVisible();
+ mExecutor = executor;
}
/**
- * The API that needs implemented and will be triggered when activity on home page changes.
+ * Called when the visibility of the home screen changes.
+ *
+ * @param isHomeActivityVisible Whether the home screen activity is now visible.
*/
public abstract void onHomeVisibilityChanged(boolean isHomeActivityVisible);
- public HomeVisibilityObserver() {
- mObserver = new IProcessObserver.Stub() {
+ public HomeVisibilityListener() {
+ mObserver = new android.app.IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
- boolean isHomeActivityVisible = isHomeActivityVisible();
- if (mIsHomeActivityVisible != isHomeActivityVisible) {
- mIsHomeActivityVisible = isHomeActivityVisible;
- onHomeVisibilityChanged(mIsHomeActivityVisible);
- }
+ refreshHomeVisibility();
}
@Override
@@ -67,6 +74,17 @@ public abstract class HomeVisibilityObserver {
@Override
public void onProcessDied(int pid, int uid) {
+ refreshHomeVisibility();
+ }
+
+ private void refreshHomeVisibility() {
+ boolean isHomeActivityVisible = isHomeActivityVisible();
+ if (mIsHomeActivityVisible != isHomeActivityVisible) {
+ mIsHomeActivityVisible = isHomeActivityVisible;
+ Binder.withCleanCallingIdentity(() ->
+ mExecutor.execute(() ->
+ onHomeVisibilityChanged(mIsHomeActivityVisible)));
+ }
}
};
}
@@ -83,12 +101,9 @@ public abstract class HomeVisibilityObserver {
}
// We can assume that the screen is idle if the home application is in the foreground.
- final Intent intent = new Intent(Intent.ACTION_MAIN, null);
- intent.addCategory(Intent.CATEGORY_HOME);
-
- ResolveInfo info = mContext.getPackageManager().resolveActivity(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (info != null && top.equals(info.activityInfo.packageName)) {
+ String defaultHomePackage = mContext.getPackageManager()
+ .getHomeActivities(new ArrayList<>()).getPackageName();
+ if (Objects.equals(top, defaultHomePackage)) {
return true;
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 45b25a3bf317..357b26c3083d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -25,7 +25,6 @@ import android.app.GrantedUriPermission;
import android.app.IApplicationThread;
import android.app.IActivityController;
import android.app.IAppTask;
-import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.IProcessObserver;
import android.app.IServiceConnection;
@@ -70,7 +69,6 @@ import android.os.RemoteCallback;
import android.os.StrictMode;
import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
-import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import com.android.internal.app.IVoiceInteractor;
@@ -156,8 +154,7 @@ interface IActivityManager {
boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta);
PendingIntent getRunningServiceControlPanel(in ComponentName service);
ComponentName startService(in IApplicationThread caller, in Intent service,
- in String resolvedType, boolean requireForeground,
- boolean hideForegroundNotification, in String callingPackage,
+ in String resolvedType, boolean requireForeground, in String callingPackage,
in String callingFeatureId, int userId);
@UnsupportedAppUsage
int stopService(in IApplicationThread caller, in Intent service,
@@ -450,9 +447,8 @@ interface IActivityManager {
void hang(in IBinder who, boolean allowRestart);
List<ActivityTaskManager.RootTaskInfo> getAllRootTaskInfos();
- @UnsupportedAppUsage
- void moveTaskToStack(int taskId, int stackId, boolean toTop);
- void setFocusedStack(int stackId);
+ void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
+ void setFocusedRootTask(int taskId);
ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo();
@UnsupportedAppUsage
void restart();
@@ -471,11 +467,6 @@ interface IActivityManager {
@UnsupportedAppUsage
boolean isInLockTaskMode();
@UnsupportedAppUsage
- void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver,
- in IRecentsAnimationRunner recentsAnimationRunner);
- @UnsupportedAppUsage
- void cancelRecentsAnimation(boolean restoreHomeStackPosition);
- @UnsupportedAppUsage
int startActivityFromRecents(int taskId, in Bundle options);
@UnsupportedAppUsage
void startSystemLockTaskMode(int taskId);
@@ -513,24 +504,14 @@ interface IActivityManager {
// descriptor.
@UnsupportedAppUsage
boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
- /**
- * Try to place task to provided position. The final position might be different depending on
- * current user and stacks state. The task will be moved to target stack if it's currently in
- * different stack.
- */
- @UnsupportedAppUsage
- void positionTaskInStack(int taskId, int stackId, int position);
@UnsupportedAppUsage
void suppressResizeConfigChanges(boolean suppress);
- @UnsupportedAppUsage
- boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
+ boolean moveTopActivityToPinnedRootTask(int rootTaskId, in Rect bounds);
boolean isAppStartModeDisabled(int uid, in String packageName);
@UnsupportedAppUsage
boolean unlockUser(int userid, in byte[] token, in byte[] secret,
in IProgressListener listener);
void killPackageDependents(in String packageName, int userId);
- @UnsupportedAppUsage
- void removeStack(int stackId);
void makePackageIdle(String packageName, int userId);
int getMemoryTrimLevel();
boolean isVrModePackageEnabled(in ComponentName packageName);
@@ -704,4 +685,10 @@ interface IActivityManager {
* @param enable set it to true to enable the app freezer, false to disable it.
*/
boolean enableAppFreezer(in boolean enable);
+
+ /**
+ * Holds the AM lock for the specified amount of milliseconds.
+ * This is intended for use by the tests that need to imitate lock contention.
+ */
+ void holdLock(in int durationMs);
}
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f3c7fe9412c9..75302293088f 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -123,7 +123,7 @@ interface IActivityTaskManager {
in ProfilerInfo profilerInfo, in Bundle options, int userId);
int startAssistantActivity(in String callingPackage, in String callingFeatureId, int callingPid,
int callingUid, in Intent intent, in String resolvedType, in Bundle options, int userId);
- void startRecentsActivity(in Intent intent, in IAssistDataReceiver assistDataReceiver,
+ void startRecentsActivity(in Intent intent, in long eventTime,
in IRecentsAnimationRunner recentsAnimationRunner);
int startActivityFromRecents(int taskId, in Bundle options);
int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
@@ -165,7 +165,8 @@ interface IActivityTaskManager {
int getTaskForActivity(in IBinder token, in boolean onlyRoot);
/** Finish all activities that were started for result from the specified activity. */
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
- ParceledListSlice getRecentTasks(int maxNum, int flags, int userId);
+ ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+ int userId);
boolean willActivityBeVisible(in IBinder token);
void setRequestedOrientation(in IBinder token, int requestedOrientation);
int getRequestedOrientation(in IBinder token);
@@ -186,11 +187,11 @@ interface IActivityTaskManager {
void reportAssistContextExtras(in IBinder token, in Bundle extras,
in AssistStructure structure, in AssistContent content, in Uri referrer);
- void setFocusedStack(int stackId);
+ void setFocusedRootTask(int taskId);
ActivityTaskManager.RootTaskInfo getFocusedRootTaskInfo();
Rect getTaskBounds(int taskId);
- void cancelRecentsAnimation(boolean restoreHomeStackPosition);
+ void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition);
void startLockTaskModeByToken(in IBinder token);
void stopLockTaskModeByToken(in IBinder token);
void updateLockTaskPackages(int userId, in String[] packages);
@@ -239,8 +240,7 @@ interface IActivityTaskManager {
* @return Return true on success. Otherwise false.
*/
boolean resizeTask(int taskId, in Rect bounds, int resizeMode);
- void moveStackToDisplay(int stackId, int displayId);
- void removeStack(int stackId);
+ void moveRootTaskToDisplay(int taskId, int displayId);
/**
* Sets the windowing mode for a specific task. Only works on tasks of type
@@ -251,15 +251,15 @@ interface IActivityTaskManager {
* @return Whether the task was successfully put into the specified windowing mode.
*/
boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
- void moveTaskToStack(int taskId, int stackId, boolean toTop);
+ void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop);
/**
- * Removes stacks in the input windowing modes from the system if they are of activity type
+ * Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
- void removeStacksInWindowingModes(in int[] windowingModes);
- /** Removes stack of the activity types from the system. */
- void removeStacksWithActivityTypes(in int[] activityTypes);
+ void removeRootTasksInWindowingModes(in int[] windowingModes);
+ /** Removes root tasks of the activity types from the system. */
+ void removeRootTasksWithActivityTypes(in int[] activityTypes);
List<ActivityTaskManager.RootTaskInfo> getAllRootTaskInfos();
ActivityTaskManager.RootTaskInfo getRootTaskInfo(int windowingMode, int activityType);
@@ -295,17 +295,11 @@ interface IActivityTaskManager {
ComponentName getActivityClassForToken(in IBinder token);
String getPackageForToken(in IBinder token);
- /**
- * Try to place task to provided position. The final position might be different depending on
- * current user and stacks state. The task will be moved to target stack if it's currently in
- * different stack.
- */
- void positionTaskInStack(int taskId, int stackId, int position);
void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
void suppressResizeConfigChanges(boolean suppress);
- boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
+ boolean moveTopActivityToPinnedRootTask(int rootTaskId, in Rect bounds);
boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
void requestPictureInPictureMode(in IBinder token);
@@ -331,7 +325,7 @@ interface IActivityTaskManager {
* stacks.
* @throws RemoteException
*/
- void resizeDockedStack(in Rect dockedBounds, in Rect tempDockedTaskBounds,
+ void resizePrimarySplitScreen(in Rect dockedBounds, in Rect tempDockedTaskBounds,
in Rect tempDockedTaskInsetBounds,
in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 4c9e400681ee..9eeb9f6a95bf 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -52,4 +52,6 @@ interface IUiAutomationConnection {
void dropShellPermissionIdentity();
// Called from the system process.
oneway void shutdown();
+ void executeShellCommandWithStderr(String command, in ParcelFileDescriptor sink,
+ in ParcelFileDescriptor source, in ParcelFileDescriptor stderrSink);
}
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 7cdf85e0a6b8..2e7c9f11ac66 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -49,6 +49,7 @@ public class LocalActivityManager {
private static final String TAG = "LocalActivityManager";
private static final boolean localLOGV = false;
+ // TODO(b/127877792): try to remove this and use {@code ActivityClientRecord} instead.
// Internal token for an Activity being managed by LocalActivityManager.
private static class LocalActivityRecord extends Binder {
LocalActivityRecord(String _id, Intent _intent) {
@@ -136,7 +137,7 @@ public class LocalActivityManager {
// startActivity() has not yet been called, so nothing to do.
return;
}
-
+
if (r.curState == INITIALIZING) {
// Get the lastNonConfigurationInstance for the activity
HashMap<String, Object> lastNonConfigurationInstances =
@@ -177,12 +178,13 @@ public class LocalActivityManager {
pendingActions = null;
}
- mActivityThread.handleStartActivity(r, pendingActions);
+ mActivityThread.handleStartActivity(clientRecord, pendingActions);
r.curState = STARTED;
if (desiredState == RESUMED) {
if (localLOGV) Log.v(TAG, r.id + ": resuming");
- mActivityThread.performResumeActivity(r, true, "moveToState-INITIALIZING");
+ mActivityThread.performResumeActivity(clientRecord, true,
+ "moveToState-INITIALIZING");
r.curState = RESUMED;
}
@@ -194,18 +196,25 @@ public class LocalActivityManager {
// group's state catches up.
return;
}
-
+
+ final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+ if (clientRecord == null) {
+ Log.w(TAG, "Can't get activity record for " + r.id);
+ return;
+ }
+
switch (r.curState) {
case CREATED:
if (desiredState == STARTED) {
if (localLOGV) Log.v(TAG, r.id + ": restarting");
- mActivityThread.performRestartActivity(r, true /* start */);
+ mActivityThread.performRestartActivity(clientRecord, true /* start */);
r.curState = STARTED;
}
if (desiredState == RESUMED) {
if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming");
- mActivityThread.performRestartActivity(r, true /* start */);
- mActivityThread.performResumeActivity(r, true, "moveToState-CREATED");
+ mActivityThread.performRestartActivity(clientRecord, true /* start */);
+ mActivityThread.performResumeActivity(clientRecord, true,
+ "moveToState-CREATED");
r.curState = RESUMED;
}
return;
@@ -214,7 +223,8 @@ public class LocalActivityManager {
if (desiredState == RESUMED) {
// Need to resume it...
if (localLOGV) Log.v(TAG, r.id + ": resuming");
- mActivityThread.performResumeActivity(r, true, "moveToState-STARTED");
+ mActivityThread.performResumeActivity(clientRecord, true,
+ "moveToState-STARTED");
r.instanceState = null;
r.curState = RESUMED;
}
@@ -352,7 +362,8 @@ public class LocalActivityManager {
ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
if (localLOGV) Log.v(TAG, r.id + ": new intent");
- mActivityThread.handleNewIntent(r, intents);
+ final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+ mActivityThread.handleNewIntent(clientRecord, intents);
r.intent = intent;
moveToState(r, mCurState);
if (mSingleMode) {
@@ -399,8 +410,11 @@ public class LocalActivityManager {
performPause(r, finish);
}
if (localLOGV) Log.v(TAG, r.id + ": destroying");
- mActivityThread.performDestroyActivity(r, finish, 0 /* configChanges */,
- false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
+ final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+ if (clientRecord != null) {
+ mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+ false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
+ }
r.activity = null;
r.window = null;
if (finish) {
@@ -664,7 +678,12 @@ public class LocalActivityManager {
for (int i=0; i<N; i++) {
LocalActivityRecord r = mActivityArray.get(i);
if (localLOGV) Log.v(TAG, r.id + ": destroying");
- mActivityThread.performDestroyActivity(r, finishing, 0 /* configChanges */,
+ final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
+ if (clientRecord == null) {
+ if (localLOGV) Log.v(TAG, r.id + ": no corresponding record");
+ continue;
+ }
+ mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
}
mActivities.clear();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0d682d64fb08..54380629e4ca 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2240,7 +2240,8 @@ public class Notification implements Parcelable
.setTicker(tickerText)
.setContentTitle(contentTitle)
.setContentText(contentText)
- .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0))
+ .setContentIntent(PendingIntent.getActivity(
+ context, 0, contentIntent, PendingIntent.FLAG_MUTABLE))
.buildInto(this);
}
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index 59dc9991c832..55fff8b2f074 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -22,12 +22,10 @@ import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
-import android.util.Slog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -387,12 +385,13 @@ public final class NotificationHistory implements Parcelable {
/**
* Removes all notifications from a conversation and regenerates the string pool
*/
- public boolean removeConversationFromWrite(String packageName, String conversationId) {
+ public boolean removeConversationsFromWrite(String packageName, Set<String> conversationIds) {
boolean removed = false;
for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
HistoricalNotification hn = mNotificationsToWrite.get(i);
if (packageName.equals(hn.getPackage())
- && conversationId.equals(hn.getConversationId())) {
+ && hn.getConversationId() != null
+ && conversationIds.contains(hn.getConversationId())) {
removed = true;
mNotificationsToWrite.remove(i);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fe8936654aa4..d442f5f321ec 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1567,8 +1567,20 @@ public class NotificationManager {
}
}
- /** @hide */
- public void setNotificationListenerAccessGranted(ComponentName listener, boolean granted) {
+ /**
+ * Grants/revokes Notification Listener access to the given component for current user.
+ * To grant access for a particular user, obtain this service by using the {@link Context}
+ * provided by {@link Context#createPackageContextAsUser}
+ *
+ * @param listener Name of component to grant/revoke access
+ * @param granted Grant/revoke access
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS)
+ public void setNotificationListenerAccessGranted(
+ @NonNull ComponentName listener, boolean granted) {
INotificationManager service = getService();
try {
service.setNotificationListenerAccessGranted(listener, granted);
@@ -1610,6 +1622,21 @@ public class NotificationManager {
}
}
+ /**
+ * Gets the list of enabled notification listener components for current user.
+ * To query for a particular user, obtain this service by using the {@link Context}
+ * provided by {@link Context#createPackageContextAsUser}
+ *
+ * @return the list of {@link ComponentName}s of the notification listeners
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS)
+ public @NonNull List<ComponentName> getEnabledNotificationListeners() {
+ return getEnabledNotificationListeners(mContext.getUserId());
+ }
+
/** @hide */
public List<ComponentName> getEnabledNotificationListeners(int userId) {
INotificationManager service = getService();
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index cd352e141994..e8937a8da911 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -19,12 +19,16 @@ package android.app;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -35,6 +39,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.ArraySet;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.os.IResultReceiver;
@@ -102,11 +107,20 @@ import java.lang.annotation.RetentionPolicy;
* FLAG_ONE_SHOT, <b>both</b> FLAG_ONE_SHOT and FLAG_NO_CREATE need to be supplied.
*/
public final class PendingIntent implements Parcelable {
+ private static final String TAG = "PendingIntent";
private final IIntentSender mTarget;
private IResultReceiver mCancelReceiver;
private IBinder mWhitelistToken;
private ArraySet<CancelListener> mCancelListeners;
+ /**
+ * It is now required to specify either {@link #FLAG_IMMUTABLE}
+ * or {@link #FLAG_MUTABLE} when creating a PendingIntent.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R)
+ static final long PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED = 160794467L;
+
/** @hide */
@IntDef(flag = true,
value = {
@@ -115,6 +129,7 @@ public final class PendingIntent implements Parcelable {
FLAG_CANCEL_CURRENT,
FLAG_UPDATE_CURRENT,
FLAG_IMMUTABLE,
+ FLAG_MUTABLE,
Intent.FILL_IN_ACTION,
Intent.FILL_IN_DATA,
@@ -175,6 +190,21 @@ public final class PendingIntent implements Parcelable {
public static final int FLAG_IMMUTABLE = 1<<26;
/**
+ * Flag indicating that the created PendingIntent should be mutable.
+ * This flag cannot be combined with {@link #FLAG_IMMUTABLE}. <p>Up until
+ * {@link android.os.Build.VERSION_CODES#R}, PendingIntents are assumed to
+ * be mutable by default, unless {@link #FLAG_IMMUTABLE} is set. Starting
+ * with {@link android.os.Build.VERSION_CODES#S}, it will be required to
+ * explicitly specify the mutability of PendingIntents on creation with
+ * either (@link #FLAG_IMMUTABLE} or {@link #FLAG_MUTABLE}. It is strongly
+ * recommended to use {@link #FLAG_IMMUTABLE} when creating a
+ * PendingIntent. {@link #FLAG_MUTABLE} should only be used when some
+ * functionality relies on modifying the underlying intent, e.g. any
+ * PendingIntent that needs to be used with inline reply or bubbles.
+ */
+ public static final int FLAG_MUTABLE = 1<<25;
+
+ /**
* Exception thrown when trying to send through a PendingIntent that
* has been canceled or is otherwise no longer able to execute the request.
*/
@@ -286,6 +316,27 @@ public final class PendingIntent implements Parcelable {
sOnMarshaledListener.set(listener);
}
+ private static void checkFlags(int flags, String packageName) {
+ final boolean flagImmutableSet = (flags & PendingIntent.FLAG_IMMUTABLE) != 0;
+ final boolean flagMutableSet = (flags & PendingIntent.FLAG_MUTABLE) != 0;
+ String msg = packageName + ": Targeting S+ (version " + Build.VERSION_CODES.S
+ + " and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE"
+ + " be specified when creating a PendingIntent.\nStrongly consider"
+ + " using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality"
+ + " depends on the PendingIntent being mutable, e.g. if it needs to"
+ + " be used with inline replies or bubbles.";
+
+ if (flagImmutableSet && flagMutableSet) {
+ throw new IllegalArgumentException(
+ "Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent");
+ }
+
+ if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
+ && !flagImmutableSet && !flagMutableSet) {
+ Log.e(TAG, msg);
+ }
+ }
+
/**
* Retrieve a PendingIntent that will start a new activity, like calling
* {@link Context#startActivity(Intent) Context.startActivity(Intent)}.
@@ -350,6 +401,7 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
+ checkFlags(flags, packageName);
try {
intent.migrateExtraStreamToClipData(context);
intent.prepareToLeaveProcess(context);
@@ -376,6 +428,7 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
+ checkFlags(flags, packageName);
try {
intent.migrateExtraStreamToClipData(context);
intent.prepareToLeaveProcess(context);
@@ -495,6 +548,7 @@ public final class PendingIntent implements Parcelable {
intents[i].prepareToLeaveProcess(context);
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
}
+ checkFlags(flags, packageName);
try {
IIntentSender target =
ActivityManager.getService().getIntentSenderWithFeature(
@@ -521,6 +575,7 @@ public final class PendingIntent implements Parcelable {
intents[i].prepareToLeaveProcess(context);
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
}
+ checkFlags(flags, packageName);
try {
IIntentSender target =
ActivityManager.getService().getIntentSenderWithFeature(
@@ -572,6 +627,7 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
+ checkFlags(flags, packageName);
try {
intent.prepareToLeaveProcess(context);
IIntentSender target =
@@ -651,6 +707,7 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
+ checkFlags(flags, packageName);
try {
intent.prepareToLeaveProcess(context);
IIntentSender target =
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index df53f98a2d75..32252a3f4bae 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -326,6 +326,16 @@ public final class PictureInPictureParams implements Parcelable {
out.writeBoolean(mAutoEnterEnabled);
}
+ @Override
+ public String toString() {
+ return "PictureInPictureParams("
+ + " aspectRatio=" + getAspectRatioRational()
+ + " sourceRectHint=" + getSourceRectHint()
+ + " hasSetActions=" + hasSetActions()
+ + " isAutoPipEnabled=" + isAutoEnterEnabled()
+ + ")";
+ }
+
public static final @android.annotation.NonNull Creator<PictureInPictureParams> CREATOR =
new Creator<PictureInPictureParams>() {
public PictureInPictureParams createFromParcel(Parcel in) {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 9e4ab33c6aa0..b26784086489 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1262,12 +1262,18 @@ public class ResourcesManager {
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
synchronized(this) {
- return applyConfigurationToResourcesLocked(config, compat);
+ return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
}
}
public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
- @Nullable CompatibilityInfo compat) {
+ @Nullable CompatibilityInfo compat) {
+ return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */);
+ }
+
+ /** Applies the global configuration to the managed resources. */
+ public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
+ @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#applyConfigurationToResourcesLocked");
@@ -1291,6 +1297,11 @@ public class ResourcesManager {
}
DisplayMetrics displayMetrics = getDisplayMetrics();
+ if (adjustments != null) {
+ // Currently the only case where the adjustment takes effect is to simulate placing
+ // an app in a rotated display.
+ adjustments.adjustGlobalAppMetrics(displayMetrics);
+ }
Resources.updateSystemConfiguration(config, displayMetrics, compat);
ApplicationPackageManager.configurationChanged();
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 4ff9e8d58c3a..99d21272236d 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -402,7 +402,7 @@ public class StatusBarManager {
@TestApi
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.STATUS_BAR)
- public void setDisabledForSimNetworkLock(boolean disabled) {
+ public void setExpansionDisabledForSimNetworkLock(boolean disabled) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
final IStatusBarService svc = getService();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d50cdeed6d73..b020c7044a00 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -33,6 +33,7 @@ import android.app.prediction.AppPredictionManager;
import android.app.role.RoleControllerManager;
import android.app.role.RoleManager;
import android.app.slice.SliceManager;
+import android.app.time.TimeManager;
import android.app.timedetector.TimeDetector;
import android.app.timedetector.TimeDetectorImpl;
import android.app.timezone.RulesManager;
@@ -80,6 +81,7 @@ import android.hardware.SystemSensorManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IAuthService;
import android.hardware.camera2.CameraManager;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.ColorDisplayManager;
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
@@ -106,6 +108,8 @@ import android.media.MediaRouter;
import android.media.MediaTranscodeManager;
import android.media.midi.IMidiManager;
import android.media.midi.MidiManager;
+import android.media.musicrecognition.IMusicRecognitionManager;
+import android.media.musicrecognition.MusicRecognitionManager;
import android.media.projection.MediaProjectionManager;
import android.media.soundtrigger.SoundTriggerManager;
import android.media.tv.ITvInputManager;
@@ -1118,6 +1122,17 @@ public final class SystemServiceRegistry {
return new AutofillManager(ctx.getOuterContext(), service);
}});
+ registerService(Context.MUSIC_RECOGNITION_SERVICE, MusicRecognitionManager.class,
+ new CachedServiceFetcher<MusicRecognitionManager>() {
+ @Override
+ public MusicRecognitionManager createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(
+ Context.MUSIC_RECOGNITION_SERVICE);
+ return new MusicRecognitionManager(
+ IMusicRecognitionManager.Stub.asInterface(b));
+ }
+ });
+
registerService(Context.CONTENT_CAPTURE_MANAGER_SERVICE, ContentCaptureManager.class,
new CachedServiceFetcher<ContentCaptureManager>() {
@Override
@@ -1218,6 +1233,14 @@ public final class SystemServiceRegistry {
return new TimeZoneDetectorImpl();
}});
+ registerService(Context.TIME_MANAGER, TimeManager.class,
+ new CachedServiceFetcher<TimeManager>() {
+ @Override
+ public TimeManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new TimeManager();
+ }});
+
registerService(Context.PERMISSION_SERVICE, PermissionManager.class,
new CachedServiceFetcher<PermissionManager>() {
@Override
@@ -1326,6 +1349,12 @@ public final class SystemServiceRegistry {
throws ServiceNotFoundException {
return new DreamManager(ctx);
}});
+ registerService(Context.DEVICE_STATE_SERVICE, DeviceStateManager.class,
+ new CachedServiceFetcher<DeviceStateManager>() {
+ @Override
+ public DeviceStateManager createService(ContextImpl ctx) {
+ return new DeviceStateManager();
+ }});
sInitializing = true;
try {
@@ -1382,6 +1411,7 @@ public final class SystemServiceRegistry {
case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
case Context.APP_PREDICTION_SERVICE:
case Context.INCREMENTAL_SERVICE:
+ case Context.ETHERNET_SERVICE:
return null;
}
Slog.wtf(TAG, "Manager wrapper not available: " + name);
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index ab997e9f7832..1b0fd9edf4f8 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -25,6 +25,7 @@ import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
@@ -1241,11 +1242,36 @@ public final class UiAutomation {
*
* @param command The command to execute.
* @return File descriptors (out, in) to the standard output/input streams.
+ */
+ @SuppressLint("ArrayReturn") // For consistency with other APIs
+ public @NonNull ParcelFileDescriptor[] executeShellCommandRw(@NonNull String command) {
+ return executeShellCommandInternal(command, false /* includeStderr */);
+ }
+
+ /**
+ * Executes a shell command. This method returns three file descriptors,
+ * one that points to the standard output stream (element at index 0), one that points
+ * to the standard input stream (element at index 1), and one points to
+ * standard error stream (element at index 2). The command execution is similar
+ * to running "adb shell <command>" from a host connected to the device.
+ * <p>
+ * <strong>Note:</strong> It is your responsibility to close the returned file
+ * descriptors once you are done reading/writing.
+ * </p>
+ *
+ * @param command The command to execute.
+ * @return File descriptors (out, in, err) to the standard output/input/error streams.
*
* @hide
*/
@TestApi
- public ParcelFileDescriptor[] executeShellCommandRw(String command) {
+ @SuppressLint("ArrayReturn") // For consistency with other APIs
+ public @NonNull ParcelFileDescriptor[] executeShellCommandRwe(@NonNull String command) {
+ return executeShellCommandInternal(command, true /* includeStderr */);
+ }
+
+ private ParcelFileDescriptor[] executeShellCommandInternal(
+ String command, boolean includeStderr) {
warnIfBetterCommand(command);
ParcelFileDescriptor source_read = null;
@@ -1254,6 +1280,9 @@ public final class UiAutomation {
ParcelFileDescriptor source_write = null;
ParcelFileDescriptor sink_write = null;
+ ParcelFileDescriptor stderr_source_read = null;
+ ParcelFileDescriptor stderr_sink_read = null;
+
try {
ParcelFileDescriptor[] pipe_read = ParcelFileDescriptor.createPipe();
source_read = pipe_read[0];
@@ -1263,8 +1292,15 @@ public final class UiAutomation {
source_write = pipe_write[0];
sink_write = pipe_write[1];
+ if (includeStderr) {
+ ParcelFileDescriptor[] stderr_read = ParcelFileDescriptor.createPipe();
+ stderr_source_read = stderr_read[0];
+ stderr_sink_read = stderr_read[1];
+ }
+
// Calling out without a lock held.
- mUiAutomationConnection.executeShellCommand(command, sink_read, source_write);
+ mUiAutomationConnection.executeShellCommandWithStderr(
+ command, sink_read, source_write, stderr_sink_read);
} catch (IOException ioe) {
Log.e(LOG_TAG, "Error executing shell command!", ioe);
} catch (RemoteException re) {
@@ -1272,11 +1308,15 @@ public final class UiAutomation {
} finally {
IoUtils.closeQuietly(sink_read);
IoUtils.closeQuietly(source_write);
+ IoUtils.closeQuietly(stderr_sink_read);
}
- ParcelFileDescriptor[] result = new ParcelFileDescriptor[2];
+ ParcelFileDescriptor[] result = new ParcelFileDescriptor[includeStderr ? 3 : 2];
result[0] = source_read;
result[1] = sink_write;
+ if (includeStderr) {
+ result[2] = stderr_source_read;
+ }
return result;
}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 70d520176ca1..255b93f79811 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -372,6 +372,13 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
@Override
public void executeShellCommand(final String command, final ParcelFileDescriptor sink,
final ParcelFileDescriptor source) throws RemoteException {
+ executeShellCommandWithStderr(command, sink, source, null /* stderrSink */);
+ }
+
+ @Override
+ public void executeShellCommandWithStderr(final String command, final ParcelFileDescriptor sink,
+ final ParcelFileDescriptor source, final ParcelFileDescriptor stderrSink)
+ throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
throwIfShutdownLocked();
@@ -409,6 +416,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
writeToProcess = null;
}
+ // Read from process stderr and write to pipe
+ final Thread readStderrFromProcess;
+ if (stderrSink != null) {
+ InputStream sink_in = process.getErrorStream();
+ OutputStream sink_out = new FileOutputStream(stderrSink.getFileDescriptor());
+
+ readStderrFromProcess = new Thread(new Repeater(sink_in, sink_out));
+ readStderrFromProcess.start();
+ } else {
+ readStderrFromProcess = null;
+ }
+
Thread cleanup = new Thread(new Runnable() {
@Override
public void run() {
@@ -419,14 +438,18 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
if (readFromProcess != null) {
readFromProcess.join();
}
+ if (readStderrFromProcess != null) {
+ readStderrFromProcess.join();
+ }
} catch (InterruptedException exc) {
Log.e(TAG, "At least one of the threads was interrupted");
}
IoUtils.closeQuietly(sink);
IoUtils.closeQuietly(source);
+ IoUtils.closeQuietly(stderrSink);
process.destroy();
- }
- });
+ }
+ });
cleanup.start();
}
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 79f05a3caa93..eedf95882294 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -852,15 +852,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * Returns true if this container may be scaled without resizing, and windows within may need
- * to be configured as such.
- * @hide
- */
- public boolean windowsAreScaleable() {
- return mWindowingMode == WINDOWING_MODE_PINNED;
- }
-
- /**
* Returns true if windows in this container should be given move animations by default.
* @hide
*/
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index a2f44149e853..3cc7f1e5df42 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -55,14 +55,6 @@ public final class DeviceAdminInfo implements Parcelable {
static final String TAG = "DeviceAdminInfo";
/**
- * A type of policy that this device admin can use: profile owner on an organization-owned
- * device.
- *
- * @hide
- */
- public static final int USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER = -3;
-
- /**
* A type of policy that this device admin can use: device owner meta-policy
* for an admin that is designated as owner of the device.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8a85b7942800..b74e18b099ce 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5718,8 +5718,8 @@ public class DevicePolicyManager {
* System apps can always bypass VPN.
* <p> Note that the system doesn't update the allowlist when packages are installed or
* uninstalled, the admin app must call this method to keep the list up to date.
- * <p> When {@code lockdownEnabled} is false {@code lockdownWhitelist} is ignored . When
- * {@code lockdownEnabled} is {@code true} and {@code lockdownWhitelist} is {@code null} or
+ * <p> When {@code lockdownEnabled} is false {@code lockdownAllowlist} is ignored . When
+ * {@code lockdownEnabled} is {@code true} and {@code lockdownAllowlist} is {@code null} or
* empty, only system apps can bypass VPN.
* <p> Setting always-on VPN package to {@code null} or using
* {@link #setAlwaysOnVpnPackage(ComponentName, String, boolean)} clears lockdown allowlist.
@@ -5728,24 +5728,24 @@ public class DevicePolicyManager {
* to remove an existing always-on VPN configuration
* @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or
* {@code false} otherwise. This has no effect when clearing.
- * @param lockdownWhitelist Packages that will be able to access the network directly when VPN
+ * @param lockdownAllowlist Packages that will be able to access the network directly when VPN
* is in lockdown mode but not connected. Has no effect when clearing.
* @throws SecurityException if {@code admin} is not a device or a profile
* owner.
* @throws NameNotFoundException if {@code vpnPackage} or one of
- * {@code lockdownWhitelist} is not installed.
+ * {@code lockdownAllowlist} is not installed.
* @throws UnsupportedOperationException if {@code vpnPackage} exists but does
* not support being set as always-on, or if always-on VPN is not
* available.
*/
public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage,
- boolean lockdownEnabled, @Nullable Set<String> lockdownWhitelist)
+ boolean lockdownEnabled, @Nullable Set<String> lockdownAllowlist)
throws NameNotFoundException {
throwIfParentInstance("setAlwaysOnVpnPackage");
if (mService != null) {
try {
mService.setAlwaysOnVpnPackage(admin, vpnPackage, lockdownEnabled,
- lockdownWhitelist == null ? null : new ArrayList<>(lockdownWhitelist));
+ lockdownAllowlist == null ? null : new ArrayList<>(lockdownAllowlist));
} catch (ServiceSpecificException e) {
switch (e.errorCode) {
case ERROR_VPN_PACKAGE_NOT_FOUND:
@@ -5820,9 +5820,9 @@ public class DevicePolicyManager {
throwIfParentInstance("getAlwaysOnVpnLockdownWhitelist");
if (mService != null) {
try {
- final List<String> whitelist =
- mService.getAlwaysOnVpnLockdownWhitelist(admin);
- return whitelist == null ? null : new HashSet<>(whitelist);
+ final List<String> allowlist =
+ mService.getAlwaysOnVpnLockdownAllowlist(admin);
+ return allowlist == null ? null : new HashSet<>(allowlist);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 19242ba9bdb5..cb879fce9c10 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -76,16 +76,24 @@ public abstract class DevicePolicyManagerInternal {
OnCrossProfileWidgetProvidersChangeListener listener);
/**
- * Checks if an app with given uid is an active device admin of its user and has the policy
- * specified.
+ * Checks if an app with given uid is an active device owner of its user.
*
* <p>This takes the DPMS lock. DO NOT call from PM/UM/AM with their lock held.
*
* @param uid App uid.
- * @param reqPolicy Required policy, for policies see {@link DevicePolicyManager}.
- * @return true if the uid is an active admin with the given policy.
+ * @return true if the uid is an active device owner.
*/
- public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
+ public abstract boolean isActiveDeviceOwner(int uid);
+
+ /**
+ * Checks if an app with given uid is an active profile owner of its user.
+ *
+ * <p>This takes the DPMS lock. DO NOT call from PM/UM/AM with their lock held.
+ *
+ * @param uid App uid.
+ * @return true if the uid is an active profile owner.
+ */
+ public abstract boolean isActiveProfileOwner(int uid);
/**
* Checks if an app with given uid is the active supervision admin.
diff --git a/core/java/android/app/admin/FreezePeriod.java b/core/java/android/app/admin/FreezePeriod.java
index 657f0177097e..eb6efec1c330 100644
--- a/core/java/android/app/admin/FreezePeriod.java
+++ b/core/java/android/app/admin/FreezePeriod.java
@@ -39,8 +39,8 @@ import java.util.List;
public class FreezePeriod {
private static final String TAG = "FreezePeriod";
- private static final int DUMMY_YEAR = 2001;
- static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year
+ private static final int SENTINEL_YEAR = 2001;
+ static final int DAYS_IN_YEAR = 365; // 365 since SENTINEL_YEAR is not a leap year
private final MonthDay mStart;
private final MonthDay mEnd;
@@ -60,9 +60,9 @@ public class FreezePeriod {
*/
public FreezePeriod(MonthDay start, MonthDay end) {
mStart = start;
- mStartDay = mStart.atYear(DUMMY_YEAR).getDayOfYear();
+ mStartDay = mStart.atYear(SENTINEL_YEAR).getDayOfYear();
mEnd = end;
- mEndDay = mEnd.atYear(DUMMY_YEAR).getDayOfYear();
+ mEndDay = mEnd.atYear(SENTINEL_YEAR).getDayOfYear();
}
/**
@@ -166,9 +166,9 @@ public class FreezePeriod {
endYearAdjustment = 1;
}
}
- final LocalDate startDate = LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).withYear(
+ final LocalDate startDate = LocalDate.ofYearDay(SENTINEL_YEAR, mStartDay).withYear(
now.getYear() + startYearAdjustment);
- final LocalDate endDate = LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).withYear(
+ final LocalDate endDate = LocalDate.ofYearDay(SENTINEL_YEAR, mEndDay).withYear(
now.getYear() + endYearAdjustment);
return new Pair<>(startDate, endDate);
}
@@ -176,13 +176,13 @@ public class FreezePeriod {
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
- return LocalDate.ofYearDay(DUMMY_YEAR, mStartDay).format(formatter) + " - "
- + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter);
+ return LocalDate.ofYearDay(SENTINEL_YEAR, mStartDay).format(formatter) + " - "
+ + LocalDate.ofYearDay(SENTINEL_YEAR, mEndDay).format(formatter);
}
/** @hide */
private static MonthDay dayOfYearToMonthDay(int dayOfYear) {
- LocalDate date = LocalDate.ofYearDay(DUMMY_YEAR, dayOfYear);
+ LocalDate date = LocalDate.ofYearDay(SENTINEL_YEAR, dayOfYear);
return MonthDay.of(date.getMonth(), date.getDayOfMonth());
}
@@ -191,7 +191,7 @@ public class FreezePeriod {
* @hide
*/
private static int dayOfYearDisregardLeapYear(LocalDate date) {
- return date.withYear(DUMMY_YEAR).getDayOfYear();
+ return date.withYear(SENTINEL_YEAR).getDayOfYear();
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1c7b617e6d9a..60dce22f35e5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -197,12 +197,12 @@ interface IDevicePolicyManager {
void setCertInstallerPackage(in ComponentName who, String installerPackage);
String getCertInstallerPackage(in ComponentName who);
- boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownWhitelist);
+ boolean setAlwaysOnVpnPackage(in ComponentName who, String vpnPackage, boolean lockdown, in List<String> lockdownAllowlist);
String getAlwaysOnVpnPackage(in ComponentName who);
String getAlwaysOnVpnPackageForUser(int userHandle);
boolean isAlwaysOnVpnLockdownEnabled(in ComponentName who);
boolean isAlwaysOnVpnLockdownEnabledForUser(int userHandle);
- List<String> getAlwaysOnVpnLockdownWhitelist(in ComponentName who);
+ List<String> getAlwaysOnVpnLockdownAllowlist(in ComponentName who);
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 056cfc7c28f1..065127138ecc 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -1054,14 +1054,15 @@ public abstract class BackupAgent extends ContextWrapper {
long quotaBytes,
IBackupCallback callbackBinder,
int transportFlags) throws RemoteException {
- // Ensure that we're running with the app's normal permission level
- long ident = Binder.clearCallingIdentity();
-
if (DEBUG) Log.v(TAG, "doBackup() invoked");
+
BackupDataOutput output = new BackupDataOutput(
data.getFileDescriptor(), quotaBytes, transportFlags);
long result = RESULT_ERROR;
+
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onBackup(oldState, output, newState);
result = RESULT_SUCCESS;
@@ -1111,9 +1112,6 @@ public abstract class BackupAgent extends ContextWrapper {
private void doRestoreInternal(ParcelFileDescriptor data, long appVersionCode,
ParcelFileDescriptor newState, int token, IBackupManager callbackBinder,
List<String> excludedKeys) throws RemoteException {
- // Ensure that we're running with the app's normal permission level
- long ident = Binder.clearCallingIdentity();
-
if (DEBUG) Log.v(TAG, "doRestore() invoked");
// Ensure that any side-effect SharedPreferences writes have landed *before*
@@ -1121,6 +1119,9 @@ public abstract class BackupAgent extends ContextWrapper {
waitForSharedPrefs();
BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
+
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onRestore(input, appVersionCode, newState,
excludedKeys != null ? new HashSet<>(excludedKeys)
@@ -1152,15 +1153,14 @@ public abstract class BackupAgent extends ContextWrapper {
@Override
public void doFullBackup(ParcelFileDescriptor data,
long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
- // Ensure that we're running with the app's normal permission level
- long ident = Binder.clearCallingIdentity();
-
if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
// Ensure that any SharedPreferences writes have landed *before*
// we potentially try to back up the underlying files directly.
waitForSharedPrefs();
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onFullBackup(new FullBackupDataOutput(
data, quotaBytes, transportFlags));
@@ -1199,12 +1199,13 @@ public abstract class BackupAgent extends ContextWrapper {
public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
int transportFlags) {
- // Ensure that we're running with the app's normal permission level
- final long ident = Binder.clearCallingIdentity();
FullBackupDataOutput measureOutput =
new FullBackupDataOutput(quotaBytes, transportFlags);
waitForSharedPrefs();
+
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onFullBackup(measureOutput);
} catch (IOException ex) {
@@ -1228,7 +1229,7 @@ public abstract class BackupAgent extends ContextWrapper {
public void doRestoreFile(ParcelFileDescriptor data, long size,
int type, String domain, String path, long mode, long mtime,
int token, IBackupManager callbackBinder) throws RemoteException {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
} catch (IOException e) {
@@ -1255,7 +1256,7 @@ public abstract class BackupAgent extends ContextWrapper {
@Override
public void doRestoreFinished(int token, IBackupManager callbackBinder) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onRestoreFinished();
} catch (Exception e) {
@@ -1284,9 +1285,10 @@ public abstract class BackupAgent extends ContextWrapper {
long backupDataBytes,
long quotaBytes,
IBackupCallback callbackBinder) {
- long ident = Binder.clearCallingIdentity();
-
long result = RESULT_ERROR;
+
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onQuotaExceeded(backupDataBytes, quotaBytes);
result = RESULT_SUCCESS;
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index 61dac0d64422..c547ef1e7e9b 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -39,4 +39,10 @@ interface IPeopleManager {
/** Removes all the recent conversations and uncaches their cached shortcuts. */
void removeAllRecentConversations();
+
+ /**
+ * Returns the last interaction with the specified conversation. If the
+ * conversation can't be found or no interactions have been recorded, returns 0L.
+ */
+ long getLastInteraction(in String packageName, int userId, in String shortcutId);
}
diff --git a/core/java/android/app/role/RoleControllerManager.java b/core/java/android/app/role/RoleControllerManager.java
index 96a4debd3d6a..8dde2c55d7d3 100644
--- a/core/java/android/app/role/RoleControllerManager.java
+++ b/core/java/android/app/role/RoleControllerManager.java
@@ -258,7 +258,7 @@ public class RoleControllerManager {
Consumer<Boolean> destination) {
operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> executor.execute(() -> {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
Log.e(LOG_TAG, "Error calling " + opName + "()", err);
@@ -276,7 +276,7 @@ public class RoleControllerManager {
RemoteCallback destination) {
operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
Log.e(LOG_TAG, "Error calling " + opName + "()", err);
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 253c73796caf..82159235ae28 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -413,7 +413,7 @@ public final class RoleManager {
@NonNull Consumer<Boolean> callback) {
return new RemoteCallback(result -> executor.execute(() -> {
boolean successful = result != null;
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
callback.accept(successful);
} finally {
@@ -634,7 +634,9 @@ public final class RoleManager {
* @hide
*/
@Nullable
- public String getDefaultSmsPackage(@UserIdInt int userId) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public String getSmsRoleHolder(@UserIdInt int userId) {
try {
return mService.getDefaultSmsPackage(userId);
} catch (RemoteException e) {
@@ -658,7 +660,7 @@ public final class RoleManager {
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(PooledLambda.obtainRunnable(
OnRoleHoldersChangedListener::onRoleHoldersChanged, mListener, roleName,
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 8b52242a6b6c..8a4bee98ca87 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -32,23 +33,24 @@ import java.util.Objects;
* Activity configuration changed callback.
* @hide
*/
-public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
private Configuration mConfiguration;
@Override
public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
- client.updatePendingActivityConfiguration(token, mConfiguration);
+ client.updatePendingActivityConfiguration(r, mConfiguration);
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
// TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+ client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -93,7 +95,7 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem {
mConfiguration = in.readTypedObject(Configuration.CREATOR);
}
- public static final @android.annotation.NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
+ public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
new Creator<ActivityConfigurationChangeItem>() {
public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
return new ActivityConfigurationChangeItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index c9193a9578e7..cadb6606b1be 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -25,7 +25,7 @@ import java.lang.annotation.RetentionPolicy;
* Request for lifecycle state that an activity should reach.
* @hide
*/
-public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
@IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = {
UNDEFINED,
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 9844de7b6e88..87ea3f8db39c 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -18,7 +18,8 @@ package android.app.servertransaction;
import static android.app.ActivityThread.DEBUG_ORDER;
-import android.app.ActivityThread;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
import android.os.IBinder;
@@ -36,7 +37,7 @@ import java.util.Objects;
* Activity relaunch callback.
* @hide
*/
-public class ActivityRelaunchItem extends ClientTransactionItem {
+public class ActivityRelaunchItem extends ActivityTransactionItem {
private static final String TAG = "ActivityRelaunchItem";
@@ -50,7 +51,7 @@ public class ActivityRelaunchItem extends ClientTransactionItem {
* A record that was properly configured for relaunch. Execution will be cancelled if not
* initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
*/
- private ActivityThread.ActivityClientRecord mActivityClientRecord;
+ private ActivityClientRecord mActivityClientRecord;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
@@ -59,7 +60,7 @@ public class ActivityRelaunchItem extends ClientTransactionItem {
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
if (mActivityClientRecord == null) {
if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
@@ -73,7 +74,8 @@ public class ActivityRelaunchItem extends ClientTransactionItem {
@Override
public void postExecute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
- client.reportRelaunch(token, pendingActions);
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
+ client.reportRelaunch(r, pendingActions);
}
// ObjectPoolItem implementation
@@ -130,16 +132,16 @@ public class ActivityRelaunchItem extends ClientTransactionItem {
mPreserveWindow = in.readBoolean();
}
- public static final @android.annotation.NonNull Creator<ActivityRelaunchItem> CREATOR =
+ public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
new Creator<ActivityRelaunchItem>() {
- public ActivityRelaunchItem createFromParcel(Parcel in) {
- return new ActivityRelaunchItem(in);
- }
-
- public ActivityRelaunchItem[] newArray(int size) {
- return new ActivityRelaunchItem[size];
- }
- };
+ public ActivityRelaunchItem createFromParcel(Parcel in) {
+ return new ActivityRelaunchItem(in);
+ }
+
+ public ActivityRelaunchItem[] newArray(int size) {
+ return new ActivityRelaunchItem[size];
+ }
+ };
@Override
public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 4e743caccad6..47096a8257e9 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -18,10 +18,11 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Trace;
@@ -33,7 +34,7 @@ import java.util.Objects;
* Activity result delivery callback.
* @hide
*/
-public class ActivityResultItem extends ClientTransactionItem {
+public class ActivityResultItem extends ActivityTransactionItem {
@UnsupportedAppUsage
private List<ResultInfo> mResultInfoList;
@@ -45,10 +46,10 @@ public class ActivityResultItem extends ClientTransactionItem {
}*/
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
- client.handleSendResult(token, mResultInfoList, "ACTIVITY_RESULT");
+ client.handleSendResult(r, mResultInfoList, "ACTIVITY_RESULT");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -88,7 +89,7 @@ public class ActivityResultItem extends ClientTransactionItem {
mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
}
- public static final @android.annotation.NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
+ public static final @NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
new Parcelable.Creator<ActivityResultItem>() {
public ActivityResultItem createFromParcel(Parcel in) {
return new ActivityResultItem(in);
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
new file mode 100644
index 000000000000..f7d7e9d20ab9
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -0,0 +1,69 @@
+/*
+ * 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.app.servertransaction;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * An activity-targeting callback message to a client that can be scheduled and executed.
+ * It also provides nullity-free version of
+ * {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)} for child class
+ * to inherit.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ * @see com.android.server.wm.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ActivityTransactionItem extends ClientTransactionItem {
+ @Override
+ public final void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
+
+ execute(client, r, pendingActions);
+ }
+
+ /**
+ * Like {@link #execute(ClientTransactionHandler, IBinder, PendingTransactionActions)},
+ * but take non-null {@link ActivityClientRecord} as a parameter.
+ */
+ @VisibleForTesting(visibility = PACKAGE)
+ public abstract void execute(@NonNull ClientTransactionHandler client,
+ @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions);
+
+ @NonNull ActivityClientRecord getActivityClientRecord(
+ @NonNull ClientTransactionHandler client, IBinder token) {
+ final ActivityClientRecord r = client.getActivityClient(token);
+ if (r == null) {
+ throw new IllegalArgumentException("Activity client record must not be null to execute "
+ + "transaction item");
+ }
+ if (client.getActivity(token) == null) {
+ throw new IllegalArgumentException("Activity must not be null to execute "
+ + "transaction item");
+ }
+ return r;
+ }
+}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index 3ee761477efd..1611369497e9 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -18,6 +18,8 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
@@ -38,10 +40,10 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- client.handleDestroyActivity(token, mFinished, mConfigChanges,
+ client.handleDestroyActivity(r, mFinished, mConfigChanges,
false /* getNonConfigInstance */, "DestroyActivityItem");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -92,7 +94,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
mConfigChanges = in.readInt();
}
- public static final @android.annotation.NonNull Creator<DestroyActivityItem> CREATOR =
+ public static final @NonNull Creator<DestroyActivityItem> CREATOR =
new Creator<DestroyActivityItem>() {
public DestroyActivityItem createFromParcel(Parcel in) {
return new DestroyActivityItem(in);
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index b2a1276fa178..b7e81a56afad 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -16,20 +16,20 @@
package android.app.servertransaction;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.os.IBinder;
import android.os.Parcel;
/**
* Request an activity to enter picture-in-picture mode.
* @hide
*/
-public final class EnterPipRequestedItem extends ClientTransactionItem {
+public final class EnterPipRequestedItem extends ActivityTransactionItem {
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
- client.handlePictureInPictureRequested(token);
+ client.handlePictureInPictureRequested(r);
}
// ObjectPoolItem implementation
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 2e7b6262c785..77457af77340 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -18,6 +18,7 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ProfilerInfo;
@@ -163,7 +164,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
in.readTypedObject(FixedRotationAdjustments.CREATOR));
}
- public static final @android.annotation.NonNull Creator<LaunchActivityItem> CREATOR =
+ public static final @NonNull Creator<LaunchActivityItem> CREATOR =
new Creator<LaunchActivityItem>() {
public LaunchActivityItem createFromParcel(Parcel in) {
return new LaunchActivityItem(in);
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 9a457a3aad40..32de53f189b0 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -19,6 +19,7 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -31,23 +32,24 @@ import java.util.Objects;
* Activity move to a different display message.
* @hide
*/
-public class MoveToDisplayItem extends ClientTransactionItem {
+public class MoveToDisplayItem extends ActivityTransactionItem {
private int mTargetDisplayId;
private Configuration mConfiguration;
@Override
public void preExecute(ClientTransactionHandler client, IBinder token) {
+ final ActivityClientRecord r = getActivityClientRecord(client, token);
// Notify the client of an upcoming change in the token configuration. This ensures that
// batches of config change items only process the newest configuration.
- client.updatePendingActivityConfiguration(token, mConfiguration);
+ client.updatePendingActivityConfiguration(r, mConfiguration);
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
- client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+ client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -96,7 +98,8 @@ public class MoveToDisplayItem extends ClientTransactionItem {
mConfiguration = in.readTypedObject(Configuration.CREATOR);
}
- public static final @android.annotation.NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+ public static final @NonNull Creator<MoveToDisplayItem> CREATOR =
+ new Creator<MoveToDisplayItem>() {
public MoveToDisplayItem createFromParcel(Parcel in) {
return new MoveToDisplayItem(in);
}
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 6a4996da38ca..b4e2a7bfa10f 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -19,9 +19,10 @@ package android.app.servertransaction;
import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Trace;
@@ -35,7 +36,7 @@ import java.util.Objects;
* New intent message.
* @hide
*/
-public class NewIntentItem extends ClientTransactionItem {
+public class NewIntentItem extends ActivityTransactionItem {
@UnsupportedAppUsage
private List<ReferrerIntent> mIntents;
@@ -47,10 +48,10 @@ public class NewIntentItem extends ClientTransactionItem {
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
- client.handleNewIntent(token, mIntents);
+ client.handleNewIntent(r, mIntents);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -94,7 +95,7 @@ public class NewIntentItem extends ClientTransactionItem {
mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
}
- public static final @android.annotation.NonNull Parcelable.Creator<NewIntentItem> CREATOR =
+ public static final @NonNull Parcelable.Creator<NewIntentItem> CREATOR =
new Parcelable.Creator<NewIntentItem>() {
public NewIntentItem createFromParcel(Parcel in) {
return new NewIntentItem(in);
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index f65c843ee76f..cb154e9585e6 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -18,8 +18,9 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import android.app.ActivityManager;
+import android.annotation.NonNull;
import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
@@ -40,10 +41,10 @@ public class PauseActivityItem extends ActivityLifecycleItem {
private boolean mDontReport;
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, pendingActions,
+ client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, pendingActions,
"PAUSE_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -130,7 +131,7 @@ public class PauseActivityItem extends ActivityLifecycleItem {
mDontReport = in.readBoolean();
}
- public static final @android.annotation.NonNull Creator<PauseActivityItem> CREATOR =
+ public static final @NonNull Creator<PauseActivityItem> CREATOR =
new Creator<PauseActivityItem>() {
public PauseActivityItem createFromParcel(Parcel in) {
return new PauseActivityItem(in);
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index 905076b08e69..d2a156c37c90 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -18,8 +18,10 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
@@ -46,10 +48,10 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
}
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
+ client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
"RESUME_ACTIVITY");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -128,7 +130,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem {
mIsForward = in.readBoolean();
}
- public static final @android.annotation.NonNull Creator<ResumeActivityItem> CREATOR =
+ public static final @NonNull Creator<ResumeActivityItem> CREATOR =
new Creator<ResumeActivityItem>() {
public ResumeActivityItem createFromParcel(Parcel in) {
return new ResumeActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 4fbe02b9cf76..ae0bd24218fb 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -18,8 +18,9 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Trace;
@@ -32,10 +33,10 @@ public class StartActivityItem extends ActivityLifecycleItem {
private static final String TAG = "StartActivityItem";
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
- client.handleStartActivity(token, pendingActions);
+ client.handleStartActivity(r, pendingActions);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -79,7 +80,7 @@ public class StartActivityItem extends ActivityLifecycleItem {
// Empty
}
- public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
+ public static final @NonNull Creator<StartActivityItem> CREATOR =
new Creator<StartActivityItem>() {
public StartActivityItem createFromParcel(Parcel in) {
return new StartActivityItem(in);
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 8668bd49c8f5..7708104da16a 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -18,6 +18,8 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
@@ -34,10 +36,10 @@ public class StopActivityItem extends ActivityLifecycleItem {
private int mConfigChanges;
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- client.handleStopActivity(token, mConfigChanges, pendingActions,
+ client.handleStopActivity(r, mConfigChanges, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -93,7 +95,7 @@ public class StopActivityItem extends ActivityLifecycleItem {
mConfigChanges = in.readInt();
}
- public static final @android.annotation.NonNull Creator<StopActivityItem> CREATOR =
+ public static final @NonNull Creator<StopActivityItem> CREATOR =
new Creator<StopActivityItem>() {
public StopActivityItem createFromParcel(Parcel in) {
return new StopActivityItem(in);
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index c7e4c3641631..345c1dd336ab 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -17,7 +17,9 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
import android.app.ActivityTaskManager;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
@@ -28,15 +30,15 @@ import android.os.Trace;
* Top resumed activity changed callback.
* @hide
*/
-public class TopResumedActivityChangeItem extends ClientTransactionItem {
+public class TopResumedActivityChangeItem extends ActivityTransactionItem {
private boolean mOnTop;
@Override
- public void execute(ClientTransactionHandler client, IBinder token,
+ public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
- client.handleTopResumedActivityChanged(token, mOnTop, "topResumedActivityChangeItem");
+ client.handleTopResumedActivityChanged(r, mOnTop, "topResumedActivityChangeItem");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -97,16 +99,16 @@ public class TopResumedActivityChangeItem extends ClientTransactionItem {
mOnTop = in.readBoolean();
}
- public static final @android.annotation.NonNull Creator<TopResumedActivityChangeItem> CREATOR =
+ public static final @NonNull Creator<TopResumedActivityChangeItem> CREATOR =
new Creator<TopResumedActivityChangeItem>() {
- public TopResumedActivityChangeItem createFromParcel(Parcel in) {
- return new TopResumedActivityChangeItem(in);
- }
-
- public TopResumedActivityChangeItem[] newArray(int size) {
- return new TopResumedActivityChangeItem[size];
- }
- };
+ public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+ return new TopResumedActivityChangeItem(in);
+ }
+
+ public TopResumedActivityChangeItem[] newArray(int size) {
+ return new TopResumedActivityChangeItem[size];
+ }
+ };
@Override
public boolean equals(Object o) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 17fcda587322..3dcf2cb5f13e 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,29 +218,29 @@ public class TransactionExecutor {
null /* customIntent */);
break;
case ON_START:
- mTransactionHandler.handleStartActivity(r.token, mPendingActions);
+ mTransactionHandler.handleStartActivity(r, mPendingActions);
break;
case ON_RESUME:
- mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
+ mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
break;
case ON_PAUSE:
- mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
+ mTransactionHandler.handlePauseActivity(r, false /* finished */,
false /* userLeaving */, 0 /* configChanges */, mPendingActions,
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
- mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
+ mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
- mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
+ mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
0 /* configChanges */, false /* getNonConfigInstance */,
"performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
- mTransactionHandler.performRestartActivity(r.token, false /* start */);
+ mTransactionHandler.performRestartActivity(r, false /* start */);
break;
default:
throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
diff --git a/core/java/android/app/time/ITimeZoneDetectorListener.aidl b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
new file mode 100644
index 000000000000..723ad5969afc
--- /dev/null
+++ b/core/java/android/app/time/ITimeZoneDetectorListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.app.time;
+
+/** {@hide} */
+oneway interface ITimeZoneDetectorListener {
+ void onChange();
+} \ No newline at end of file
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
new file mode 100644
index 000000000000..951905bcbac5
--- /dev/null
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.app.time."
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/app/time/TimeManager.java b/core/java/android/app/time/TimeManager.java
new file mode 100644
index 000000000000..4796d8db3e96
--- /dev/null
+++ b/core/java/android/app/time/TimeManager.java
@@ -0,0 +1,217 @@
+/*
+ * 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.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.timezonedetector.ITimeZoneDetectorService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * The interface through which system components can interact with time and time zone services.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.TIME_MANAGER)
+public final class TimeManager {
+ private static final String TAG = "time.TimeManager";
+ private static final boolean DEBUG = false;
+
+ private final Object mLock = new Object();
+ private final ITimeZoneDetectorService mITimeZoneDetectorService;
+
+ @GuardedBy("mLock")
+ private ITimeZoneDetectorListener mTimeZoneDetectorReceiver;
+
+ /**
+ * The registered listeners. The key is the actual listener that was registered, the value is a
+ * wrapper that ensures the listener is executed on the correct Executor.
+ */
+ @GuardedBy("mLock")
+ private ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> mTimeZoneDetectorListeners;
+
+ /** @hide */
+ public TimeManager() throws ServiceNotFoundException {
+ // TimeManager is an API over one or possibly more services. At least until there's an
+ // internal refactoring.
+ mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
+ }
+
+ /**
+ * Returns the calling user's time zone capabilities and configuration.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ @NonNull
+ public TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig() {
+ if (DEBUG) {
+ Log.d(TAG, "getTimeZoneCapabilities called");
+ }
+ try {
+ return mITimeZoneDetectorService.getCapabilitiesAndConfig();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Modifies the time zone detection configuration.
+ *
+ * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
+ * specific to the current user.
+ *
+ * <p>The ability to modify configuration settings can be subject to restrictions. For
+ * example, they may be determined by device hardware, general policy (i.e. only the primary
+ * user can set them), or by a managed device policy. Use {@link
+ * #getTimeZoneCapabilitiesAndConfig()} to obtain information at runtime about the user's
+ * capabilities.
+ *
+ * <p>Attempts to modify configuration settings with capabilities that are {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
+ * will be returned. Modifying configuration settings with capabilities that are {@link
+ * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
+ * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
+ * TimeZoneCapabilities} for further details.
+ *
+ * <p>If the supplied configuration only has some values set, then only the specified settings
+ * will be updated (where the user's capabilities allow) and other settings will be left
+ * unchanged.
+ *
+ * @return {@code true} if all the configuration settings specified have been set to the
+ * new values, {@code false} if none have
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public boolean updateTimeZoneConfiguration(@NonNull TimeZoneConfiguration configuration) {
+ if (DEBUG) {
+ Log.d(TAG, "updateConfiguration called: " + configuration);
+ }
+ try {
+ return mITimeZoneDetectorService.updateConfiguration(configuration);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * An interface that can be used to listen for changes to the time zone detector behavior.
+ */
+ @FunctionalInterface
+ public interface TimeZoneDetectorListener {
+ /**
+ * Called when something about the time zone detector behavior on the device has changed.
+ * For example, this could be because the current user has switched, one of the global or
+ * user's settings been changed, or something that could affect a user's capabilities with
+ * respect to the time zone detector has changed. Because different users can have different
+ * configuration and capabilities, this method may be called when nothing has changed for
+ * the receiving user.
+ */
+ void onChange();
+ }
+
+ /**
+ * Registers a listener that will be informed when something about the time zone detector
+ * behavior changes.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public void addTimeZoneDetectorListener(@NonNull Executor executor,
+ @NonNull TimeZoneDetectorListener listener) {
+
+ if (DEBUG) {
+ Log.d(TAG, "addTimeZoneDetectorListener called: " + listener);
+ }
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null) {
+ mTimeZoneDetectorListeners = new ArrayMap<>();
+ } else if (mTimeZoneDetectorListeners.containsKey(listener)) {
+ return;
+ }
+
+ if (mTimeZoneDetectorReceiver == null) {
+ ITimeZoneDetectorListener iListener = new ITimeZoneDetectorListener.Stub() {
+ @Override
+ public void onChange() {
+ notifyTimeZoneDetectorListeners();
+ }
+ };
+ mTimeZoneDetectorReceiver = iListener;
+ try {
+ mITimeZoneDetectorService.addListener(mTimeZoneDetectorReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ mTimeZoneDetectorListeners.put(listener, () -> executor.execute(listener::onChange));
+ }
+ }
+
+ private void notifyTimeZoneDetectorListeners() {
+ ArrayMap<TimeZoneDetectorListener, TimeZoneDetectorListener> timeZoneDetectorListeners;
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+ return;
+ }
+ timeZoneDetectorListeners = new ArrayMap<>(mTimeZoneDetectorListeners);
+ }
+ int size = timeZoneDetectorListeners.size();
+ for (int i = 0; i < size; i++) {
+ timeZoneDetectorListeners.valueAt(i).onChange();
+ }
+ }
+
+ /**
+ * Removes a listener previously passed to
+ * {@link #addTimeZoneDetectorListener(Executor, TimeZoneDetectorListener)}
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION)
+ public void removeTimeZoneDetectorListener(@NonNull TimeZoneDetectorListener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "removeConfigurationListener called: " + listener);
+ }
+
+ synchronized (mLock) {
+ if (mTimeZoneDetectorListeners == null || mTimeZoneDetectorListeners.isEmpty()) {
+ return;
+ }
+ mTimeZoneDetectorListeners.remove(listener);
+
+ // If the last local listener has been removed, remove and discard the
+ // mTimeZoneDetectorReceiver.
+ if (mTimeZoneDetectorListeners.isEmpty()) {
+ try {
+ mITimeZoneDetectorService.removeListener(mTimeZoneDetectorReceiver);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mTimeZoneDetectorReceiver = null;
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl b/core/java/android/app/time/TimeZoneCapabilities.aidl
index fede6458318a..f744bf162c67 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.aidl
+++ b/core/java/android/app/time/TimeZoneCapabilities.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
parcelable TimeZoneCapabilities;
diff --git a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 09fffe9f4f25..df89c285d5bd 100644
--- a/core/java/android/app/timezonedetector/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
-
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED;
-import static android.app.timezonedetector.TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED;
+package android.app.time;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TimeZoneDetector;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -42,15 +43,18 @@ import java.util.Objects;
* <p>Actions have associated methods, see the documentation for each action for details.
*
* <p>For configuration settings capabilities, the associated settings value can be found via
- * {@link #getConfiguration()} and may be changed using {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} (if the user's capabilities allow).
+ * {@link TimeManager#getTimeZoneCapabilitiesAndConfig()} and may be changed using {@link
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} (if the user's capabilities
+ * allow).
*
* <p>Note: Capabilities are independent of app permissions required to call the associated APIs.
*
* @hide
*/
+@SystemApi
public final class TimeZoneCapabilities implements Parcelable {
+ /** @hide */
@IntDef({ CAPABILITY_NOT_SUPPORTED, CAPABILITY_NOT_ALLOWED, CAPABILITY_NOT_APPLICABLE,
CAPABILITY_POSSESSED })
@Retention(RetentionPolicy.SOURCE)
@@ -94,64 +98,60 @@ public final class TimeZoneCapabilities implements Parcelable {
}
};
-
- @NonNull private final TimeZoneConfiguration mConfiguration;
- private final @CapabilityState int mConfigureAutoDetectionEnabled;
- private final @CapabilityState int mConfigureGeoDetectionEnabled;
- private final @CapabilityState int mSuggestManualTimeZone;
+ /**
+ * The user the capabilities are for. This is used for object equality and debugging but there
+ * is no accessor.
+ */
+ @NonNull private final UserHandle mUserHandle;
+ private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+ private final @CapabilityState int mSuggestManualTimeZoneCapability;
private TimeZoneCapabilities(@NonNull Builder builder) {
- this.mConfiguration = Objects.requireNonNull(builder.mConfiguration);
- this.mConfigureAutoDetectionEnabled = builder.mConfigureAutoDetectionEnabled;
- this.mConfigureGeoDetectionEnabled = builder.mConfigureGeoDetectionEnabled;
- this.mSuggestManualTimeZone = builder.mSuggestManualTimeZone;
+ this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
+ this.mConfigureAutoDetectionEnabledCapability =
+ builder.mConfigureAutoDetectionEnabledCapability;
+ this.mConfigureGeoDetectionEnabledCapability =
+ builder.mConfigureGeoDetectionEnabledCapability;
+ this.mSuggestManualTimeZoneCapability = builder.mSuggestManualTimeZoneCapability;
}
@NonNull
private static TimeZoneCapabilities createFromParcel(Parcel in) {
- return new TimeZoneCapabilities.Builder()
- .setConfiguration(in.readParcelable(null))
- .setConfigureAutoDetectionEnabled(in.readInt())
- .setConfigureGeoDetectionEnabled(in.readInt())
- .setSuggestManualTimeZone(in.readInt())
+ UserHandle userHandle = UserHandle.readFromParcel(in);
+ return new TimeZoneCapabilities.Builder(userHandle)
+ .setConfigureAutoDetectionEnabledCapability(in.readInt())
+ .setConfigureGeoDetectionEnabledCapability(in.readInt())
+ .setSuggestManualTimeZoneCapability(in.readInt())
.build();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeParcelable(mConfiguration, flags);
- dest.writeInt(mConfigureAutoDetectionEnabled);
- dest.writeInt(mConfigureGeoDetectionEnabled);
- dest.writeInt(mSuggestManualTimeZone);
- }
-
- /**
- * Returns the user's time zone behavior configuration.
- */
- public @NonNull TimeZoneConfiguration getConfiguration() {
- return mConfiguration;
+ UserHandle.writeToParcel(mUserHandle, dest);
+ dest.writeInt(mConfigureAutoDetectionEnabledCapability);
+ dest.writeInt(mConfigureGeoDetectionEnabledCapability);
+ dest.writeInt(mSuggestManualTimeZoneCapability);
}
/**
* Returns the capability state associated with the user's ability to modify the automatic time
* zone detection setting. The setting can be updated via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
- * #getConfiguration()}.
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
*/
@CapabilityState
- public int getConfigureAutoDetectionEnabled() {
- return mConfigureAutoDetectionEnabled;
+ public int getConfigureAutoDetectionEnabledCapability() {
+ return mConfigureAutoDetectionEnabledCapability;
}
/**
* Returns the capability state associated with the user's ability to modify the geolocation
* detection setting. The setting can be updated via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and accessed via {@link
- * #getConfiguration()}.
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
*/
@CapabilityState
- public int getConfigureGeoDetectionEnabled() {
- return mConfigureGeoDetectionEnabled;
+ public int getConfigureGeoDetectionEnabledCapability() {
+ return mConfigureGeoDetectionEnabledCapability;
}
/**
@@ -160,36 +160,37 @@ public final class TimeZoneCapabilities implements Parcelable {
*
* <p>The suggestion will be ignored in all cases unless the value is {@link
* #CAPABILITY_POSSESSED}. See also {@link TimeZoneConfiguration#isAutoDetectionEnabled()}.
+ *
+ * @hide
*/
@CapabilityState
- public int getSuggestManualTimeZone() {
- return mSuggestManualTimeZone;
+ public int getSuggestManualTimeZoneCapability() {
+ return mSuggestManualTimeZoneCapability;
}
/**
- * Constructs a new {@link TimeZoneConfiguration} from an {@code oldConfiguration} and a set of
- * {@code requestedChanges}, if the current capabilities allow. The new configuration is
- * returned and the capabilities are left unchanged. If the capabilities do not permit one or
- * more of the changes then {@code null} is returned.
+ * Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of
+ * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is
+ * returned. If the capabilities do not permit one or more of the requested changes then {@code
+ * null} is returned.
+ *
+ * @hide
*/
@Nullable
- public TimeZoneConfiguration applyUpdate(TimeZoneConfiguration requestedChanges) {
- if (requestedChanges.getUserId() != mConfiguration.getUserId()) {
- throw new IllegalArgumentException("User does not match:"
- + " this=" + mConfiguration + ", other=" + requestedChanges);
- }
-
+ public TimeZoneConfiguration tryApplyConfigChanges(
+ @NonNull TimeZoneConfiguration config,
+ @NonNull TimeZoneConfiguration requestedChanges) {
TimeZoneConfiguration.Builder newConfigBuilder =
- new TimeZoneConfiguration.Builder(mConfiguration);
- if (requestedChanges.hasSetting(SETTING_AUTO_DETECTION_ENABLED)) {
- if (getConfigureAutoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+ new TimeZoneConfiguration.Builder(config);
+ if (requestedChanges.hasIsAutoDetectionEnabled()) {
+ if (this.getConfigureAutoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
return null;
}
newConfigBuilder.setAutoDetectionEnabled(requestedChanges.isAutoDetectionEnabled());
}
- if (requestedChanges.hasSetting(SETTING_GEO_DETECTION_ENABLED)) {
- if (getConfigureGeoDetectionEnabled() < CAPABILITY_NOT_APPLICABLE) {
+ if (requestedChanges.hasIsGeoDetectionEnabled()) {
+ if (this.getConfigureGeoDetectionEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
return null;
}
newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
@@ -212,71 +213,71 @@ public final class TimeZoneCapabilities implements Parcelable {
return false;
}
TimeZoneCapabilities that = (TimeZoneCapabilities) o;
- return Objects.equals(mConfiguration, that.mConfiguration)
- && mConfigureAutoDetectionEnabled == that.mConfigureAutoDetectionEnabled
- && mConfigureGeoDetectionEnabled == that.mConfigureGeoDetectionEnabled
- && mSuggestManualTimeZone == that.mSuggestManualTimeZone;
+ return mUserHandle.equals(that.mUserHandle)
+ && mConfigureAutoDetectionEnabledCapability
+ == that.mConfigureAutoDetectionEnabledCapability
+ && mConfigureGeoDetectionEnabledCapability
+ == that.mConfigureGeoDetectionEnabledCapability
+ && mSuggestManualTimeZoneCapability == that.mSuggestManualTimeZoneCapability;
}
@Override
public int hashCode() {
- return Objects.hash(mConfiguration,
- mConfigureAutoDetectionEnabled,
- mConfigureGeoDetectionEnabled,
- mSuggestManualTimeZone);
+ return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
+ mConfigureGeoDetectionEnabledCapability, mSuggestManualTimeZoneCapability);
}
@Override
public String toString() {
return "TimeZoneDetectorCapabilities{"
- + "mConfiguration=" + mConfiguration
- + ", mConfigureAutomaticDetectionEnabled=" + mConfigureAutoDetectionEnabled
- + ", mConfigureGeoDetectionEnabled=" + mConfigureGeoDetectionEnabled
- + ", mSuggestManualTimeZone=" + mSuggestManualTimeZone
+ + "mUserHandle=" + mUserHandle
+ + ", mConfigureAutoDetectionEnabledCapability="
+ + mConfigureAutoDetectionEnabledCapability
+ + ", mConfigureGeoDetectionEnabledCapability="
+ + mConfigureGeoDetectionEnabledCapability
+ + ", mSuggestManualTimeZoneCapability=" + mSuggestManualTimeZoneCapability
+ '}';
}
/** @hide */
public static class Builder {
- private TimeZoneConfiguration mConfiguration;
- private @CapabilityState int mConfigureAutoDetectionEnabled;
- private @CapabilityState int mConfigureGeoDetectionEnabled;
- private @CapabilityState int mSuggestManualTimeZone;
+ @NonNull private UserHandle mUserHandle;
+ private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
+ private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
+ private @CapabilityState int mSuggestManualTimeZoneCapability;
- /** Sets the user-visible configuration settings. */
- public Builder setConfiguration(@NonNull TimeZoneConfiguration configuration) {
- if (!configuration.isComplete()) {
- throw new IllegalArgumentException(configuration + " is not complete");
- }
- this.mConfiguration = configuration;
- return this;
+ public Builder(@NonNull UserHandle userHandle) {
+ mUserHandle = Objects.requireNonNull(userHandle);
}
/** Sets the state for the automatic time zone detection enabled config. */
- public Builder setConfigureAutoDetectionEnabled(@CapabilityState int value) {
- this.mConfigureAutoDetectionEnabled = value;
+ public Builder setConfigureAutoDetectionEnabledCapability(@CapabilityState int value) {
+ this.mConfigureAutoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the geolocation time zone detection enabled config. */
- public Builder setConfigureGeoDetectionEnabled(@CapabilityState int value) {
- this.mConfigureGeoDetectionEnabled = value;
+ public Builder setConfigureGeoDetectionEnabledCapability(@CapabilityState int value) {
+ this.mConfigureGeoDetectionEnabledCapability = value;
return this;
}
/** Sets the state for the suggestManualTimeZone action. */
- public Builder setSuggestManualTimeZone(@CapabilityState int value) {
- this.mSuggestManualTimeZone = value;
+ public Builder setSuggestManualTimeZoneCapability(@CapabilityState int value) {
+ this.mSuggestManualTimeZoneCapability = value;
return this;
}
/** Returns the {@link TimeZoneCapabilities}. */
@NonNull
public TimeZoneCapabilities build() {
- verifyCapabilitySet(mConfigureAutoDetectionEnabled, "configureAutoDetectionEnabled");
- verifyCapabilitySet(mConfigureGeoDetectionEnabled, "configureGeoDetectionEnabled");
- verifyCapabilitySet(mSuggestManualTimeZone, "suggestManualTimeZone");
+ verifyCapabilitySet(mConfigureAutoDetectionEnabledCapability,
+ "configureAutoDetectionEnabledCapability");
+ verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
+ "configureGeoDetectionEnabledCapability");
+ verifyCapabilitySet(mSuggestManualTimeZoneCapability,
+ "suggestManualTimeZoneCapability");
return new TimeZoneCapabilities(this);
}
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
new file mode 100644
index 000000000000..d7b6b58bf85a
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.time;
+
+parcelable TimeZoneCapabilitiesAndConfig;
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
new file mode 100644
index 000000000000..b339e53b8be3
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -0,0 +1,120 @@
+/*
+ * 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.app.time;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
+
+ public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
+ new Creator<TimeZoneCapabilitiesAndConfig>() {
+ public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+ }
+
+ public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeZoneCapabilitiesAndConfig[size];
+ }
+ };
+
+
+ @NonNull private final TimeZoneCapabilities mCapabilities;
+ @NonNull private final TimeZoneConfiguration mConfiguration;
+
+ /**
+ * Creates a new instance.
+ *
+ * @hide
+ */
+ public TimeZoneCapabilitiesAndConfig(
+ @NonNull TimeZoneCapabilities capabilities,
+ @NonNull TimeZoneConfiguration configuration) {
+ this.mCapabilities = Objects.requireNonNull(capabilities);
+ this.mConfiguration = Objects.requireNonNull(configuration);
+ }
+
+ @NonNull
+ private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ TimeZoneCapabilities capabilities = in.readParcelable(null);
+ TimeZoneConfiguration configuration = in.readParcelable(null);
+ return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mCapabilities, flags);
+ dest.writeParcelable(mConfiguration, flags);
+ }
+
+ /**
+ * Returns the user's time zone behavior capabilities.
+ */
+ @NonNull
+ public TimeZoneCapabilities getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Returns the user's time zone behavior configuration.
+ */
+ @NonNull
+ public TimeZoneConfiguration getConfiguration() {
+ return mConfiguration;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
+ return mCapabilities.equals(that.mCapabilities)
+ && mConfiguration.equals(that.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCapabilities, mConfiguration);
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorCapabilitiesAndConfig{"
+ + "mCapabilities=" + mCapabilities
+ + ", mConfiguration=" + mConfiguration
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl b/core/java/android/app/time/TimeZoneConfiguration.aidl
index 62240ba5946b..8e859299d073 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.aidl
+++ b/core/java/android/app/time/TimeZoneConfiguration.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
parcelable TimeZoneConfiguration;
diff --git a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java
index e879091cd68e..c0a0c21b546d 100644
--- a/core/java/android/app/timezonedetector/TimeZoneConfiguration.java
+++ b/core/java/android/app/time/TimeZoneConfiguration.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.annotation.UserIdInt;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -36,15 +36,13 @@ import java.util.Objects;
* several settings, the device behavior may not be directly affected by the setting value.
*
* <p>Settings can be left absent when updating configuration via {@link
- * TimeZoneDetector#updateConfiguration(TimeZoneConfiguration)} and those settings will not be
+ * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)} and those settings will not be
* changed. Not all configuration settings can be modified by all users: see {@link
- * TimeZoneDetector#getCapabilities()} and {@link TimeZoneCapabilities} for details.
- *
- * <p>See {@link #hasSetting(String)} with {@code PROPERTY_} constants for testing for the presence
- * of individual settings.
+ * TimeManager#getTimeZoneCapabilitiesAndConfig()} and {@link TimeZoneCapabilities} for details.
*
* @hide
*/
+@SystemApi
public final class TimeZoneConfiguration implements Parcelable {
public static final @NonNull Creator<TimeZoneConfiguration> CREATOR =
@@ -58,53 +56,48 @@ public final class TimeZoneConfiguration implements Parcelable {
}
};
- /** All configuration properties */
+ /**
+ * All configuration properties
+ *
+ * @hide
+ */
@StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
@Retention(RetentionPolicy.SOURCE)
@interface Setting {}
/** See {@link TimeZoneConfiguration#isAutoDetectionEnabled()} for details. */
@Setting
- public static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
+ private static final String SETTING_AUTO_DETECTION_ENABLED = "autoDetectionEnabled";
/** See {@link TimeZoneConfiguration#isGeoDetectionEnabled()} for details. */
@Setting
- public static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
+ private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
- private final @UserIdInt int mUserId;
@NonNull private final Bundle mBundle;
private TimeZoneConfiguration(Builder builder) {
- this.mUserId = builder.mUserId;
this.mBundle = Objects.requireNonNull(builder.mBundle);
}
private static TimeZoneConfiguration createFromParcel(Parcel in) {
- return new TimeZoneConfiguration.Builder(in.readInt())
+ return new TimeZoneConfiguration.Builder()
.setPropertyBundleInternal(in.readBundle())
.build();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mUserId);
dest.writeBundle(mBundle);
}
- /** Returns the ID of the user this configuration is associated with. */
- public @UserIdInt int getUserId() {
- return mUserId;
- }
-
- /** Returns {@code true} if all known settings are present. */
+ /**
+ * Returns {@code true} if all known settings are present.
+ *
+ * @hide
+ */
public boolean isComplete() {
- return hasSetting(SETTING_AUTO_DETECTION_ENABLED)
- && hasSetting(SETTING_GEO_DETECTION_ENABLED);
- }
-
- /** Returns true if the specified setting is set. */
- public boolean hasSetting(@Setting String setting) {
- return mBundle.containsKey(setting);
+ return hasIsAutoDetectionEnabled()
+ && hasIsGeoDetectionEnabled();
}
/**
@@ -112,9 +105,10 @@ public final class TimeZoneConfiguration implements Parcelable {
* controls whether a device will attempt to determine the time zone automatically using
* contextual information if the device supports auto detection.
*
- * <p>This setting is global and can be updated by some users.
+ * <p>See {@link TimeZoneCapabilities#getConfigureAutoDetectionEnabledCapability()} for how to
+ * tell if the setting is meaningful for the current user at this time.
*
- * @throws IllegalStateException if the setting has not been set
+ * @throws IllegalStateException if the setting is not present
*/
public boolean isAutoDetectionEnabled() {
enforceSettingPresent(SETTING_AUTO_DETECTION_ENABLED);
@@ -122,21 +116,39 @@ public final class TimeZoneConfiguration implements Parcelable {
}
/**
+ * Returns {@code true} if the {@link #isAutoDetectionEnabled()} setting is present.
+ *
+ * @hide
+ */
+ public boolean hasIsAutoDetectionEnabled() {
+ return mBundle.containsKey(SETTING_AUTO_DETECTION_ENABLED);
+ }
+
+ /**
* Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
- * controls whether a device can use geolocation to determine time zone. Only used when
- * {@link #isAutoDetectionEnabled()} is {@code true} and when the user has allowed their
- * location to be used.
+ * controls whether the device can use geolocation to determine time zone. This value may only
+ * be used by Android under some circumstances. For example, it is not used when
+ * {@link #isGeoDetectionEnabled()} is {@code false}.
*
- * <p>This setting is user-scoped and can be updated by some users.
- * See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabled()}.
+ * <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to
+ * tell if the setting is meaningful for the current user at this time.
*
- * @throws IllegalStateException if the setting has not been set
+ * @throws IllegalStateException if the setting is not present
*/
public boolean isGeoDetectionEnabled() {
enforceSettingPresent(SETTING_GEO_DETECTION_ENABLED);
return mBundle.getBoolean(SETTING_GEO_DETECTION_ENABLED);
}
+ /**
+ * Returns {@code true} if the {@link #isGeoDetectionEnabled()} setting is present.
+ *
+ * @hide
+ */
+ public boolean hasIsGeoDetectionEnabled() {
+ return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -151,20 +163,18 @@ public final class TimeZoneConfiguration implements Parcelable {
return false;
}
TimeZoneConfiguration that = (TimeZoneConfiguration) o;
- return mUserId == that.mUserId
- && mBundle.kindofEquals(that.mBundle);
+ return mBundle.kindofEquals(that.mBundle);
}
@Override
public int hashCode() {
- return Objects.hash(mUserId, mBundle);
+ return Objects.hash(mBundle);
}
@Override
public String toString() {
return "TimeZoneConfiguration{"
- + "mUserId=" + mUserId
- + ", mBundle=" + mBundle
+ + "mBundle=" + mBundle
+ '}';
}
@@ -174,43 +184,43 @@ public final class TimeZoneConfiguration implements Parcelable {
}
}
- /** @hide */
- public static class Builder {
+ /**
+ * A builder for {@link TimeZoneConfiguration} objects.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Builder {
- private final @UserIdInt int mUserId;
private final Bundle mBundle = new Bundle();
/**
- * Creates a new Builder for a userId with no settings held.
+ * Creates a new Builder with no settings held.
*/
- public Builder(@UserIdInt int userId) {
- mUserId = userId;
+ public Builder() {
}
/**
- * Creates a new Builder by copying the user ID and settings from an existing instance.
+ * Creates a new Builder by copying the settings from an existing instance.
*/
- public Builder(TimeZoneConfiguration toCopy) {
- this.mUserId = toCopy.mUserId;
+ public Builder(@NonNull TimeZoneConfiguration toCopy) {
mergeProperties(toCopy);
}
/**
* Merges {@code other} settings into this instances, replacing existing values in this
* where the settings appear in both.
+ *
+ * @hide
*/
- public Builder mergeProperties(TimeZoneConfiguration other) {
- if (mUserId != other.mUserId) {
- throw new IllegalArgumentException(
- "Cannot merge configurations for different user IDs."
- + " this.mUserId=" + this.mUserId
- + ", other.mUserId=" + other.mUserId);
- }
+ @NonNull
+ public Builder mergeProperties(@NonNull TimeZoneConfiguration other) {
this.mBundle.putAll(other.mBundle);
return this;
}
- Builder setPropertyBundleInternal(Bundle bundle) {
+ @NonNull
+ Builder setPropertyBundleInternal(@NonNull Bundle bundle) {
this.mBundle.putAll(bundle);
return this;
}
@@ -218,6 +228,7 @@ public final class TimeZoneConfiguration implements Parcelable {
/**
* Sets the state of the {@link #SETTING_AUTO_DETECTION_ENABLED} setting.
*/
+ @NonNull
public Builder setAutoDetectionEnabled(boolean enabled) {
this.mBundle.putBoolean(SETTING_AUTO_DETECTION_ENABLED, enabled);
return this;
@@ -226,6 +237,7 @@ public final class TimeZoneConfiguration implements Parcelable {
/**
* Sets the state of the {@link #SETTING_GEO_DETECTION_ENABLED} setting.
*/
+ @NonNull
public Builder setGeoDetectionEnabled(boolean enabled) {
this.mBundle.putBoolean(SETTING_GEO_DETECTION_ENABLED, enabled);
return this;
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
index 4f7e1f62928a..af0389a14c4b 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
@@ -16,11 +16,11 @@
package android.app.timezonedetector;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
/**
* System private API to communicate with time zone detector service.
@@ -35,9 +35,9 @@ import android.app.timezonedetector.TimeZoneConfiguration;
* {@hide}
*/
interface ITimeZoneDetectorService {
- TimeZoneCapabilities getCapabilities();
- void addConfigurationListener(ITimeZoneConfigurationListener listener);
- void removeConfigurationListener(ITimeZoneConfigurationListener listener);
+ TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig();
+ void addListener(ITimeZoneDetectorListener listener);
+ void removeListener(ITimeZoneDetectorListener listener);
boolean updateConfiguration(in TimeZoneConfiguration configuration);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 2b1cbf259c55..486232d0f6ed 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -30,70 +30,6 @@ import android.content.Context;
public interface TimeZoneDetector {
/**
- * Returns the current user's time zone capabilities. See {@link TimeZoneCapabilities}.
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- @NonNull
- TimeZoneCapabilities getCapabilities();
-
- /**
- * Modifies the time zone detection configuration.
- *
- * <p>Configuration settings vary in scope: some may be global (affect all users), others may be
- * specific to the current user.
- *
- * <p>The ability to modify configuration settings can be subject to restrictions. For
- * example, they may be determined by device hardware, general policy (i.e. only the primary
- * user can set them), or by a managed device policy. Use {@link #getCapabilities()} to obtain
- * information at runtime about the user's capabilities.
- *
- * <p>Attempts to modify configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_SUPPORTED} or {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_ALLOWED} will have no effect and a {@code false}
- * will be returned. Modifying configuration settings with capabilities that are {@link
- * TimeZoneCapabilities#CAPABILITY_NOT_APPLICABLE} or {@link
- * TimeZoneCapabilities#CAPABILITY_POSSESSED} will succeed. See {@link
- * TimeZoneCapabilities} for further details.
- *
- * <p>If the supplied configuration only has some values set, then only the specified settings
- * will be updated (where the user's capabilities allow) and other settings will be left
- * unchanged.
- *
- * @return {@code true} if all the configuration settings specified have been set to the
- * new values, {@code false} if none have
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
-
- /**
- * An interface that can be used to listen for changes to the time zone detector configuration.
- */
- @FunctionalInterface
- interface TimeZoneConfigurationListener {
- /**
- * Called when something about the time zone configuration on the device has changed.
- * This could be because the current user has changed, one of the device's relevant settings
- * has changed, or something that could affect a user's capabilities has changed.
- * There are no guarantees about the thread used.
- */
- void onChange();
- }
-
- /**
- * Registers a listener that will be informed when something about the time zone configuration
- * changes.
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
- /**
- * Removes a listener previously passed to
- * {@link #addConfigurationListener(ITimeZoneConfigurationListener)}
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener);
-
- /**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
*
* @hide
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
index 4c69732abec9..3bd6b4bd692a 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.ArraySet;
import android.util.Log;
/**
@@ -35,108 +34,12 @@ public final class TimeZoneDetectorImpl implements TimeZoneDetector {
private final ITimeZoneDetectorService mITimeZoneDetectorService;
- private ITimeZoneConfigurationListener mConfigurationReceiver;
- private ArraySet<TimeZoneConfigurationListener> mConfigurationListeners;
-
public TimeZoneDetectorImpl() throws ServiceNotFoundException {
mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
}
@Override
- @NonNull
- public TimeZoneCapabilities getCapabilities() {
- if (DEBUG) {
- Log.d(TAG, "getCapabilities called");
- }
- try {
- return mITimeZoneDetectorService.getCapabilities();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
- public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
- if (DEBUG) {
- Log.d(TAG, "updateConfiguration called: " + configuration);
- }
- try {
- return mITimeZoneDetectorService.updateConfiguration(configuration);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- @Override
- public void addConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
- if (DEBUG) {
- Log.d(TAG, "addConfigurationListener called: " + listener);
- }
- synchronized (this) {
- if (mConfigurationListeners.contains(listener)) {
- return;
- }
- if (mConfigurationReceiver == null) {
- ITimeZoneConfigurationListener iListener =
- new ITimeZoneConfigurationListener.Stub() {
- @Override
- public void onChange() {
- notifyConfigurationListeners();
- }
- };
- mConfigurationReceiver = iListener;
- }
- if (mConfigurationListeners == null) {
- mConfigurationListeners = new ArraySet<>();
- }
-
- boolean wasEmpty = mConfigurationListeners.isEmpty();
- mConfigurationListeners.add(listener);
- if (wasEmpty) {
- try {
- mITimeZoneDetectorService.addConfigurationListener(mConfigurationReceiver);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- private void notifyConfigurationListeners() {
- final ArraySet<TimeZoneConfigurationListener> configurationListeners;
- synchronized (this) {
- configurationListeners = new ArraySet<>(mConfigurationListeners);
- }
- int size = configurationListeners.size();
- for (int i = 0; i < size; i++) {
- configurationListeners.valueAt(i).onChange();
- }
- }
-
- @Override
- public void removeConfigurationListener(@NonNull TimeZoneConfigurationListener listener) {
- if (DEBUG) {
- Log.d(TAG, "removeConfigurationListener called: " + listener);
- }
-
- synchronized (this) {
- if (mConfigurationListeners == null) {
- return;
- }
- boolean wasEmpty = mConfigurationListeners.isEmpty();
- mConfigurationListeners.remove(listener);
- if (mConfigurationListeners.isEmpty() && !wasEmpty) {
- try {
- mITimeZoneDetectorService.removeConfigurationListener(mConfigurationReceiver);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- @Override
public boolean suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index dc267aa8d325..55ec0f8c67bc 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2574,7 +2574,7 @@ public final class BluetoothAdapter {
* Create a listening, insecure RFCOMM Bluetooth socket with Service Record.
* <p>The link key is not required to be authenticated, i.e the communication may be
* vulnerable to Person In the Middle attacks. For Bluetooth 2.1 devices,
- * the link will be encrypted, as encryption is mandartory.
+ * the link will be encrypted, as encryption is mandatory.
* For legacy devices (pre Bluetooth 2.1 devices) the link will not
* be encrypted. Use {@link #listenUsingRfcommWithServiceRecord}, if an
* encrypted and authenticated communication channel is desired.
@@ -2614,7 +2614,7 @@ public final class BluetoothAdapter {
* an input and output capability or just has the ability to display a numeric key,
* a secure socket connection is not possible and this socket can be used.
* Use {@link #listenUsingInsecureRfcommWithServiceRecord}, if encryption is not required.
- * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandartory.
+ * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandatory.
* For more details, refer to the Security Model section 5.2 (vol 3) of
* Bluetooth Core Specification version 2.1 + EDR.
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index a7b19ef8e8f8..6470fb066347 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -53,25 +53,34 @@ public final class BluetoothCodecConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface SourceCodecType {}
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_SBC = 0;
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_AAC = 1;
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_APTX = 2;
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
- public static final int SOURCE_CODEC_TYPE_APTX_ADAPTIVE = 4;
+ @UnsupportedAppUsage
+ public static final int SOURCE_CODEC_TYPE_LDAC = 4;
- public static final int SOURCE_CODEC_TYPE_LDAC = 5;
+ @UnsupportedAppUsage
+ public static final int SOURCE_CODEC_TYPE_APTX_ADAPTIVE = 5;
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_APTX_TWSP = 6;
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_MAX = 7;
/* CELT is not an A2DP Codec and only used to fetch encoder
** format for BA usecase, moving out of a2dp codec value list
*/
+ @UnsupportedAppUsage
public static final int SOURCE_CODEC_TYPE_CELT = 8;
public static final int SOURCE_CODEC_TYPE_LC3 = 9;
@@ -88,10 +97,13 @@ public final class BluetoothCodecConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface CodecPriority {}
+ @UnsupportedAppUsage
public static final int CODEC_PRIORITY_DISABLED = -1;
+ @UnsupportedAppUsage
public static final int CODEC_PRIORITY_DEFAULT = 0;
+ @UnsupportedAppUsage
public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
@@ -108,18 +120,25 @@ public final class BluetoothCodecConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface SampleRate {}
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_NONE = 0;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_44100 = 0x1 << 0;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_48000 = 0x1 << 1;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_88200 = 0x1 << 2;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_96000 = 0x1 << 3;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_176400 = 0x1 << 4;
+ @UnsupportedAppUsage
public static final int SAMPLE_RATE_192000 = 0x1 << 5;
public static final int SAMPLE_RATE_16000 = 0x1 << 6;
@@ -141,12 +160,16 @@ public final class BluetoothCodecConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface BitsPerSample {}
+ @UnsupportedAppUsage
public static final int BITS_PER_SAMPLE_NONE = 0;
+ @UnsupportedAppUsage
public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
+ @UnsupportedAppUsage
public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
+ @UnsupportedAppUsage
public static final int BITS_PER_SAMPLE_32 = 0x1 << 2;
@@ -159,10 +182,13 @@ public final class BluetoothCodecConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface ChannelMode {}
+ @UnsupportedAppUsage
public static final int CHANNEL_MODE_NONE = 0;
+ @UnsupportedAppUsage
public static final int CHANNEL_MODE_MONO = 0x1 << 0;
+ @UnsupportedAppUsage
public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
public static final int CHANNEL_MODE_JOINT_STEREO = 0x1 << 2;
@@ -176,6 +202,7 @@ public final class BluetoothCodecConfig implements Parcelable {
private final long mCodecSpecific3;
private final long mCodecSpecific4;
+ @UnsupportedAppUsage
public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority,
@SampleRate int sampleRate, @BitsPerSample int bitsPerSample,
@ChannelMode int channelMode, long codecSpecific1,
@@ -192,6 +219,7 @@ public final class BluetoothCodecConfig implements Parcelable {
mCodecSpecific4 = codecSpecific4;
}
+ @UnsupportedAppUsage
public BluetoothCodecConfig(@SourceCodecType int codecType) {
mCodecType = codecType;
mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
@@ -417,6 +445,7 @@ public final class BluetoothCodecConfig implements Parcelable {
*
* @return the codec type
*/
+ @UnsupportedAppUsage
public @SourceCodecType int getCodecType() {
return mCodecType;
}
@@ -437,6 +466,7 @@ public final class BluetoothCodecConfig implements Parcelable {
*
* @return the codec priority
*/
+ @UnsupportedAppUsage
public @CodecPriority int getCodecPriority() {
return mCodecPriority;
}
@@ -467,6 +497,7 @@ public final class BluetoothCodecConfig implements Parcelable {
*
* @return the codec sample rate
*/
+ @UnsupportedAppUsage
public @SampleRate int getSampleRate() {
return mSampleRate;
}
@@ -481,6 +512,7 @@ public final class BluetoothCodecConfig implements Parcelable {
*
* @return the codec bits per sample
*/
+ @UnsupportedAppUsage
public @BitsPerSample int getBitsPerSample() {
return mBitsPerSample;
}
@@ -505,6 +537,7 @@ public final class BluetoothCodecConfig implements Parcelable {
*
* @return a codec specific value1.
*/
+ @UnsupportedAppUsage
public long getCodecSpecific1() {
return mCodecSpecific1;
}
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 1e394b830d51..7b567b4098e7 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -17,6 +17,7 @@
package android.bluetooth;
import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -38,6 +39,7 @@ public final class BluetoothCodecStatus implements Parcelable {
* This extra represents the current codec status of the A2DP
* profile.
*/
+ @UnsupportedAppUsage
public static final String EXTRA_CODEC_STATUS =
"android.bluetooth.extra.CODEC_STATUS";
@@ -196,6 +198,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return the current codec configuration
*/
+ @UnsupportedAppUsage
public @Nullable BluetoothCodecConfig getCodecConfig() {
return mCodecConfig;
}
@@ -205,6 +208,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return an array with the codecs local capabilities
*/
+ @UnsupportedAppUsage
public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
return mCodecsLocalCapabilities;
}
@@ -214,6 +218,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return an array with the codecs selectable capabilities
*/
+ @UnsupportedAppUsage
public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
return mCodecsSelectableCapabilities;
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c58b5d218e74..6d22eb93fd02 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -688,6 +688,31 @@ public final class BluetoothGatt implements BluetoothProfile {
}
});
}
+
+ /**
+ * Callback invoked when service changed event is received
+ * @hide
+ */
+ @Override
+ public void onServiceChanged(String address) {
+ if (DBG) {
+ Log.d(TAG, "onServiceChanged() - Device=" + address);
+ }
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+
+ runOrQueueCallback(new Runnable() {
+ @Override
+ public void run() {
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onServiceChanged(BluetoothGatt.this);
+ }
+ }
+ });
+ }
};
/*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index f718c0b57c1b..1c40cff076f6 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import android.annotation.NonNull;
+
/**
* This abstract class is used to implement {@link BluetoothGatt} callbacks.
*/
@@ -194,4 +196,16 @@ public abstract class BluetoothGattCallback {
public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
int status) {
}
+
+ /**
+ * Callback indicating service changed event is received
+ *
+ * <p>Receiving this event means that the GATT database is out of sync with
+ * the remote device. {@link BluetoothGatt#discoverServices} should be
+ * called to re-discover the services.
+ *
+ * @param gatt GATT client involved
+ */
+ public void onServiceChanged(@NonNull BluetoothGatt gatt) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index b5959c06cc1a..2baa73822c9c 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -342,44 +342,72 @@ public final class BluetoothHidDevice implements BluetoothProfile {
@Override
public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onConnectionStateChanged(BluetoothDevice device, int state) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onSetProtocol(BluetoothDevice device, byte protocol) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
@Override
public void onVirtualCableUnplug(BluetoothDevice device) {
- clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
+ final long token = clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
+ } finally {
+ restoreCallingIdentity(token);
+ }
}
}
diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java
index d7f22bf4ac1b..eb23cf658d15 100644
--- a/core/java/android/bluetooth/le/AdvertiseData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -16,6 +16,7 @@
package android.bluetooth.le;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.ParcelUuid;
@@ -43,6 +44,9 @@ public final class AdvertiseData implements Parcelable {
@Nullable
private final List<ParcelUuid> mServiceUuids;
+ @Nullable
+ private final List<ParcelUuid> mServiceSolicitationUuids;
+
private final SparseArray<byte[]> mManufacturerSpecificData;
private final Map<ParcelUuid, byte[]> mServiceData;
private final boolean mIncludeTxPowerLevel;
@@ -50,12 +54,14 @@ public final class AdvertiseData implements Parcelable {
private final byte[] mTransportDiscoveryData;
private AdvertiseData(List<ParcelUuid> serviceUuids,
+ List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
Map<ParcelUuid, byte[]> serviceData,
boolean includeTxPowerLevel,
boolean includeDeviceName,
byte[] transportDiscoveryData) {
mServiceUuids = serviceUuids;
+ mServiceSolicitationUuids = serviceSolicitationUuids;
mManufacturerSpecificData = manufacturerData;
mServiceData = serviceData;
mIncludeTxPowerLevel = includeTxPowerLevel;
@@ -72,6 +78,14 @@ public final class AdvertiseData implements Parcelable {
}
/**
+ * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect.
+ */
+ @Nullable
+ public List<ParcelUuid> getServiceSolicitationUuids() {
+ return mServiceSolicitationUuids;
+ }
+
+ /**
* Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
* manufacturer id is a non-negative number assigned by Bluetooth SIG.
*/
@@ -113,8 +127,8 @@ public final class AdvertiseData implements Parcelable {
*/
@Override
public int hashCode() {
- return Objects.hash(mServiceUuids, mManufacturerSpecificData, mServiceData,
- mIncludeDeviceName, mIncludeTxPowerLevel, mTransportDiscoveryData);
+ return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mManufacturerSpecificData,
+ mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel, mTransportDiscoveryData);
}
/**
@@ -130,6 +144,7 @@ public final class AdvertiseData implements Parcelable {
}
AdvertiseData other = (AdvertiseData) obj;
return Objects.equals(mServiceUuids, other.mServiceUuids)
+ && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids)
&& BluetoothLeUtils.equals(mManufacturerSpecificData,
other.mManufacturerSpecificData)
&& BluetoothLeUtils.equals(mServiceData, other.mServiceData)
@@ -140,7 +155,8 @@ public final class AdvertiseData implements Parcelable {
@Override
public String toString() {
- return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerSpecificData="
+ return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids="
+ + mServiceSolicitationUuids + ", mManufacturerSpecificData="
+ BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
+ BluetoothLeUtils.toString(mServiceData)
+ ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
@@ -156,6 +172,8 @@ public final class AdvertiseData implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags);
+ dest.writeTypedArray(mServiceSolicitationUuids.toArray(
+ new ParcelUuid[mServiceSolicitationUuids.size()]), flags);
// mManufacturerSpecificData could not be null.
dest.writeInt(mManufacturerSpecificData.size());
@@ -191,6 +209,11 @@ public final class AdvertiseData implements Parcelable {
builder.addServiceUuid(uuid);
}
+ ArrayList<ParcelUuid> solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR);
+ for (ParcelUuid uuid : solicitationUuids) {
+ builder.addServiceSolicitationUuid(uuid);
+ }
+
int manufacturerSize = in.readInt();
for (int i = 0; i < manufacturerSize; ++i) {
int manufacturerId = in.readInt();
@@ -220,6 +243,8 @@ public final class AdvertiseData implements Parcelable {
public static final class Builder {
@Nullable
private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
+ @Nullable
+ private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
private boolean mIncludeTxPowerLevel;
@@ -230,17 +255,31 @@ public final class AdvertiseData implements Parcelable {
* Add a service UUID to advertise data.
*
* @param serviceUuid A service UUID to be advertised.
- * @throws IllegalArgumentException If the {@code serviceUuids} are null.
+ * @throws IllegalArgumentException If the {@code serviceUuid} is null.
*/
public Builder addServiceUuid(ParcelUuid serviceUuid) {
if (serviceUuid == null) {
- throw new IllegalArgumentException("serivceUuids are null");
+ throw new IllegalArgumentException("serviceUuid is null");
}
mServiceUuids.add(serviceUuid);
return this;
}
/**
+ * Add a service solicitation UUID to advertise data.
+ *
+ * @param serviceSolicitationUuid A service solicitation UUID to be advertised.
+ * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null.
+ */
+ @NonNull
+ public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) {
+ if (serviceSolicitationUuid == null) {
+ throw new IllegalArgumentException("serviceSolicitationUuid is null");
+ }
+ mServiceSolicitationUuids.add(serviceSolicitationUuid);
+ return this;
+ }
+ /**
* Add service data to advertise data.
*
* @param serviceDataUuid 16-bit UUID of the service the data is associated with
@@ -314,8 +353,9 @@ public final class AdvertiseData implements Parcelable {
* Build the {@link AdvertiseData}.
*/
public AdvertiseData build() {
- return new AdvertiseData(mServiceUuids, mManufacturerSpecificData, mServiceData,
- mIncludeTxPowerLevel, mIncludeDeviceName, mTransportDiscoveryData);
+ return new AdvertiseData(mServiceUuids, mServiceSolicitationUuids,
+ mManufacturerSpecificData, mServiceData, mIncludeTxPowerLevel,
+ mIncludeDeviceName, mTransportDiscoveryData);
}
}
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 13c5ff690973..5f166f4a41da 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -507,6 +507,33 @@ public final class BluetoothLeAdvertiser {
+ num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
}
}
+ if (data.getServiceSolicitationUuids() != null) {
+ int num16BitUuids = 0;
+ int num32BitUuids = 0;
+ int num128BitUuids = 0;
+ for (ParcelUuid uuid : data.getServiceSolicitationUuids()) {
+ if (BluetoothUuid.is16BitUuid(uuid)) {
+ ++num16BitUuids;
+ } else if (BluetoothUuid.is32BitUuid(uuid)) {
+ ++num32BitUuids;
+ } else {
+ ++num128BitUuids;
+ }
+ }
+ // 16 bit service uuids are grouped into one field when doing advertising.
+ if (num16BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
+ }
+ // 32 bit service uuids are grouped into one field when doing advertising.
+ if (num32BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
+ }
+ // 128 bit service uuids are grouped into one field when doing advertising.
+ if (num128BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD
+ + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
+ }
+ }
for (ParcelUuid uuid : data.getServiceData().keySet()) {
int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
size += OVERHEAD_BYTES_PER_FIELD + uuidLen
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 33be50d34777..6cb5b92abb37 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1043,6 +1043,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
* calling identity by passing it to
* {@link #restoreCallingIdentity}.
*/
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
public final @NonNull CallingIdentity clearCallingIdentity() {
return new CallingIdentity(Binder.clearCallingIdentity(), setCallingPackage(null));
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 38bc79750631..c4157cfe09ad 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@ import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
+import android.app.time.TimeManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -351,6 +352,13 @@ public abstract class Context {
* due to its foreground state such as an activity or foreground service, then this flag will
* allow the bound app to get the same capabilities, as long as it has the required permissions
* as well.
+ *
+ * If binding from a top app and its target SDK version is at or above
+ * {@link android.os.Build.VERSION_CODES#R}, the app needs to
+ * explicitly use BIND_INCLUDE_CAPABILITIES flag to pass all capabilities to the service so the
+ * other app can have while-use-use access such as location, camera, microphone from background.
+ * If binding from a top app and its target SDK version is below
+ * {@link android.os.Build.VERSION_CODES#R}, BIND_INCLUDE_CAPABILITIES is implicit.
*/
public static final int BIND_INCLUDE_CAPABILITIES = 0x000001000;
@@ -3179,8 +3187,8 @@ public abstract class Context {
* {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
* {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
* {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
- * {@link #BIND_IMPORTANT}, or
- * {@link #BIND_ADJUST_WITH_ACTIVITY}.
+ * {@link #BIND_IMPORTANT}, {@link #BIND_ADJUST_WITH_ACTIVITY},
+ * {@link #BIND_NOT_PERCEPTIBLE}, or {@link #BIND_INCLUDE_CAPABILITIES}.
* @return {@code true} if the system is in the process of bringing up a
* service that your client has permission to bind to; {@code false}
* if the system couldn't find the service or if your client doesn't
@@ -3201,6 +3209,8 @@ public abstract class Context {
* @see #BIND_WAIVE_PRIORITY
* @see #BIND_IMPORTANT
* @see #BIND_ADJUST_WITH_ACTIVITY
+ * @see #BIND_NOT_PERCEPTIBLE
+ * @see #BIND_INCLUDE_CAPABILITIES
*/
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
@@ -3498,6 +3508,7 @@ public abstract class Context {
PERMISSION_SERVICE,
LIGHTS_SERVICE,
//@hide: PEOPLE_SERVICE,
+ //@hide: DEVICE_STATE_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -4492,6 +4503,14 @@ public abstract class Context {
public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
/**
+ * Used to access {@link MusicRecognitionManagerService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String MUSIC_RECOGNITION_SERVICE = "music_recognition";
+
+ /**
* Official published name of the (internal) permission service.
*
* @see #getSystemService(String)
@@ -5081,6 +5100,14 @@ public abstract class Context {
public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an {@link TimeManager}.
+ * @hide
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String TIME_MANAGER = "time_manager";
+
+ /**
* Binder service name for {@link AppBindingService}.
* @hide
*/
@@ -5220,6 +5247,14 @@ public abstract class Context {
public static final String PEOPLE_SERVICE = "people";
/**
+ * Use with {@link #getSystemService(String)} to access device state service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String DEVICE_STATE_SERVICE = "device_state";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -5820,10 +5855,19 @@ public abstract class Context {
* {@link #createWindowContext(int, Bundle)} on the returned display Context or use an
* {@link android.app.Activity}.
*
+ * <p>
+ * Note that invoking #createDisplayContext(Display) from an UI context is not regarded
+ * as an UI context. In other words, it is not suggested to access UI components (such as
+ * obtain a {@link WindowManager} by {@link #getSystemService(String)})
+ * from the context created from #createDisplayContext(Display).
+ * </p>
+ *
* @param display A {@link Display} object specifying the display for whose metrics the
* Context's resources should be tailored.
*
* @return A {@link Context} for the display.
+ *
+ * @see #getSystemService(String)
*/
@DisplayContext
public abstract Context createDisplayContext(@NonNull Display display);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1fb432b6016c..b4e16a1bbf31 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -620,6 +620,7 @@ import java.util.TimeZone;
* <li> {@link #EXTRA_PHONE_NUMBER}
* <li> {@link #EXTRA_REFERRER}
* <li> {@link #EXTRA_REMOTE_INTENT_TOKEN}
+ * <li> {@link #EXTRA_REMOVED_BY_SYSTEM}
* <li> {@link #EXTRA_REPLACING}
* <li> {@link #EXTRA_SHORTCUT_ICON}
* <li> {@link #EXTRA_SHORTCUT_ICON_RESOURCE}
@@ -2464,6 +2465,8 @@ public class Intent implements Parcelable, Cloneable {
* application -- data and code -- is being removed.
* <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
* by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
+ * <li> {@link #EXTRA_REMOVED_BY_SYSTEM} containing boolean field to to signal that the
+ * application was removed automatically without the user-initiated action.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent
@@ -2732,6 +2735,52 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_MY_PACKAGE_UNSUSPENDED = "android.intent.action.MY_PACKAGE_UNSUSPENDED";
/**
+ * Broadcast Action: Sent to indicate that the package becomes startable.
+ * The intent will have the following extra values:
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
+ * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
+ * </li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
+
+ /**
+ * Broadcast Action: Sent to indicate that the package becomes unstartable.
+ * The intent will have the following extra values:
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
+ * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
+ * <li> {@link #EXTRA_UNSTARTABLE_REASON} containing the integer indicating the reason for
+ * the state change,
+ * @see PackageManager.UnstartableReason
+ * </li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_UNSTARTABLE =
+ "android.intent.action.PACKAGE_UNSTARTABLE";
+
+ /**
+ * Broadcast Action: Sent to indicate that the package is fully loaded.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. </li>
+ * <li> {@link #EXTRA_PACKAGE_NAME} containing the package name. </li>
+ * </li>
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_FULLY_LOADED =
+ "android.intent.action.PACKAGE_FULLY_LOADED";
+
+ /**
* Broadcast Action: A user ID has been removed from the system. The user
* ID number is stored in the extra data under {@link #EXTRA_UID}.
*
@@ -5521,6 +5570,14 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
/**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to signal that the application was removed automatically without the user-initiated
+ * action.
+ */
+ public static final String EXTRA_REMOVED_BY_SYSTEM =
+ "android.intent.extra.REMOVED_BY_SYSTEM";
+
+ /**
* A String holding the phone number originally entered in
* {@link android.content.Intent#ACTION_NEW_OUTGOING_CALL}, or the actual
* number to call in a {@link android.content.Intent#ACTION_CALL}.
@@ -5968,6 +6025,13 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
+ /**
+ * Intent extra: the reason that the package associated with this intent has become unstartable.
+ *
+ * <p>Type: String
+ */
+ public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index eec7c9cc5a63..159db92c79c9 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -116,6 +116,10 @@ public final class PermissionChecker {
* will evaluate the permission access based on the current fg/bg state of the app and
* leave a record that the data was accessed.
*
+ * <p>For more details how to determine the {@code packageName}, {@code attributionTag}, and
+ * {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
* @param context Context for accessing resources.
* @param permission The permission to check.
* @param pid The process id for which to check. Use {@link #PID_UNKNOWN} if the PID
@@ -262,11 +266,15 @@ public final class PermissionChecker {
* will evaluate the permission access based on the current fg/bg state of the app and
* leave a record that the data was accessed.
*
+ * <p>For more details how to determine the {@code callingPackageName},
+ * {@code callingAttributionTag}, and {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
* @param context Context for accessing resources.
* @param permission The permission to check.
- * @param packageName The package name making the IPC. If null the
+ * @param callingPackageName The package name making the IPC. If null the
* the first package for the calling UID will be used.
- * @param attributionTag attribution tag
+ * @param callingAttributionTag attribution tag
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
* or {@link #PERMISSION_SOFT_DENIED} or {@link #PERMISSION_HARD_DENIED}.
* @param message A message describing the reason the permission was checked
@@ -275,13 +283,13 @@ public final class PermissionChecker {
*/
@PermissionResult
public static int checkCallingPermissionForDataDelivery(@NonNull Context context,
- @NonNull String permission, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable String message) {
+ @NonNull String permission, @Nullable String callingPackageName,
+ @Nullable String callingAttributionTag, @Nullable String message) {
if (Binder.getCallingPid() == Process.myPid()) {
return PERMISSION_HARD_DENIED;
}
return checkPermissionForDataDelivery(context, permission, Binder.getCallingPid(),
- Binder.getCallingUid(), packageName, attributionTag, message);
+ Binder.getCallingUid(), callingPackageName, callingAttributionTag, message);
}
/**
@@ -339,6 +347,10 @@ public final class PermissionChecker {
* will evaluate the permission access based on the current fg/bg state of the app and
* leave a record that the data was accessed.
*
+ * <p>For more details how to determine the {@code callingPackageName},
+ * {@code callingAttributionTag}, and {@code message}, please check the description in
+ * {@link AppOpsManager#noteOp(String, int, String, String, String)}
+ *
* @param context Context for accessing resources.
* @param permission The permission to check.
* @return The permission check result which is either {@link #PERMISSION_GRANTED}
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index d35dfdbf32c8..d8f885439b34 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -21,6 +21,20 @@
"include-filter": "android.appsecurity.cts.OverlayHostTest"
}
]
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.om.cts"
+ }
+ ]
}
]
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 31c77eeb5424..ea4b76257ab7 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -831,6 +831,16 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
*/
public static final int CONFIG_WINDOW_CONFIGURATION = 0x20000000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to bold text. Set from the
+ * {@link android.R.attr#configChanges} attribute. This is
+ * not a core resource configuration, but a higher-level value, so its
+ * constant starts at the high bits.
+ */
+
+ public static final int CONFIG_FORCE_BOLD_TEXT = 0x10000000;
+
/** @hide
* Unfortunately the constants for config changes in native code are
* different from ActivityInfo. :( Here are the values we should use for the
@@ -1099,6 +1109,34 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
}
/**
+ * Returns the reversed orientation.
+ * @hide
+ */
+ @ActivityInfo.ScreenOrientation
+ public static int reverseOrientation(@ActivityInfo.ScreenOrientation int orientation) {
+ switch (orientation) {
+ case SCREEN_ORIENTATION_LANDSCAPE:
+ return SCREEN_ORIENTATION_PORTRAIT;
+ case SCREEN_ORIENTATION_PORTRAIT:
+ return SCREEN_ORIENTATION_LANDSCAPE;
+ case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ return SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ return SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ return SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ return SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ case SCREEN_ORIENTATION_USER_LANDSCAPE:
+ return SCREEN_ORIENTATION_USER_PORTRAIT;
+ case SCREEN_ORIENTATION_USER_PORTRAIT:
+ return SCREEN_ORIENTATION_USER_LANDSCAPE;
+ default:
+ return orientation;
+ }
+ }
+
+ /**
* Returns true if the activity supports picture-in-picture.
* @hide
*/
diff --git a/core/java/android/content/pm/ApkChecksum.java b/core/java/android/content/pm/ApkChecksum.java
index e087c44d1ed1..eca48eca9e4b 100644
--- a/core/java/android/content/pm/ApkChecksum.java
+++ b/core/java/android/content/pm/ApkChecksum.java
@@ -18,7 +18,6 @@ package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
@@ -31,12 +30,11 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.List;
/**
* A typed checksum of an APK.
*
- * @see PackageManager#getChecksums(String, boolean, int, List, IntentSender)
+ * @see PackageManager#requestChecksums
*/
@DataClass(genHiddenConstructor = true)
@DataClass.Suppress({"getChecksum"})
@@ -50,18 +48,22 @@ public final class ApkChecksum implements Parcelable {
*/
private final @NonNull Checksum mChecksum;
/**
- * For Installer-provided checksums, certificate of the Installer/AppStore.
+ * For Installer-provided checksums, package name of the Installer.
*/
- private final @Nullable byte[] mSourceCertificate;
+ private final @Nullable String mInstallerPackageName;
+ /**
+ * For Installer-provided checksums, certificate of the Installer.
+ */
+ private final @Nullable byte[] mInstallerCertificate;
/**
* Constructor, internal use only.
*
* @hide
*/
- public ApkChecksum(@Nullable String splitName, @Checksum.Kind int kind,
+ public ApkChecksum(@Nullable String splitName, @Checksum.Type int type,
@NonNull byte[] value) {
- this(splitName, new Checksum(kind, value), (byte[]) null);
+ this(splitName, new Checksum(type, value), (String) null, (byte[]) null);
}
/**
@@ -69,19 +71,19 @@ public final class ApkChecksum implements Parcelable {
*
* @hide
*/
- public ApkChecksum(@Nullable String splitName, @Checksum.Kind int kind,
- @NonNull byte[] value, @Nullable Certificate sourceCertificate)
+ public ApkChecksum(@Nullable String splitName, @Checksum.Type int type, @NonNull byte[] value,
+ @Nullable String sourcePackageName, @Nullable Certificate sourceCertificate)
throws CertificateEncodingException {
- this(splitName, new Checksum(kind, value),
+ this(splitName, new Checksum(type, value), sourcePackageName,
(sourceCertificate != null) ? sourceCertificate.getEncoded() : null);
}
/**
- * Checksum kind.
+ * Checksum type.
*/
- public @Checksum.Kind int getKind() {
- return mChecksum.getKind();
+ public @Checksum.Type int getType() {
+ return mChecksum.getType();
}
/**
@@ -92,24 +94,24 @@ public final class ApkChecksum implements Parcelable {
}
/**
- * Returns raw bytes representing encoded certificate of the source of this checksum.
+ * Returns raw bytes representing encoded certificate of the Installer.
* @hide
*/
- public @Nullable byte[] getSourceCertificateBytes() {
- return mSourceCertificate;
+ public @Nullable byte[] getInstallerCertificateBytes() {
+ return mInstallerCertificate;
}
/**
- * Certificate of the source of this checksum.
+ * For Installer-provided checksums, certificate of the Installer.
* @throws CertificateException in case when certificate can't be re-created from serialized
* data.
*/
- public @Nullable Certificate getSourceCertificate() throws CertificateException {
- if (mSourceCertificate == null) {
+ public @Nullable Certificate getInstallerCertificate() throws CertificateException {
+ if (mInstallerCertificate == null) {
return null;
}
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
- final InputStream is = new ByteArrayInputStream(mSourceCertificate);
+ final InputStream is = new ByteArrayInputStream(mInstallerCertificate);
final X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
return cert;
}
@@ -136,20 +138,24 @@ public final class ApkChecksum implements Parcelable {
* Checksum for which split. Null indicates base.apk.
* @param checksum
* Checksum.
- * @param sourceCertificate
- * For Installer-provided checksums, certificate of the Installer/AppStore.
+ * @param installerPackageName
+ * For Installer-provided checksums, package name of the Installer.
+ * @param installerCertificate
+ * For Installer-provided checksums, certificate of the Installer.
* @hide
*/
@DataClass.Generated.Member
public ApkChecksum(
@Nullable String splitName,
@NonNull Checksum checksum,
- @Nullable byte[] sourceCertificate) {
+ @Nullable String installerPackageName,
+ @Nullable byte[] installerCertificate) {
this.mSplitName = splitName;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mChecksum);
- this.mSourceCertificate = sourceCertificate;
+ this.mInstallerPackageName = installerPackageName;
+ this.mInstallerCertificate = installerCertificate;
// onConstructed(); // You can define this method to get a callback
}
@@ -162,6 +168,14 @@ public final class ApkChecksum implements Parcelable {
return mSplitName;
}
+ /**
+ * For Installer-provided checksums, package name of the Installer.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getInstallerPackageName() {
+ return mInstallerPackageName;
+ }
+
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -170,11 +184,13 @@ public final class ApkChecksum implements Parcelable {
byte flg = 0;
if (mSplitName != null) flg |= 0x1;
- if (mSourceCertificate != null) flg |= 0x4;
+ if (mInstallerPackageName != null) flg |= 0x4;
+ if (mInstallerCertificate != null) flg |= 0x8;
dest.writeByte(flg);
if (mSplitName != null) dest.writeString(mSplitName);
dest.writeTypedObject(mChecksum, flags);
- if (mSourceCertificate != null) dest.writeByteArray(mSourceCertificate);
+ if (mInstallerPackageName != null) dest.writeString(mInstallerPackageName);
+ if (mInstallerCertificate != null) dest.writeByteArray(mInstallerCertificate);
}
@Override
@@ -191,13 +207,15 @@ public final class ApkChecksum implements Parcelable {
byte flg = in.readByte();
String splitName = (flg & 0x1) == 0 ? null : in.readString();
Checksum checksum = (Checksum) in.readTypedObject(Checksum.CREATOR);
- byte[] sourceCertificate = (flg & 0x4) == 0 ? null : in.createByteArray();
+ String installerPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ byte[] installerCertificate = (flg & 0x8) == 0 ? null : in.createByteArray();
this.mSplitName = splitName;
this.mChecksum = checksum;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mChecksum);
- this.mSourceCertificate = sourceCertificate;
+ this.mInstallerPackageName = installerPackageName;
+ this.mInstallerCertificate = installerCertificate;
// onConstructed(); // You can define this method to get a callback
}
@@ -217,10 +235,10 @@ public final class ApkChecksum implements Parcelable {
};
@DataClass.Generated(
- time = 1599845645160L,
+ time = 1601589269293L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
- inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable byte[] mSourceCertificate\npublic @android.content.pm.Checksum.Kind int getKind()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getSourceCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getSourceCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/Checksum.java b/core/java/android/content/pm/Checksum.java
index 123aaddda7ce..318d3635ede1 100644
--- a/core/java/android/content/pm/Checksum.java
+++ b/core/java/android/content/pm/Checksum.java
@@ -25,87 +25,107 @@ import com.android.internal.util.DataClass;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.List;
/**
* A typed checksum.
*
- * @see PackageInstaller.Session#addChecksums(String, List)
+ * @see ApkChecksum
+ * @see PackageManager#requestChecksums
*/
-@DataClass(genHiddenConstructor = true, genConstDefs = false)
+@DataClass(genConstDefs = false)
public final class Checksum implements Parcelable {
/**
* Root SHA256 hash of a 4K Merkle tree computed over all file bytes.
* <a href="https://source.android.com/security/apksigning/v4">See APK Signature Scheme V4</a>.
* <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst">See fs-verity</a>.
*
- * @see PackageManager#getChecksums
+ * Recommended for all new applications.
+ * Can be used by kernel to enforce authenticity and integrity of the APK.
+ * <a href="https://git.kernel.org/pub/scm/fs/fscrypt/fscrypt.git/tree/Documentation/filesystems/fsverity.rst#">See fs-verity for details</a>
+ *
+ * @see PackageManager#requestChecksums
*/
- public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
+ public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 = 0x00000001;
/**
* MD5 hash computed over all file bytes.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
+ * @deprecated Not platform enforced. Cryptographically broken and unsuitable for further use.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
*/
- public static final int WHOLE_MD5 = 0x00000002;
+ @Deprecated
+ public static final int TYPE_WHOLE_MD5 = 0x00000002;
/**
* SHA1 hash computed over all file bytes.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
+ * @deprecated Not platform enforced. Broken and should not be used.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
*/
- public static final int WHOLE_SHA1 = 0x00000004;
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA1 = 0x00000004;
/**
* SHA256 hash computed over all file bytes.
+ * @deprecated Not platform enforced.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
*/
- public static final int WHOLE_SHA256 = 0x00000008;
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA256 = 0x00000008;
/**
* SHA512 hash computed over all file bytes.
+ * @deprecated Not platform enforced.
+ * Use platform enforced digests e.g. {@link #TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}.
+ * Provided for completeness' sake and to support legacy usecases.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
*/
- public static final int WHOLE_SHA512 = 0x00000010;
+ @Deprecated
+ public static final int TYPE_WHOLE_SHA512 = 0x00000010;
/**
* Root SHA256 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
*/
- public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
+ public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 = 0x00000020;
/**
* Root SHA512 hash of a 1M Merkle tree computed over protected content.
* Excludes signing block.
* <a href="https://source.android.com/security/apksigning/v2">See APK Signature Scheme V2</a>.
*
- * @see PackageManager#getChecksums
+ * @see PackageManager#requestChecksums
*/
- public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
+ public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512 = 0x00000040;
/** @hide */
- @IntDef(flag = true, prefix = {"WHOLE_", "PARTIAL_"}, value = {
- WHOLE_MERKLE_ROOT_4K_SHA256,
- WHOLE_MD5,
- WHOLE_SHA1,
- WHOLE_SHA256,
- WHOLE_SHA512,
- PARTIAL_MERKLE_ROOT_1M_SHA256,
- PARTIAL_MERKLE_ROOT_1M_SHA512,
+ @IntDef(flag = true, prefix = {"TYPE_"}, value = {
+ TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
+ TYPE_WHOLE_MD5,
+ TYPE_WHOLE_SHA1,
+ TYPE_WHOLE_SHA256,
+ TYPE_WHOLE_SHA512,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Kind {}
+ public @interface Type {}
/**
- * Checksum kind.
+ * Checksum type.
*/
- private final @Checksum.Kind int mKind;
+ private final @Checksum.Type int mType;
/**
* Checksum value.
*/
@@ -129,19 +149,18 @@ public final class Checksum implements Parcelable {
/**
* Creates a new Checksum.
*
- * @param kind
- * Checksum kind.
+ * @param type
+ * Checksum type.
* @param value
* Checksum value.
- * @hide
*/
@DataClass.Generated.Member
public Checksum(
- @Checksum.Kind int kind,
+ @Checksum.Type int type,
@NonNull byte[] value) {
- this.mKind = kind;
+ this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
- Checksum.Kind.class, null, mKind);
+ Checksum.Type.class, null, mType);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
@@ -150,11 +169,11 @@ public final class Checksum implements Parcelable {
}
/**
- * Checksum kind.
+ * Checksum type.
*/
@DataClass.Generated.Member
- public @Checksum.Kind int getKind() {
- return mKind;
+ public @Checksum.Type int getType() {
+ return mType;
}
/**
@@ -171,7 +190,7 @@ public final class Checksum implements Parcelable {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- dest.writeInt(mKind);
+ dest.writeInt(mType);
dest.writeByteArray(mValue);
}
@@ -186,12 +205,12 @@ public final class Checksum implements Parcelable {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- int kind = in.readInt();
+ int type = in.readInt();
byte[] value = in.createByteArray();
- this.mKind = kind;
+ this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
- Checksum.Kind.class, null, mKind);
+ Checksum.Type.class, null, mType);
this.mValue = value;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mValue);
@@ -214,10 +233,10 @@ public final class Checksum implements Parcelable {
};
@DataClass.Generated(
- time = 1599845646883L,
+ time = 1601955017774L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/content/pm/Checksum.java",
- inputSignatures = "public static final int WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final int WHOLE_MD5\npublic static final int WHOLE_SHA1\npublic static final int WHOLE_SHA256\npublic static final int WHOLE_SHA512\npublic static final int PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final int PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Kind int mKind\nprivate final @android.annotation.NonNull byte[] mValue\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genConstDefs=false)")
+ inputSignatures = "public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_MD5\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA1\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA256\npublic static final @java.lang.Deprecated int TYPE_WHOLE_SHA512\npublic static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256\npublic static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512\nprivate final @android.content.pm.Checksum.Type int mType\nprivate final @android.annotation.NonNull byte[] mValue\nclass Checksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index efb00a016da5..745c39b460fa 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -50,7 +50,30 @@ oneway interface IDataLoaderStatusListener {
* fail and all retry limits are exceeded. */
const int DATA_LOADER_UNRECOVERABLE = 8;
+ /** There are no known issues with the data stream. */
+ const int STREAM_HEALTHY = 0;
+
+ /** There are issues with the current transport layer (network, adb connection, etc.) that may
+ * recover automatically or could eventually require user intervention. */
+ const int STREAM_TRANSPORT_ERROR = 1;
+
+ /** Integrity failures in the data stream, this could be due to file corruption, decompression
+ * issues or similar. This indicates a likely unrecoverable error. */
+ const int STREAM_INTEGRITY_ERROR = 2;
+
+ /** There are issues with the source of the data, e.g., backend availability issues, account
+ * issues. This indicates a potentially recoverable error, but one that may take a long time to
+ * resolve. */
+ const int STREAM_SOURCE_ERROR = 3;
+
+ /** The device or app is low on storage and cannot complete the stream as a result.
+ * A subsequent page miss resulting in app failure will transition app to unstartable state. */
+ const int STREAM_STORAGE_ERROR = 4;
+
/** Data loader status callback */
void onStatusChanged(in int dataLoaderId, in int status);
+
+ /** Callback to report streaming health status of a specific data loader */
+ void reportStreamHealth(in int dataLoaderId, in int streamStatus);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index af2b9056dc81..30f3325cc11c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -379,8 +379,7 @@ interface IPackageManager {
/**
* Logs process start information (including APK hash) to the security log.
*/
- void logAppProcessStartIfNeeded(String processName, int uid, String seinfo, String apkFile,
- int pid);
+ void logAppProcessStartIfNeeded(String packageName, String processName, int uid, String seinfo, String apkFile, int pid);
/**
* As per {@link android.content.pm.PackageManager#flushPackageRestrictionsAsUser}.
@@ -749,7 +748,7 @@ interface IPackageManager {
void notifyPackagesReplacedReceived(in String[] packages);
- void getChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId);
+ void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId);
//------------------------------------------------------------------------
//
@@ -793,4 +792,8 @@ interface IPackageManager {
List<String> getMimeGroup(String packageName, String group);
boolean isAutoRevokeWhitelisted(String packageName);
+
+ void grantImplicitAccess(int queryingUid, String visibleAuthority);
+
+ void holdLock(in int durationMs);
}
diff --git a/core/java/android/content/pm/IncrementalStatesInfo.aidl b/core/java/android/content/pm/IncrementalStatesInfo.aidl
new file mode 100644
index 000000000000..7064fbfa8b6a
--- /dev/null
+++ b/core/java/android/content/pm/IncrementalStatesInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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.pm;
+
+parcelable IncrementalStatesInfo;
diff --git a/core/java/android/content/pm/IncrementalStatesInfo.java b/core/java/android/content/pm/IncrementalStatesInfo.java
new file mode 100644
index 000000000000..6e91c19affc2
--- /dev/null
+++ b/core/java/android/content/pm/IncrementalStatesInfo.java
@@ -0,0 +1,76 @@
+/*
+ * 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.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Info about a package's states in Parcelable format.
+ * @hide
+ */
+public class IncrementalStatesInfo implements Parcelable {
+ private boolean mIsStartable;
+ private boolean mIsLoading;
+ private float mProgress;
+
+ public IncrementalStatesInfo(boolean isStartable, boolean isLoading, float progress) {
+ mIsStartable = isStartable;
+ mIsLoading = isLoading;
+ mProgress = progress;
+ }
+
+ private IncrementalStatesInfo(Parcel source) {
+ mIsStartable = source.readBoolean();
+ mIsLoading = source.readBoolean();
+ mProgress = source.readFloat();
+ }
+
+ public boolean isStartable() {
+ return mIsStartable;
+ }
+
+ public boolean isLoading() {
+ return mIsLoading;
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsStartable);
+ dest.writeBoolean(mIsLoading);
+ dest.writeFloat(mProgress);
+ }
+
+ public static final @android.annotation.NonNull Creator<IncrementalStatesInfo> CREATOR =
+ new Creator<IncrementalStatesInfo>() {
+ public IncrementalStatesInfo createFromParcel(Parcel source) {
+ return new IncrementalStatesInfo(source);
+ }
+ public IncrementalStatesInfo[] newArray(int size) {
+ return new IncrementalStatesInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 75ee9b7943fe..f03425b9e117 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1222,12 +1222,17 @@ public class PackageInstaller {
* Adds installer-provided checksums for the APK file in session.
*
* @param name previously written as part of this session.
+ * {@link #openWrite}
* @param checksums installer intends to make available via
- * {@link PackageManager#getChecksums(String, boolean, int, List,
- * IntentSender)}.
+ * {@link PackageManager#requestChecksums}.
* @throws SecurityException if called after the session has been
* committed or abandoned.
+ * @deprecated do not use installer-provided checksums,
+ * use platform-enforced checksums
+ * e.g. {@link Checksum#TYPE_WHOLE_MERKLE_ROOT_4K_SHA256}
+ * in {@link PackageManager#requestChecksums}.
*/
+ @Deprecated
public void addChecksums(@NonNull String name, @NonNull List<Checksum> checksums)
throws IOException {
Objects.requireNonNull(name);
@@ -1875,6 +1880,7 @@ public class PackageInstaller {
/** {@hide} */
@SystemApi
+ @TestApi
public void setInstallAsInstantApp(boolean isInstantApp) {
if (isInstantApp) {
installFlags |= PackageManager.INSTALL_INSTANT_APP;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index bbb9ab4a5b10..32ae105a0ff2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -79,7 +79,6 @@ import com.android.internal.util.ArrayUtils;
import dalvik.system.VMRuntime;
import java.io.File;
-import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.cert.Certificate;
@@ -866,6 +865,12 @@ public abstract class PackageManager {
* is set the restricted permissions will be whitelisted for all users, otherwise
* only to the owner.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @hide
*/
public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
@@ -3303,7 +3308,7 @@ public abstract class PackageManager {
/**
* Extra field name for the ID of a package pending verification. Passed to
* a package verifier and is used to call back to
- * @see #getChecksums
+ * @see #requestChecksums
*/
public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
@@ -3505,6 +3510,17 @@ public abstract class PackageManager {
public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17;
/**
+ * Permission flag: The permission is restricted but the app is exempt
+ * from the restriction and is allowed to hold this permission in its
+ * full form and the exemption is provided by the held roles.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18;
+
+ /**
* Permission flags: Reserved for use by the permission controller. The platform and any
* packages besides the permission controller should not assume any definition about these
* flags.
@@ -3522,7 +3538,8 @@ public abstract class PackageManager {
public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT
| FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+ | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
+ | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
/**
* Mask for all permission flags.
@@ -3568,13 +3585,27 @@ public abstract class PackageManager {
/**
* Permission whitelist flag: permissions whitelisted by the system.
- * Permissions can also be whitelisted by the installer or on upgrade.
+ * Permissions can also be whitelisted by the installer, on upgrade, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1 << 0;
/**
* Permission whitelist flag: permissions whitelisted by the installer.
- * Permissions can also be whitelisted by the system or on upgrade.
+ * Permissions can also be whitelisted by the system, on upgrade, or on role
+ * grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 1 << 1;
@@ -3582,15 +3613,31 @@ public abstract class PackageManager {
* Permission whitelist flag: permissions whitelisted by the system
* when upgrading from an OS version where the permission was not
* restricted to an OS version where the permission is restricted.
- * Permissions can also be whitelisted by the installer or the system.
+ * Permissions can also be whitelisted by the installer, the system, or on
+ * role grant.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
*/
public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2;
+ /**
+ * Permission allowlist flag: permissions exempted by the system
+ * when being granted a role.
+ * Permissions can also be exempted by the installer, the system, or on
+ * upgrade.
+ */
+ public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3;
+
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = {
FLAG_PERMISSION_WHITELIST_SYSTEM,
FLAG_PERMISSION_WHITELIST_INSTALLER,
- FLAG_PERMISSION_WHITELIST_UPGRADE
+ FLAG_PERMISSION_WHITELIST_UPGRADE,
+ FLAG_PERMISSION_ALLOWLIST_ROLE
})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionWhitelistFlags {}
@@ -3738,6 +3785,36 @@ public abstract class PackageManager {
*/
public static final int SYSTEM_APP_STATE_UNINSTALLED = 3;
+ /**
+ * Reasons for why a package is unstartable.
+ * @hide
+ */
+ @IntDef({UNSTARTABLE_REASON_UNKNOWN,
+ UNSTARTABLE_REASON_CONNECTION_ERROR,
+ UNSTARTABLE_REASON_INSUFFICIENT_STORAGE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnstartableReason {}
+
+ /**
+ * Unstartable state with no root cause specified. E.g., data loader seeing missing pages but
+ * unclear about the cause. This corresponds to a generic alert window shown to the user when
+ * the user attempts to launch the app.
+ */
+ public static final int UNSTARTABLE_REASON_UNKNOWN = 0;
+
+ /**
+ * Unstartable state due to connection issues that interrupt package loading.
+ * This corresponds to an alert window shown to the user indicating connection errors.
+ */
+ public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1;
+
+ /**
+ * Unstartable state after encountering storage limitations.
+ * This corresponds to an alert window indicating limited storage.
+ */
+ public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2;
+
/** {@hide} */
public int getUserId() {
return UserHandle.myUserId();
@@ -4536,7 +4613,7 @@ public abstract class PackageManager {
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four allowlists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the{@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4553,6 +4630,17 @@ public abstract class PackageManager {
* Can be accessed by pre-installed holders of a dedicated permission or the
* installer on record.
*
+ * <li>one for cases where the system exempts the permission when granting a role.
+ * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can
+ * be accessed by pre-installed holders of a dedicated permission.
+ * </ol>
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param whitelistFlag The flag to determine which whitelist to query. Only one flag
* can be passed.s
@@ -4563,6 +4651,7 @@ public abstract class PackageManager {
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to access a whitelist that you have no access to.
*/
@@ -4584,7 +4673,7 @@ public abstract class PackageManager {
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four whitelists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4602,10 +4691,21 @@ public abstract class PackageManager {
* Can be modified by pre-installed holders of a dedicated permission or the installer
* on record.
*
+ * <li>one for cases where the system exempts the permission when permission when
+ * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE}
+ * flag. Can be modified by pre-installed holders of a dedicated permission.
+ * </ol>
+ *
* <p>You need to specify the whitelists for which to set the whitelisted permissions
* which will clear the previous whitelisted permissions and replace them with the
* provided ones.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param permName The whitelisted permission to add.
* @param whitelistFlags The whitelists to which to add. Passing multiple flags
@@ -4617,6 +4717,7 @@ public abstract class PackageManager {
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
@@ -4638,7 +4739,7 @@ public abstract class PackageManager {
* allows for the to hold that permission and whitelisting a soft restricted
* permission allows the app to hold the permission in its full, unrestricted form.
*
- * <p><ol>There are three whitelists:
+ * <p><ol>There are four whitelists:
*
* <li>one for cases where the system permission policy whitelists a permission
* This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_SYSTEM} flag.
@@ -4656,10 +4757,24 @@ public abstract class PackageManager {
* Can be modified by pre-installed holders of a dedicated permission or the installer
* on record.
*
+ * <li>one for cases where the system exempts the permission when upgrading
+ * from an OS version in which the permission was not restricted to an OS version
+ * in which the permission is restricted. This list corresponds to the {@link
+ * #FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be modified by pre-installed
+ * holders of a dedicated permission. The installer on record can only remove
+ * permissions from this allowlist.
+ * </ol>
+ *
* <p>You need to specify the whitelists for which to set the whitelisted permissions
* which will clear the previous whitelisted permissions and replace them with the
* provided ones.
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to get whitelisted permissions.
* @param permName The whitelisted permission to remove.
* @param whitelistFlags The whitelists from which to remove. Passing multiple flags
@@ -4671,6 +4786,7 @@ public abstract class PackageManager {
* @see #FLAG_PERMISSION_WHITELIST_SYSTEM
* @see #FLAG_PERMISSION_WHITELIST_UPGRADE
* @see #FLAG_PERMISSION_WHITELIST_INSTALLER
+ * @see #FLAG_PERMISSION_ALLOWLIST_ROLE
*
* @throws SecurityException if you try to modify a whitelist that you have no access to.
*/
@@ -4691,6 +4807,12 @@ public abstract class PackageManager {
* un-whitelist the packages it installs, unless auto-revoking permissions from that package
* would cause breakages beyond having to re-request the permission(s).
*
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to set exemption.
* @param whitelisted Whether the app should be whitelisted.
*
@@ -4712,6 +4834,13 @@ public abstract class PackageManager {
*
* Only the installer on record that installed the given package, or a holder of
* {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this.
+ *
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @param packageName The app for which to set exemption.
*
* @return Whether the app is whitelisted.
@@ -7854,42 +7983,49 @@ public abstract class PackageManager {
/**
* Trust any Installer to provide checksums for the package.
- * @see #getChecksums
+ * @see #requestChecksums
*/
- public static final @Nullable List<Certificate> TRUST_ALL = null;
+ public static final @Nullable List<Certificate> TRUST_ALL = Collections.singletonList(null);
/**
* Don't trust any Installer to provide checksums for the package.
* This effectively disables optimized Installer-enforced checksums.
- * @see #getChecksums
+ * @see #requestChecksums
*/
- public static final @NonNull List<Certificate> TRUST_NONE = Collections.emptyList();
+ public static final @NonNull List<Certificate> TRUST_NONE = Collections.singletonList(null);
/**
- * Returns the checksums for APKs within a package.
+ * Requesting the checksums for APKs within a package.
+ * The checksums will be returned asynchronously via statusReceiver.
*
* By default returns all readily available checksums:
* - enforced by platform,
* - enforced by installer.
* If caller needs a specific checksum kind, they can specify it as required.
*
+ * <b>Caution: Android can not verify installer-provided checksums. Make sure you specify
+ * trusted installers.</b>
+ *
* @param packageName whose checksums to return.
* @param includeSplits whether to include checksums for non-base splits.
- * @param required explicitly request the checksum kinds. Will incur significant
+ * @param required explicitly request the checksum types. May incur significant
* CPU/memory/disk usage.
- * @param trustedInstallers for checksums enforced by Installer, which ones to be trusted.
- * {@link #TRUST_ALL} will return checksums from any Installer,
- * {@link #TRUST_NONE} disables optimized Installer-enforced checksums.
+ * @param trustedInstallers for checksums enforced by installer, which installers are to be
+ * trusted.
+ * {@link #TRUST_ALL} will return checksums from any installer,
+ * {@link #TRUST_NONE} disables optimized installer-enforced checksums,
+ * otherwise the list has to be non-empty list of certificates.
* @param statusReceiver called once when the results are available as
- * {@link #EXTRA_CHECKSUMS} of type ApkChecksum[].
+ * {@link #EXTRA_CHECKSUMS} of type {@link ApkChecksum}[].
* @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
+ * @throws IllegalArgumentException if the list of trusted installer certificates is empty.
* @throws NameNotFoundException if a package with the given name cannot be found on the system.
*/
- public void getChecksums(@NonNull String packageName, boolean includeSplits,
- @Checksum.Kind int required, @Nullable List<Certificate> trustedInstallers,
+ public void requestChecksums(@NonNull String packageName, boolean includeSplits,
+ @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers,
@NonNull IntentSender statusReceiver)
- throws CertificateEncodingException, IOException, NameNotFoundException {
- throw new UnsupportedOperationException("getChecksums not implemented in subclass");
+ throws CertificateEncodingException, NameNotFoundException {
+ throw new UnsupportedOperationException("requestChecksums not implemented in subclass");
}
/**
@@ -8023,6 +8159,12 @@ public abstract class PackageManager {
}
/**
+ * <p>
+ * <strong>Note: </strong>In retrospect it would have been preferred to use
+ * more inclusive terminology when naming this API. Similar APIs added will
+ * refrain from using the term "whitelist".
+ * </p>
+ *
* @return whether this package is whitelisted from having its runtime permission be
* auto-revoked if unused for an extended period of time.
*/
@@ -8082,6 +8224,20 @@ public abstract class PackageManager {
"getMimeGroup not implemented in subclass");
}
+ /**
+ * Grants implicit visibility of the package that provides an authority to a querying UID.
+ *
+ * @throws SecurityException when called by a package other than the contacts provider
+ * @hide
+ */
+ public void grantImplicitAccess(int queryingUid, String visibleAuthority) {
+ try {
+ ActivityThread.getPackageManager().grantImplicitAccess(queryingUid, visibleAuthority);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// Some of the flags don't affect the query result, but let's be conservative and cache
// each combination of flags separately.
@@ -8288,4 +8444,19 @@ public abstract class PackageManager {
public static void uncorkPackageInfoCache() {
PropertyInvalidatedCache.uncorkInvalidations(PermissionManager.CACHE_KEY_PACKAGE_INFO);
}
+
+ /**
+ * Holds the PM lock for the specified amount of milliseconds.
+ * Intended for use by the tests that need to imitate lock contention.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
+ public void holdLock(int durationMs) {
+ try {
+ ActivityThread.getPackageManager().holdLock(durationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 327d1b8beeb1..3ed21b087972 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -44,7 +44,6 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 04e15c20b2f4..5d4c843d2eab 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -377,6 +377,14 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4;
/**
+ * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ *
+ * <p> Modifier for permission restriction. This permission cannot be exempted by the installer.
+ */
+ public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5;
+
+ /**
* Flag for {@link #flags}, indicating that this permission has been
* installed into the system's globally defined permissions.
*/
@@ -656,6 +664,11 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
}
/** @hide */
+ public boolean isInstallerExemptIgnored() {
+ return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+ }
+
+ /** @hide */
public boolean isAppOp() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
}
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 1c98a1ffcbb6..953400e10e15 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -17,6 +17,20 @@
},
{
"name": "CarrierAppIntegrationTestCases"
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.pm.cts"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 08b23b04f2ae..d81dff8f2908 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -366,8 +366,9 @@ public class UserInfo implements Parcelable {
* @return true if this user can be switched to.
**/
public boolean supportsSwitchTo() {
- if (isEphemeral() && !isEnabled()) {
- // Don't support switching to an ephemeral user with removal in progress.
+ if (partial || !isEnabled()) {
+ // Don't support switching to disabled or partial users, which includes users with
+ // removal in progress.
return false;
}
if (preCreated) {
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 9197020e1fce..b936c6323a80 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -1080,14 +1080,57 @@ public class ParsingPackageUtils {
}
}
- final String requiredFeature = sa.getNonConfigurationString(
- R.styleable.AndroidManifestUsesPermission_requiredFeature, 0);
+ final ArraySet<String> requiredFeatures = new ArraySet<>();
+ String feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_requiredFeature,
+ 0);
+ if (feature != null) {
+ requiredFeatures.add(feature);
+ }
- final String requiredNotfeature = sa.getNonConfigurationString(
- R.styleable.AndroidManifestUsesPermission_requiredNotFeature,
+ final ArraySet<String> requiredNotFeatures = new ArraySet<>();
+ feature = sa.getNonConfigurationString(
+ com.android.internal.R.styleable
+ .AndroidManifestUsesPermission_requiredNotFeature,
0);
+ if (feature != null) {
+ requiredNotFeatures.add(feature);
+ }
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final ParseResult<?> result;
+ switch (parser.getName()) {
+ case "required-feature":
+ result = parseRequiredFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredFeatures.add((String) result.getResult());
+ }
+ break;
+
+ case "required-not-feature":
+ result = parseRequiredNotFeature(input, res, parser);
+ if (result.isSuccess()) {
+ requiredNotFeatures.add((String) result.getResult());
+ }
+ break;
- XmlUtils.skipCurrentTag(parser);
+ default:
+ result = ParsingUtils.unknownTag("<uses-permission>", pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
// Can only succeed from here on out
ParseResult<ParsingPackage> success = input.success(pkg);
@@ -1100,17 +1143,22 @@ public class ParsingPackageUtils {
return success;
}
- // Only allow requesting this permission if the platform supports the given feature.
- if (requiredFeature != null && mCallback != null && !mCallback.hasFeature(
- requiredFeature)) {
- return success;
- }
+ if (mCallback != null) {
+ // Only allow requesting this permission if the platform supports all of the
+ // "required-feature"s.
+ for (int i = requiredFeatures.size() - 1; i >= 0; i--) {
+ if (!mCallback.hasFeature(requiredFeatures.valueAt(i))) {
+ return success;
+ }
+ }
- // Only allow requesting this permission if the platform doesn't support the given
- // feature.
- if (requiredNotfeature != null && mCallback != null
- && mCallback.hasFeature(requiredNotfeature)) {
- return success;
+ // Only allow requesting this permission if the platform does not supports any of
+ // the "required-not-feature"s.
+ for (int i = requiredNotFeatures.size() - 1; i >= 0; i--) {
+ if (mCallback.hasFeature(requiredNotFeatures.valueAt(i))) {
+ return success;
+ }
+ }
}
if (!pkg.getRequestedPermissions().contains(name)) {
@@ -1127,6 +1175,36 @@ public class ParsingPackageUtils {
}
}
+ private ParseResult<String> parseRequiredFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from <required-feature> tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ private ParseResult<String> parseRequiredNotFeature(ParseInput input, Resources res,
+ AttributeSet attrs) {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestRequiredNotFeature);
+ try {
+ final String featureName = sa.getString(
+ R.styleable.AndroidManifestRequiredNotFeature_name);
+ return TextUtils.isEmpty(featureName)
+ ? input.error("Feature name is missing from <required-not-feature> tag.")
+ : input.success(featureName);
+ } finally {
+ sa.recycle();
+ }
+ }
+
private static ParseResult<ParsingPackage> parseUsesConfiguration(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser) {
ConfigurationInfo cPref = new ConfigurationInfo();
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 6407f9b3c1b8..49f81f83c39a 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -19,6 +19,7 @@ package android.content.res;
import static android.content.ConfigurationProto.COLOR_MODE;
import static android.content.ConfigurationProto.DENSITY_DPI;
import static android.content.ConfigurationProto.FONT_SCALE;
+import static android.content.ConfigurationProto.FORCE_BOLD_TEXT;
import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN;
import static android.content.ConfigurationProto.KEYBOARD;
import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
@@ -332,6 +333,26 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int screenLayout;
/**
+ * An undefined forceBoldText.
+ */
+ public static final int FORCE_BOLD_TEXT_UNDEFINED = 0;
+
+ /**
+ * Text is not bold.
+ */
+ public static final int FORCE_BOLD_TEXT_NO = 1;
+
+ /**
+ * Text is bold.
+ */
+ public static final int FORCE_BOLD_TEXT_YES = 2;
+
+ /**
+ * Current user preference for displaying all text in bold.
+ */
+ public int forceBoldText;
+
+ /**
* Configuration relating to the windowing state of the object associated with this
* Configuration. Contents of this field are not intended to affect resources, but need to be
* communicated and propagated at the same time as the rest of Configuration.
@@ -467,6 +488,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if ((diff & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) {
list.add("CONFIG_WINDOW_CONFIGURATION");
}
+ if ((diff & ActivityInfo.CONFIG_FORCE_BOLD_TEXT) != 0) {
+ list.add("CONFIG_AUTO_BOLD_TEXT");
+ }
StringBuilder builder = new StringBuilder("{");
for (int i = 0, n = list.size(); i < n; i++) {
builder.append(list.get(i));
@@ -957,6 +981,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
assetsSeq = o.assetsSeq;
seq = o.seq;
windowConfiguration.setTo(o.windowConfiguration);
+ forceBoldText = o.forceBoldText;
}
public String toString() {
@@ -1112,6 +1137,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (seq != 0) {
sb.append(" s.").append(seq);
}
+ if (forceBoldText != FORCE_BOLD_TEXT_UNDEFINED) {
+ sb.append(" boldText=");
+ sb.append(forceBoldText);
+ } else {
+ sb.append(" ?boldText");
+ }
sb.append('}');
return sb.toString();
}
@@ -1152,6 +1183,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (!persisted && windowConfiguration != null) {
windowConfiguration.dumpDebug(protoOutputStream, WINDOW_CONFIGURATION);
}
+ protoOutputStream.write(FORCE_BOLD_TEXT, forceBoldText);
}
protoOutputStream.write(ORIENTATION, orientation);
protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
@@ -1315,6 +1347,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
Slog.e(TAG, "error parsing locale list in configuration.", e);
}
break;
+ case (int) FORCE_BOLD_TEXT:
+ forceBoldText = protoInputStream.readInt(FORCE_BOLD_TEXT);
+ break;
}
}
} finally {
@@ -1410,6 +1445,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
assetsSeq = ASSETS_SEQ_UNDEFINED;
seq = 0;
windowConfiguration.setToDefaults();
+ forceBoldText = FORCE_BOLD_TEXT_UNDEFINED;
}
/**
@@ -1607,6 +1643,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
}
+ if (delta.forceBoldText != FORCE_BOLD_TEXT_UNDEFINED
+ && delta.forceBoldText != forceBoldText) {
+ changed |= ActivityInfo.CONFIG_FORCE_BOLD_TEXT;
+ forceBoldText = delta.forceBoldText;
+ }
+
return changed;
}
@@ -1685,6 +1727,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if ((mask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0) {
windowConfiguration.setTo(delta.windowConfiguration, windowMask);
}
+ if ((mask & ActivityInfo.CONFIG_FORCE_BOLD_TEXT) != 0) {
+ forceBoldText = delta.forceBoldText;
+ }
}
/**
@@ -1717,6 +1762,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* PackageManager.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE}.
* {@link android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION
* PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
+ * {@link android.content.pm.ActivityInfo#CONFIG_FORCE_BOLD_TEXT
+ * PackageManager.ActivityInfo.CONFIG_FORCE_BOLD_TEXT.
*/
public int diff(Configuration delta) {
return diff(delta, false /* compareUndefined */, false /* publicOnly */);
@@ -1840,6 +1887,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
}
+ if ((compareUndefined || delta.forceBoldText != FORCE_BOLD_TEXT_UNDEFINED)
+ && forceBoldText != delta.forceBoldText) {
+ changed |= ActivityInfo.CONFIG_FORCE_BOLD_TEXT;
+ }
return changed;
}
@@ -1933,6 +1984,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeValue(windowConfiguration);
dest.writeInt(assetsSeq);
dest.writeInt(seq);
+ dest.writeInt(forceBoldText);
}
public void readFromParcel(Parcel source) {
@@ -1964,6 +2016,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
windowConfiguration.setTo((WindowConfiguration) source.readValue(null));
assetsSeq = source.readInt();
seq = source.readInt();
+ forceBoldText = source.readInt();
}
public static final @android.annotation.NonNull Parcelable.Creator<Configuration> CREATOR
@@ -2062,6 +2115,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (n != 0) return n;
n = windowConfiguration.compareTo(that.windowConfiguration);
if (n != 0) return n;
+ n = this.forceBoldText - that.forceBoldText;
+ if (n != 0) return n;
// if (n != 0) return n;
return n;
@@ -2102,6 +2157,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + smallestScreenWidthDp;
result = 31 * result + densityDpi;
result = 31 * result + assetsSeq;
+ result = 31 * result + forceBoldText;
return result;
}
@@ -2674,6 +2730,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (!base.windowConfiguration.equals(change.windowConfiguration)) {
delta.windowConfiguration.setTo(change.windowConfiguration);
}
+
+ if (base.forceBoldText != change.forceBoldText) {
+ delta.forceBoldText = change.forceBoldText;
+ }
return delta;
}
@@ -2697,6 +2757,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
private static final String XML_ATTR_SMALLEST_WIDTH = "sw";
private static final String XML_ATTR_DENSITY = "density";
private static final String XML_ATTR_APP_BOUNDS = "app_bounds";
+ private static final String XML_ATTR_FORCE_BOLD_TEXT = "forceBoldText";
/**
* Reads the attributes corresponding to Configuration member fields from the Xml parser.
@@ -2746,6 +2807,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
configOut.densityDpi = XmlUtils.readIntAttribute(parser, XML_ATTR_DENSITY,
DENSITY_DPI_UNDEFINED);
+ configOut.forceBoldText = XmlUtils.readIntAttribute(parser, XML_ATTR_FORCE_BOLD_TEXT,
+ FORCE_BOLD_TEXT_UNDEFINED);
// For persistence, we don't care about assetsSeq and WindowConfiguration, so do not read it
// out.
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index 9ebc9969a730..c02af59ab72e 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -2,6 +2,20 @@
"presubmit": [
{
"name": "CtsResourcesLoaderTests"
+ },
+ {
+ "name": "CtsContentTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.content.res.cts"
+ }
+ ]
}
]
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index cc4c45699bd4..a6e8c1395701 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -1245,6 +1245,7 @@ public final class Sensor {
return true;
case TYPE_HINGE_ANGLE:
mStringType = STRING_TYPE_HINGE_ANGLE;
+ return true;
default:
return false;
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index f86eb9082e01..25c749b9c14b 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -16,14 +16,17 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.RemoteException;
import android.security.keystore.KeyGenParameterSpec;
@@ -32,6 +35,8 @@ import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}.
@@ -103,6 +108,7 @@ public class BiometricManager {
@IntDef(flag = true, value = {
BIOMETRIC_STRONG,
BIOMETRIC_WEAK,
+ BIOMETRIC_CONVENIENCE,
DEVICE_CREDENTIAL,
})
@interface Types {}
@@ -195,6 +201,28 @@ public class BiometricManager {
}
/**
+ * @return A list of {@link SensorProperties}
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(TEST_BIOMETRIC)
+ public List<SensorProperties> getSensorProperties() {
+ return new ArrayList<>(); // TODO(169459906)
+ }
+
+ /**
+ * Retrieves a test session for BiometricManager/BiometricPrompt.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(TEST_BIOMETRIC)
+ public BiometricTestSession createTestSession(int sensorId) {
+ return null; // TODO(169459906)
+ }
+
+ /**
* Determine if biometrics can be used. In other words, determine if
* {@link BiometricPrompt} can be expected to be shown (hardware available, templates enrolled,
* user-enabled). This is the equivalent of {@link #canAuthenticate(int)} with
diff --git a/core/java/android/hardware/biometrics/BiometricTestSession.java b/core/java/android/hardware/biometrics/BiometricTestSession.java
new file mode 100644
index 000000000000..60ed404fecc3
--- /dev/null
+++ b/core/java/android/hardware/biometrics/BiometricTestSession.java
@@ -0,0 +1,181 @@
+/*
+ * 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.hardware.biometrics;
+
+import static android.Manifest.permission.TEST_BIOMETRIC;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.ArraySet;
+
+/**
+ * Common set of interfaces to test biometric-related APIs, including {@link BiometricPrompt} and
+ * {@link android.hardware.fingerprint.FingerprintManager}.
+ * @hide
+ */
+@TestApi
+public class BiometricTestSession implements AutoCloseable {
+ private final Context mContext;
+ private final ITestSession mTestSession;
+
+ // Keep track of users that were tested, which need to be cleaned up when finishing.
+ private final ArraySet<Integer> mTestedUsers;
+
+ /**
+ * @hide
+ */
+ public BiometricTestSession(@NonNull Context context, @NonNull ITestSession testSession) {
+ mContext = context;
+ mTestSession = testSession;
+ mTestedUsers = new ArraySet<>();
+ enableTestHal(true);
+ }
+
+ /**
+ * Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke
+ * any methods on the real HAL implementation. This allows the framework to test a substantial
+ * portion of the framework code that would otherwise require human interaction. Note that
+ * secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
+ * equivalent for the secret key.
+ *
+ * @param enableTestHal If true, enable testing with a fake HAL instead of the real HAL.
+ * @hide
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ private void enableTestHal(boolean enableTestHal) {
+ try {
+ mTestSession.enableTestHal(enableTestHal);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts the enrollment process. This should generally be used when the test HAL is enabled.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void startEnroll(int userId) {
+ try {
+ mTestedUsers.add(userId);
+ mTestSession.startEnroll(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Finishes the enrollment process. Simulates the HAL's callback.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void finishEnroll(int userId) {
+ try {
+ mTestedUsers.add(userId);
+ mTestSession.finishEnroll(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulates a successful authentication, but does not provide a valid HAT.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void acceptAuthentication(int userId) {
+ try {
+ mTestSession.acceptAuthentication(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulates a rejected attempt.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void rejectAuthentication(int userId) {
+ try {
+ mTestSession.rejectAuthentication(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulates an acquired message from the HAL.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void notifyAcquired(int userId) {
+ try {
+ mTestSession.notifyAcquired(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulates an error message from the HAL.
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void notifyError(int userId) {
+ try {
+ mTestSession.notifyError(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment
+ * that isn't known by both sides are deleted. This should generally be used when the test
+ * HAL is disabled (e.g. to clean up after a test).
+ *
+ * @param userId User that this command applies to.
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void cleanupInternalState(int userId) {
+ try {
+ mTestSession.cleanupInternalState(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ @RequiresPermission(TEST_BIOMETRIC)
+ public void close() {
+ for (int user : mTestedUsers) {
+ cleanupInternalState(user);
+ }
+
+ enableTestHal(false);
+ }
+}
diff --git a/core/java/android/hardware/biometrics/ITestSession.aidl b/core/java/android/hardware/biometrics/ITestSession.aidl
new file mode 100644
index 000000000000..5677f6517ae5
--- /dev/null
+++ b/core/java/android/hardware/biometrics/ITestSession.aidl
@@ -0,0 +1,54 @@
+/*
+ * 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.hardware.biometrics;
+
+import android.hardware.biometrics.SensorPropertiesInternal;
+
+/**
+ * A test service for FingerprintManager and BiometricPrompt.
+ * @hide
+ */
+interface ITestSession {
+ // Switches the specified sensor to use a test HAL. In this mode, the framework will not invoke
+ // any methods on the real HAL implementation. This allows the framework to test a substantial
+ // portion of the framework code that would otherwise require human interaction. Note that
+ // secure pathways such as HAT/Keystore are not testable, since they depend on the TEE or its
+ // equivalent for the secret key.
+ void enableTestHal(boolean enableTestHal);
+
+ // Starts the enrollment process. This should generally be used when the test HAL is enabled.
+ void startEnroll(int userId);
+
+ // Finishes the enrollment process. Simulates the HAL's callback.
+ void finishEnroll(int userId);
+
+ // Simulates a successful authentication, but does not provide a valid HAT.
+ void acceptAuthentication(int userId);
+
+ // Simulates a rejected attempt.
+ void rejectAuthentication(int userId);
+
+ // Simulates an acquired message from the HAL.
+ void notifyAcquired(int userId);
+
+ // Simulates an error message from the HAL.
+ void notifyError(int userId);
+
+ // Matches the framework's cached enrollments against the HAL's enrollments. Any enrollment
+ // that isn't known by both sides are deleted. This should generally be used when the test
+ // HAL is disabled (e.g. to clean up after a test).
+ void cleanupInternalState(int userId);
+}
diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java
new file mode 100644
index 000000000000..5b1b5e612c73
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorProperties.java
@@ -0,0 +1,84 @@
+/*
+ * 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.hardware.biometrics;
+
+import android.annotation.IntDef;
+import android.annotation.TestApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The base class containing all modality-agnostic information.
+ * @hide
+ */
+@TestApi
+public class SensorProperties {
+ /**
+ * A sensor that meets the requirements for Class 1 biometrics as defined in the CDD. This does
+ * not correspond to a public BiometricManager.Authenticators constant. Sensors of this strength
+ * are not available to applications via the public API surface.
+ */
+ public static final int STRENGTH_CONVENIENCE = 0;
+
+ /**
+ * A sensor that meets the requirements for Class 2 biometrics as defined in the CDD.
+ * Corresponds to BiometricManager.Authenticators.BIOMETRIC_WEAK.
+ */
+ public static final int STRENGTH_WEAK = 1;
+
+ /**
+ * A sensor that meets the requirements for Class 3 biometrics as defined in the CDD.
+ * Corresponds to BiometricManager.Authenticators.BIOMETRIC_STRONG.
+ *
+ * Notably, this is the only strength that allows generation of HardwareAuthToken(s).
+ */
+ public static final int STRENGTH_STRONG = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef({STRENGTH_CONVENIENCE, STRENGTH_WEAK, STRENGTH_STRONG})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Strength {}
+
+ private final int mSensorId;
+ @Strength private final int mSensorStrength;
+
+ /**
+ * @hide
+ */
+ public SensorProperties(int sensorId, @Strength int sensorStrength) {
+ mSensorId = sensorId;
+ mSensorStrength = sensorStrength;
+ }
+
+ /**
+ * @return The sensor's unique identifier.
+ */
+ public int getSensorId() {
+ return mSensorId;
+ }
+
+ /**
+ * @return The sensor's strength.
+ */
+ @Strength
+ public int getSensorStrength() {
+ return mSensorStrength;
+ }
+}
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl b/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl
new file mode 100644
index 000000000000..d85c78821f53
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.aidl
@@ -0,0 +1,18 @@
+/*
+ * 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.hardware.biometrics;
+
+parcelable SensorPropertiesInternal; \ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
new file mode 100644
index 000000000000..2189de0827b7
--- /dev/null
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
@@ -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.
+ */
+
+package android.hardware.biometrics;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The base class containing all modality-agnostic information. This is a superset of the
+ * {@link android.hardware.biometrics.common.CommonProps}, and provides backwards-compatible
+ * behavior with the older generation of HIDL (non-AIDL) interfaces.
+ * @hide
+ */
+public class SensorPropertiesInternal implements Parcelable {
+
+ public final int sensorId;
+ @SensorProperties.Strength public final int sensorStrength;
+ public final int maxEnrollmentsPerUser;
+
+ protected SensorPropertiesInternal(int sensorId, @SensorProperties.Strength int sensorStrength,
+ int maxEnrollmentsPerUser) {
+ this.sensorId = sensorId;
+ this.sensorStrength = sensorStrength;
+ this.maxEnrollmentsPerUser = maxEnrollmentsPerUser;
+ }
+
+ protected SensorPropertiesInternal(Parcel in) {
+ sensorId = in.readInt();
+ sensorStrength = in.readInt();
+ maxEnrollmentsPerUser = in.readInt();
+ }
+
+ public static final Creator<SensorPropertiesInternal> CREATOR =
+ new Creator<SensorPropertiesInternal>() {
+ @Override
+ public SensorPropertiesInternal createFromParcel(Parcel in) {
+ return new SensorPropertiesInternal(in);
+ }
+
+ @Override
+ public SensorPropertiesInternal[] newArray(int size) {
+ return new SensorPropertiesInternal[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sensorId);
+ dest.writeInt(sensorStrength);
+ dest.writeInt(maxEnrollmentsPerUser);
+ }
+}
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 1061121b4449..0d0bfb32e293 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2175,10 +2175,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>The desired zoom ratio</p>
- * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the
- * application can now choose to use this tag to specify the desired zoom level. The
- * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical
- * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} for zoom, the application can now choose to
+ * use this tag to specify the desired zoom level.</p>
* <p>By using this control, the application gains a simpler way to control zoom, which can
* be a combination of optical and digital zoom. For example, a multi-camera system may
* contain more than one lens with different focal lengths, and the user can use optical
@@ -2860,11 +2858,18 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* respectively.</p>
* <p>The camera device may adjust the crop region to account for rounding and other hardware
* requirements; the final crop region used will be included in the output capture result.</p>
+ * <p>The camera sensor output aspect ratio depends on factors such as output stream
+ * combination and {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}, and shouldn't be adjusted by using
+ * this control. And the camera device will treat different camera sensor output sizes
+ * (potentially with in-sensor crop) as the same crop of
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}. As a result, the application shouldn't assume the
+ * maximum crop region always maps to the same aspect ratio or field of view for the
+ * sensor output.</p>
* <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
* to take advantage of better support for zoom with logical multi-camera. The benefits
* include better precision with optical-digital zoom combination, and ability to do
* zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in
- * the capture request must be either letterboxing or pillarboxing (but not both). The
+ * the capture request should be left as the default activeArray size. The
* coordinate system is post-zoom, meaning that the activeArraySize or
* preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
@@ -2874,6 +2879,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* capability and mode</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 29a53fb6d4a2..8cfa0866f13a 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2405,10 +2405,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>The desired zoom ratio</p>
- * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} with dual purposes of crop and zoom, the
- * application can now choose to use this tag to specify the desired zoom level. The
- * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical
- * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>Instead of using {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} for zoom, the application can now choose to
+ * use this tag to specify the desired zoom level.</p>
* <p>By using this control, the application gains a simpler way to control zoom, which can
* be a combination of optical and digital zoom. For example, a multi-camera system may
* contain more than one lens with different focal lengths, and the user can use optical
@@ -3506,11 +3504,18 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* respectively.</p>
* <p>The camera device may adjust the crop region to account for rounding and other hardware
* requirements; the final crop region used will be included in the output capture result.</p>
+ * <p>The camera sensor output aspect ratio depends on factors such as output stream
+ * combination and {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}, and shouldn't be adjusted by using
+ * this control. And the camera device will treat different camera sensor output sizes
+ * (potentially with in-sensor crop) as the same crop of
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}. As a result, the application shouldn't assume the
+ * maximum crop region always maps to the same aspect ratio or field of view for the
+ * sensor output.</p>
* <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
* to take advantage of better support for zoom with logical multi-camera. The benefits
* include better precision with optical-digital zoom combination, and ability to do
* zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in
- * the capture request must be either letterboxing or pillarboxing (but not both). The
+ * the capture request should be left as the default activeArray size. The
* coordinate system is post-zoom, meaning that the activeArraySize or
* preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
* {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
@@ -3520,6 +3525,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* capability and mode</p>
* <p>This key is available on all devices.</p>
*
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index fd1ef6c9e7ce..8f7e3a76153c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1629,7 +1629,7 @@ public class CameraDeviceImpl extends CameraDevice
}
switch (errorCode) {
- case CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
+ case CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED: {
final long ident = Binder.clearCallingIdentity();
try {
mDeviceExecutor.execute(mCallOnDisconnected);
@@ -1637,6 +1637,7 @@ public class CameraDeviceImpl extends CameraDevice
Binder.restoreCallingIdentity(ident);
}
break;
+ }
case CameraDeviceCallbacks.ERROR_CAMERA_REQUEST:
case CameraDeviceCallbacks.ERROR_CAMERA_RESULT:
case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 986e6eafac2a..9d4ab0b08e92 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -72,6 +72,7 @@ import android.util.Range;
import android.util.Size;
import dalvik.annotation.optimization.FastNative;
+import dalvik.system.VMRuntime;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -351,6 +352,7 @@ public class CameraMetadataNative implements Parcelable {
if (mMetadataPtr == 0) {
throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
}
+ updateNativeAllocation();
}
/**
@@ -362,6 +364,7 @@ public class CameraMetadataNative implements Parcelable {
if (mMetadataPtr == 0) {
throw new OutOfMemoryError("Failed to allocate native CameraMetadata");
}
+ updateNativeAllocation();
}
/**
@@ -443,6 +446,7 @@ public class CameraMetadataNative implements Parcelable {
public void readFromParcel(Parcel in) {
nativeReadFromParcel(in, mMetadataPtr);
+ updateNativeAllocation();
}
/**
@@ -533,6 +537,11 @@ public class CameraMetadataNative implements Parcelable {
// Delete native pointer, but does not clear it
nativeClose(mMetadataPtr);
mMetadataPtr = 0;
+
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+ }
+ mBufferSize = 0;
}
private <T> T getBase(CameraCharacteristics.Key<T> key) {
@@ -1645,9 +1654,26 @@ public class CameraMetadataNative implements Parcelable {
return true;
}
+ private void updateNativeAllocation() {
+ long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
+
+ if (currentBufferSize != mBufferSize) {
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeFree(mBufferSize);
+ }
+
+ mBufferSize = currentBufferSize;
+
+ if (mBufferSize > 0) {
+ VMRuntime.getRuntime().registerNativeAllocation(mBufferSize);
+ }
+ }
+ }
+
private int mCameraId = -1;
private boolean mHasMandatoryConcurrentStreams = false;
private Size mDisplaySize = new Size(0, 0);
+ private long mBufferSize = 0;
/**
* Set the current camera Id.
@@ -1705,6 +1731,8 @@ public class CameraMetadataNative implements Parcelable {
private static synchronized native boolean nativeIsEmpty(long ptr);
@FastNative
private static synchronized native int nativeGetEntryCount(long ptr);
+ @FastNative
+ private static synchronized native long nativeGetBufferSize(long ptr);
@UnsupportedAppUsage
@FastNative
@@ -1744,6 +1772,8 @@ public class CameraMetadataNative implements Parcelable {
mCameraId = other.mCameraId;
mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
mDisplaySize = other.mDisplaySize;
+ updateNativeAllocation();
+ other.updateNativeAllocation();
}
/**
diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
index 413caf5e22e0..1d8b2a123c6a 100644
--- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java
@@ -147,7 +147,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
case CameraDeviceCallbacks.ERROR_CAMERA_BUFFER:
onCaptureErrorLocked(errorCode, resultExtras);
break;
- default:
+ default: {
Runnable errorDispatch = new Runnable() {
@Override
public void run() {
@@ -164,6 +164,7 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession
} finally {
Binder.restoreCallingIdentity(ident);
}
+ }
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
index 27f8a61b8999..7b6a457411f3 100644
--- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
+++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java
@@ -37,12 +37,16 @@ public class FrameNumberTracker {
/** the completed frame number for each type of capture results */
private long[] mCompletedFrameNumber = new long[CaptureRequest.REQUEST_TYPE_COUNT];
- /** the skipped frame numbers that don't belong to each type of capture results */
- private final LinkedList<Long>[] mSkippedOtherFrameNumbers =
+ /** the frame numbers that don't belong to each type of capture results and are yet to be seen
+ * through an updateTracker() call. Each list holds a list of frame numbers that should appear
+ * with request types other than that, to which the list corresponds.
+ */
+ private final LinkedList<Long>[] mPendingFrameNumbersWithOtherType =
new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
- /** the skipped frame numbers that belong to each type of capture results */
- private final LinkedList<Long>[] mSkippedFrameNumbers =
+ /** the frame numbers that belong to each type of capture results which should appear, but
+ * haven't yet.*/
+ private final LinkedList<Long>[] mPendingFrameNumbers =
new LinkedList[CaptureRequest.REQUEST_TYPE_COUNT];
/** frame number -> request type */
@@ -53,8 +57,8 @@ public class FrameNumberTracker {
public FrameNumberTracker() {
for (int i = 0; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
mCompletedFrameNumber[i] = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED;
- mSkippedOtherFrameNumbers[i] = new LinkedList<Long>();
- mSkippedFrameNumbers[i] = new LinkedList<Long>();
+ mPendingFrameNumbersWithOtherType[i] = new LinkedList<Long>();
+ mPendingFrameNumbers[i] = new LinkedList<Long>();
}
}
@@ -66,29 +70,29 @@ public class FrameNumberTracker {
int requestType = (int) pair.getValue();
Boolean removeError = false;
if (errorFrameNumber == mCompletedFrameNumber[requestType] + 1) {
- mCompletedFrameNumber[requestType] = errorFrameNumber;
removeError = true;
+ }
+ // The error frame number could have also either been in the pending list or one of the
+ // 'other' pending lists.
+ if (!mPendingFrameNumbers[requestType].isEmpty()) {
+ if (errorFrameNumber == mPendingFrameNumbers[requestType].element()) {
+ mPendingFrameNumbers[requestType].remove();
+ removeError = true;
+ }
} else {
- if (!mSkippedFrameNumbers[requestType].isEmpty()) {
- if (errorFrameNumber == mSkippedFrameNumbers[requestType].element()) {
- mCompletedFrameNumber[requestType] = errorFrameNumber;
- mSkippedFrameNumbers[requestType].remove();
+ for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
+ int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
+ if (!mPendingFrameNumbersWithOtherType[otherType].isEmpty() && errorFrameNumber
+ == mPendingFrameNumbersWithOtherType[otherType].element()) {
+ mPendingFrameNumbersWithOtherType[otherType].remove();
removeError = true;
- }
- } else {
- for (int i = 1; i < CaptureRequest.REQUEST_TYPE_COUNT; i++) {
- int otherType = (requestType + i) % CaptureRequest.REQUEST_TYPE_COUNT;
- if (!mSkippedOtherFrameNumbers[otherType].isEmpty() && errorFrameNumber
- == mSkippedOtherFrameNumbers[otherType].element()) {
- mCompletedFrameNumber[requestType] = errorFrameNumber;
- mSkippedOtherFrameNumbers[otherType].remove();
- removeError = true;
- break;
- }
+ break;
}
}
}
if (removeError) {
+ mCompletedFrameNumber[requestType] = errorFrameNumber;
+ mPartialResults.remove(errorFrameNumber);
iter.remove();
}
}
@@ -182,7 +186,7 @@ public class FrameNumberTracker {
* It validates that all previous frames of the same category have arrived.
*
* If there is a gap since previous frame number of the same category, assume the frames in
- * the gap are other categories and store them in the skipped frame number queue to check
+ * the gap are other categories and store them in the pending frame number queue to check
* against when frames of those categories arrive.
*/
private void updateCompletedFrameNumber(long frameNumber,
@@ -199,25 +203,29 @@ public class FrameNumberTracker {
if (frameNumber < maxOtherFrameNumberSeen) {
// if frame number is smaller than completed frame numbers of other categories,
// it must be:
- // - the head of mSkippedFrameNumbers for this category, or
- // - in one of other mSkippedOtherFrameNumbers
- if (!mSkippedFrameNumbers[requestType].isEmpty()) {
- // frame number must be head of current type of mSkippedFrameNumbers if
- // mSkippedFrameNumbers isn't empty.
- if (frameNumber < mSkippedFrameNumbers[requestType].element()) {
+ // - the head of mPendingFrameNumbers for this category, or
+ // - in one of other mPendingFrameNumbersWithOtherType
+ if (!mPendingFrameNumbers[requestType].isEmpty()) {
+ // frame number must be head of current type of mPendingFrameNumbers if
+ // mPendingFrameNumbers isn't empty.
+ Long pendingFrameNumberSameType = mPendingFrameNumbers[requestType].element();
+ if (frameNumber == pendingFrameNumberSameType) {
+ // frame number matches the head of the pending frame number queue.
+ // Do this before the inequality checks since this is likely to be the common
+ // case.
+ mPendingFrameNumbers[requestType].remove();
+ } else if (frameNumber < pendingFrameNumberSameType) {
throw new IllegalArgumentException("frame number " + frameNumber
+ " is a repeat");
- } else if (frameNumber > mSkippedFrameNumbers[requestType].element()) {
+ } else {
throw new IllegalArgumentException("frame number " + frameNumber
+ " comes out of order. Expecting "
- + mSkippedFrameNumbers[requestType].element());
+ + pendingFrameNumberSameType);
}
- // frame number matches the head of the skipped frame number queue.
- mSkippedFrameNumbers[requestType].remove();
} else {
- // frame number must be in one of the other mSkippedOtherFrameNumbers.
- int index1 = mSkippedOtherFrameNumbers[otherType1].indexOf(frameNumber);
- int index2 = mSkippedOtherFrameNumbers[otherType2].indexOf(frameNumber);
+ // frame number must be in one of the other mPendingFrameNumbersWithOtherType.
+ int index1 = mPendingFrameNumbersWithOtherType[otherType1].indexOf(frameNumber);
+ int index2 = mPendingFrameNumbersWithOtherType[otherType2].indexOf(frameNumber);
boolean inSkippedOther1 = index1 != -1;
boolean inSkippedOther2 = index2 != -1;
if (!(inSkippedOther1 ^ inSkippedOther2)) {
@@ -225,33 +233,39 @@ public class FrameNumberTracker {
+ " is a repeat or invalid");
}
- // We know the category of frame numbers in skippedOtherFrameNumbers leading up
- // to the current frame number. Move them into the correct skippedFrameNumbers.
+ // We know the category of frame numbers in pendingFrameNumbersWithOtherType leading
+ // up to the current frame number. The destination is the type which isn't the
+ // requestType* and isn't the src. Move them into the correct pendingFrameNumbers.
+ // * : This is since frameNumber is the first frame of requestType that we've
+ // received in the 'others' list, since for each request type frames come in order.
+ // All the frames before frameNumber are of the same type. They're not of
+ // 'requestType', neither of the type of the 'others' list they were found in. The
+ // remaining option is the 3rd type.
LinkedList<Long> srcList, dstList;
int index;
if (inSkippedOther1) {
- srcList = mSkippedOtherFrameNumbers[otherType1];
- dstList = mSkippedFrameNumbers[otherType2];
+ srcList = mPendingFrameNumbersWithOtherType[otherType1];
+ dstList = mPendingFrameNumbers[otherType2];
index = index1;
} else {
- srcList = mSkippedOtherFrameNumbers[otherType2];
- dstList = mSkippedFrameNumbers[otherType1];
+ srcList = mPendingFrameNumbersWithOtherType[otherType2];
+ dstList = mPendingFrameNumbers[otherType1];
index = index2;
}
for (int i = 0; i < index; i++) {
dstList.add(srcList.removeFirst());
}
- // Remove current frame number from skippedOtherFrameNumbers
+ // Remove current frame number from pendingFrameNumbersWithOtherType
srcList.remove();
}
} else {
// there is a gap of unseen frame numbers which should belong to the other
- // 2 categories. Put all the skipped frame numbers in the queue.
+ // 2 categories. Put all the pending frame numbers in the queue.
for (long i =
Math.max(maxOtherFrameNumberSeen, mCompletedFrameNumber[requestType]) + 1;
i < frameNumber; i++) {
- mSkippedOtherFrameNumbers[requestType].add(i);
+ mPendingFrameNumbersWithOtherType[requestType].add(i);
}
}
diff --git a/libs/hwui/shader/RuntimeShader.h b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 7fe0b0206467..a52f983ff2a7 100644
--- a/libs/hwui/shader/RuntimeShader.h
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -14,27 +14,25 @@
* limitations under the License.
*/
-#pragma once
+package android.hardware.devicestate;
-#include "Shader.h"
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
+import android.annotation.SystemService;
+import android.content.Context;
/**
- * RuntimeShader implementation that can map to either a SkShader or SkImageFilter
+ * Manages the state of the system for devices with user-configurable hardware like a foldable
+ * phone.
+ *
+ * @hide
*/
-class RuntimeShader : public Shader {
-public:
- RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
- const SkMatrix* matrix);
- ~RuntimeShader() override;
+@SystemService(Context.DEVICE_STATE_SERVICE)
+public final class DeviceStateManager {
+ /** Invalid device state. */
+ public static final int INVALID_DEVICE_STATE = -1;
-protected:
- sk_sp<SkShader> makeSkShader() override;
+ private DeviceStateManagerGlobal mGlobal;
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
+ public DeviceStateManager() {
+ mGlobal = DeviceStateManagerGlobal.getInstance();
+ }
+}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
new file mode 100644
index 000000000000..4e7cf4af118d
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -0,0 +1,58 @@
+/*
+ * 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.hardware.devicestate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Provides communication with the device state system service on behalf of applications.
+ *
+ * @see DeviceStateManager
+ * @hide
+ */
+final class DeviceStateManagerGlobal {
+ private static DeviceStateManagerGlobal sInstance;
+
+ /**
+ * Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
+ * connection with the device state service couldn't be established.
+ */
+ @Nullable
+ static DeviceStateManagerGlobal getInstance() {
+ synchronized (DeviceStateManagerGlobal.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
+ if (b != null) {
+ sInstance = new DeviceStateManagerGlobal(IDeviceStateManager
+ .Stub.asInterface(b));
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ @NonNull
+ private final IDeviceStateManager mDeviceStateManager;
+
+ private DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
+ mDeviceStateManager = deviceStateManager;
+ }
+}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
new file mode 100644
index 000000000000..24913e9b2d23
--- /dev/null
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.hardware.devicestate;
+
+/** @hide */
+interface IDeviceStateManager {}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 392f56212058..fc14b89d83f7 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,6 +16,8 @@
package android.hardware.display;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -381,6 +383,7 @@ public final class DisplayManager {
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
+ addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
}
return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
} finally {
@@ -401,6 +404,9 @@ public final class DisplayManager {
private void addPresentationDisplaysLocked(
ArrayList<Display> displays, int[] displayIds, int matchType) {
for (int i = 0; i < displayIds.length; i++) {
+ if (displayIds[i] == DEFAULT_DISPLAY) {
+ continue;
+ }
Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
if (display != null
&& (display.getFlags() & Display.FLAG_PRESENTATION) != 0
@@ -841,6 +847,30 @@ public final class DisplayManager {
}
/**
+ * When enabled the app requested mode is always selected regardless of user settings and
+ * policies for low brightness, low battery, etc.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
+ public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
+ mGlobal.setShouldAlwaysRespectAppRequestedMode(enabled);
+ }
+
+ /**
+ * Returns whether we are running in a mode which always selects the app requested display mode
+ * and ignores user settings and policies for low brightness, low battery etc.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
+ public boolean shouldAlwaysRespectAppRequestedMode() {
+ return mGlobal.shouldAlwaysRespectAppRequestedMode();
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
@@ -917,6 +947,7 @@ public final class DisplayManager {
*/
String KEY_PEAK_REFRESH_RATE_DEFAULT = "peak_refresh_rate_default";
+ // TODO(b/162536543): rename it once it is proved not harmful for users.
/**
* Key for controlling which packages are explicitly blocked from running at refresh rates
* higher than 60hz. An app may be added to this list if they exhibit performance issues at
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 0f9c7088a1d0..b046d1df5b8c 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -699,6 +699,32 @@ public final class DisplayManagerGlobal {
}
}
+ /**
+ * When enabled the app requested display resolution and refresh rate is always selected
+ * in DisplayModeDirector regardless of user settings and policies for low brightness, low
+ * battery etc.
+ */
+ public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
+ try {
+ mDm.setShouldAlwaysRespectAppRequestedMode(enabled);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether DisplayModeDirector is running in a mode which always selects the app
+ * requested display mode and ignores user settings and policies for low brightness, low
+ * battery etc.
+ */
+ public boolean shouldAlwaysRespectAppRequestedMode() {
+ try {
+ return mDm.shouldAlwaysRespectAppRequestedMode();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index c697106d0c17..85da6424377a 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -128,4 +128,10 @@ interface IDisplayManager {
// The wide gamut color space is returned from composition pipeline
// based on hardware capability.
int getPreferredWideGamutColorSpaceId();
+
+ // When enabled the app requested display resolution and refresh rate is always selected
+ // in DisplayModeDirector regardless of user settings and policies for low brightness, low
+ // battery etc.
+ void setShouldAlwaysRespectAppRequestedMode(boolean enabled);
+ boolean shouldAlwaysRespectAppRequestedMode();
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 1b114d3528a2..e65d7b5965d5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -49,9 +49,6 @@ import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
/**
* A class that coordinates access to the face authentication hardware.
@@ -299,6 +296,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* int[], Surface)} with {@code surface} set to null.
*
* @see FaceManager#enroll(int, byte[], CancellationSignal, EnrollmentCallback, int[], Surface)
+ * @hide
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
@@ -443,7 +441,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void generateChallenge(GenerateChallengeCallback callback) {
- final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+ final List<FaceSensorPropertiesInternal> faceSensorProperties =
+ getSensorPropertiesInternal();
if (faceSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors");
return;
@@ -460,7 +459,8 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
*/
@RequiresPermission(MANAGE_BIOMETRIC)
public void revokeChallenge() {
- final List<FaceSensorProperties> faceSensorProperties = getSensorProperties();
+ final List<FaceSensorPropertiesInternal> faceSensorProperties =
+ getSensorPropertiesInternal();
if (faceSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors during revokeChallenge");
}
@@ -597,6 +597,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* Determine if there is a face enrolled.
*
* @return true if a face is enrolled, false otherwise
+ * @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledTemplates() {
@@ -632,6 +633,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* Determine if face authentication sensor hardware is present and functional.
*
* @return true if hardware is present and functional, false otherwise.
+ * @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean isHardwareDetected() {
@@ -648,17 +650,32 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
/**
+ * Retrieves a list of properties for all face authentication sensors on the device.
+ * @hide
+ */
+ @NonNull
+ public List<FaceSensorProperties> getSensorProperties() {
+ final List<FaceSensorProperties> properties = new ArrayList<>();
+ final List<FaceSensorPropertiesInternal> internalProperties
+ = getSensorPropertiesInternal();
+ for (FaceSensorPropertiesInternal internalProp : internalProperties) {
+ properties.add(FaceSensorProperties.from(internalProp));
+ }
+ return properties;
+ }
+
+ /**
* Get statically configured sensor properties.
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@NonNull
- public List<FaceSensorProperties> getSensorProperties() {
+ public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal() {
try {
if (mService == null || !mService.isHardwareDetected(mContext.getOpPackageName())) {
return new ArrayList<>();
}
- return mService.getSensorProperties(mContext.getOpPackageName());
+ return mService.getSensorPropertiesInternal(mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -874,6 +891,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
/**
* Container for callback data from {@link FaceManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
+ * @hide
*/
public static class AuthenticationResult {
private Face mFace;
@@ -943,6 +961,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
* FaceManager#authenticate(CryptoObject, CancellationSignal,
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening
* to face events.
+ * @hide
*/
public abstract static class AuthenticationCallback
extends BiometricAuthenticator.AuthenticationCallback {
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index 23ad9353a206..e61d93166cc5 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -16,76 +16,25 @@
package android.hardware.face;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.hardware.biometrics.SensorProperties;
/**
* Container for face sensor properties.
* @hide
*/
-public class FaceSensorProperties implements Parcelable {
+public class FaceSensorProperties extends SensorProperties {
/**
- * A statically configured ID representing this sensor. Sensor IDs must be unique across all
- * biometrics across the device, starting at 0, and in increments of 1.
+ * @hide
*/
- public final int sensorId;
- /**
- * True if the sensor is able to perform generic face detection, without running the
- * matching algorithm, and without affecting the lockout counter.
- */
- public final boolean supportsFaceDetection;
- /**
- * True if the sensor is able to provide self illumination in dark scenarios, without support
- * from above the HAL.
- */
- public final boolean supportsSelfIllumination;
- /**
- * Maximum number of enrollments a user/profile can have.
- */
- public final int maxTemplatesAllowed;
-
+ public static FaceSensorProperties from(FaceSensorPropertiesInternal internalProp) {
+ return new FaceSensorProperties(internalProp.sensorId, internalProp.sensorStrength);
+ }
/**
- * Initializes SensorProperties with specified values
+ * @hide
*/
- public FaceSensorProperties(int sensorId, boolean supportsFaceDetection,
- boolean supportsSelfIllumination, int maxTemplatesAllowed) {
- this.sensorId = sensorId;
- this.supportsFaceDetection = supportsFaceDetection;
- this.supportsSelfIllumination = supportsSelfIllumination;
- this.maxTemplatesAllowed = maxTemplatesAllowed;
+ public FaceSensorProperties(int sensorId, int sensorStrength) {
+ super(sensorId, sensorStrength);
}
- protected FaceSensorProperties(Parcel in) {
- sensorId = in.readInt();
- supportsFaceDetection = in.readBoolean();
- supportsSelfIllumination = in.readBoolean();
- maxTemplatesAllowed = in.readInt();
- }
-
- public static final Creator<FaceSensorProperties> CREATOR =
- new Creator<FaceSensorProperties>() {
- @Override
- public FaceSensorProperties createFromParcel(Parcel in) {
- return new FaceSensorProperties(in);
- }
-
- @Override
- public FaceSensorProperties[] newArray(int size) {
- return new FaceSensorProperties[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(sensorId);
- dest.writeBoolean(supportsFaceDetection);
- dest.writeBoolean(supportsSelfIllumination);
- dest.writeInt(maxTemplatesAllowed);
- }
}
diff --git a/core/java/android/hardware/face/FaceSensorProperties.aidl b/core/java/android/hardware/face/FaceSensorPropertiesInternal.aidl
index d83ee4b17fa7..75bc250f52e2 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.aidl
+++ b/core/java/android/hardware/face/FaceSensorPropertiesInternal.aidl
@@ -15,4 +15,4 @@
*/
package android.hardware.face;
-parcelable FaceSensorProperties; \ No newline at end of file
+parcelable FaceSensorPropertiesInternal; \ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorPropertiesInternal.java b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java
new file mode 100644
index 000000000000..e91554b532b0
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorPropertiesInternal.java
@@ -0,0 +1,81 @@
+/*
+ * 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.hardware.face;
+
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container for face sensor properties.
+ * @hide
+ */
+public class FaceSensorPropertiesInternal extends SensorPropertiesInternal {
+ /**
+ * True if the sensor is able to perform generic face detection, without running the
+ * matching algorithm, and without affecting the lockout counter.
+ */
+ public final boolean supportsFaceDetection;
+ /**
+ * True if the sensor is able to provide self illumination in dark scenarios, without support
+ * from above the HAL.
+ */
+ public final boolean supportsSelfIllumination;
+
+ /**
+ * Initializes SensorProperties with specified values
+ */
+ public FaceSensorPropertiesInternal(int sensorId, @SensorProperties.Strength int strength,
+ int maxEnrollmentsPerUser, boolean supportsFaceDetection,
+ boolean supportsSelfIllumination) {
+ super(sensorId, strength, maxEnrollmentsPerUser);
+ this.supportsFaceDetection = supportsFaceDetection;
+ this.supportsSelfIllumination = supportsSelfIllumination;
+ }
+
+ protected FaceSensorPropertiesInternal(Parcel in) {
+ super(in);
+ supportsFaceDetection = in.readBoolean();
+ supportsSelfIllumination = in.readBoolean();
+ }
+
+ public static final Creator<FaceSensorPropertiesInternal> CREATOR =
+ new Creator<FaceSensorPropertiesInternal>() {
+ @Override
+ public FaceSensorPropertiesInternal createFromParcel(Parcel in) {
+ return new FaceSensorPropertiesInternal(in);
+ }
+
+ @Override
+ public FaceSensorPropertiesInternal[] newArray(int size) {
+ return new FaceSensorPropertiesInternal[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeBoolean(supportsFaceDetection);
+ dest.writeBoolean(supportsSelfIllumination);
+ }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 437feb13b845..b85b6f7d8174 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -19,7 +19,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.view.Surface;
/**
@@ -29,7 +29,7 @@ import android.view.Surface;
*/
interface IFaceService {
// Retrieve static sensor properties for all face sensors
- List<FaceSensorProperties> getSensorProperties(String opPackageName);
+ List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Authenticate the given sessionId with a face
void authenticate(IBinder token, long operationId, int userid, IFaceServiceReceiver receiver,
@@ -110,5 +110,5 @@ interface IFaceService {
String opPackageName);
// Give FaceService its ID. See AuthService.java
- void initializeConfiguration(int sensorId);
+ void initializeConfiguration(int sensorId, int strength);
}
diff --git a/core/java/android/hardware/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 57e52d987474..9ce834ca5981 100644
--- a/core/java/android/hardware/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -31,6 +31,10 @@ public final class Fingerprint extends BiometricAuthenticator.Identifier {
mGroupId = groupId;
}
+ public Fingerprint(CharSequence name, int fingerId, long deviceId) {
+ super(name, fingerId, deviceId);
+ }
+
private Fingerprint(Parcel in) {
super(in.readString(), in.readInt(), in.readLong());
mGroupId = in.readInt();
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index c12bb39c3175..c5f8dac24a4e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -19,6 +19,7 @@ package android.hardware.fingerprint;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
@@ -28,6 +29,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -35,7 +37,9 @@ import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.SensorProperties;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
@@ -53,10 +57,7 @@ import android.view.Surface;
import java.security.Signature;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -97,6 +98,40 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private Fingerprint mRemovalFingerprint;
private Handler mHandler;
+
+ /**
+ * Retrieves a list of properties for all fingerprint sensors on the device.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(TEST_BIOMETRIC)
+ public List<SensorProperties> getSensorProperties() {
+ final List<SensorProperties> properties = new ArrayList<>();
+ final List<FingerprintSensorPropertiesInternal> internalProperties
+ = getSensorPropertiesInternal();
+ for (FingerprintSensorPropertiesInternal internalProp : internalProperties) {
+ properties.add(FingerprintSensorProperties.from(internalProp));
+ }
+ return properties;
+ }
+
+ /**
+ * Retrieves a test session for FingerprintManager.
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(TEST_BIOMETRIC)
+ public BiometricTestSession createTestSession(int sensorId) {
+ try {
+ return new BiometricTestSession(mContext,
+ mService.createTestSession(sensorId, mContext.getOpPackageName()));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private class OnEnrollCancelListener implements OnCancelListener {
@Override
public void onCancel() {
@@ -608,7 +643,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void generateChallenge(GenerateChallengeCallback callback) {
- final List<FingerprintSensorProperties> fingerprintSensorProperties = getSensorProperties();
+ final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
+ getSensorPropertiesInternal();
if (fingerprintSensorProperties.isEmpty()) {
Slog.e(TAG, "No sensors");
return;
@@ -619,13 +655,24 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
- * Finishes enrollment and cancels the current auth token.
+ * Revokes the current challenge.
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void revokeChallenge() {
+ // On HALs with only single in-flight challenge such as IBiometricsFingerprint@2.1,
+ // this parameter is ignored.
+ revokeChallenge(0L);
+ }
+
+ /**
+ * Revokes the specified challenge.
+ * @hide
+ */
+ @RequiresPermission(MANAGE_FINGERPRINT)
+ public void revokeChallenge(long challenge) {
if (mService != null) try {
- mService.revokeChallenge(mToken, mContext.getOpPackageName());
+ mService.revokeChallenge(mToken, mContext.getOpPackageName(), challenge);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -746,7 +793,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void setUdfpsOverlayController(IUdfpsOverlayController controller) {
+ public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
if (mService == null) {
Slog.w(TAG, "setUdfpsOverlayController: no fingerprint service");
return;
@@ -763,14 +810,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
if (mService == null) {
Slog.w(TAG, "onFingerDown: no fingerprint service");
return;
}
try {
- mService.onFingerDown(x, y, minor, major);
+ mService.onPointerDown(sensorId, x, y, minor, major);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -780,14 +827,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void onFingerUp() {
+ public void onPointerUp(int sensorId) {
if (mService == null) {
Slog.w(TAG, "onFingerDown: no fingerprint service");
return;
}
try {
- mService.onFingerUp();
+ mService.onPointerUp(sensorId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -855,12 +902,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@NonNull
- public List<FingerprintSensorProperties> getSensorProperties() {
+ public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal() {
try {
if (mService == null || !mService.isHardwareDetected(mContext.getOpPackageName())) {
return new ArrayList<>();
}
- return mService.getSensorProperties(mContext.getOpPackageName());
+ return mService.getSensorPropertiesInternal(mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
index 2fd006809046..684d7d9cf0a0 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorProperties.java
@@ -17,9 +17,7 @@
package android.hardware.fingerprint;
import android.annotation.IntDef;
-import android.hardware.face.FaceSensorProperties;
-import android.os.Parcel;
-import android.os.Parcelable;
+import android.hardware.biometrics.SensorProperties;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -28,70 +26,77 @@ import java.lang.annotation.RetentionPolicy;
* Container for fingerprint sensor properties.
* @hide
*/
-public class FingerprintSensorProperties implements Parcelable {
-
+public class FingerprintSensorProperties extends SensorProperties {
+ /**
+ * @hide
+ */
public static final int TYPE_UNKNOWN = 0;
+
+ /**
+ * @hide
+ */
public static final int TYPE_REAR = 1;
- public static final int TYPE_UDFPS = 2;
- public static final int TYPE_POWER_BUTTON = 3;
- @IntDef({
- TYPE_UNKNOWN,
+ /**
+ * @hide
+ */
+ public static final int TYPE_UDFPS_ULTRASONIC = 2;
+
+ /**
+ * @hide
+ */
+ public static final int TYPE_UDFPS_OPTICAL = 3;
+
+ /**
+ * @hide
+ */
+ public static final int TYPE_POWER_BUTTON = 4;
+
+ /**
+ * @hide
+ */
+ public static final int TYPE_HOME_BUTTON = 5;
+
+ /**
+ * @hide
+ */
+ @IntDef({TYPE_UNKNOWN,
TYPE_REAR,
- TYPE_UDFPS,
- TYPE_POWER_BUTTON})
+ TYPE_UDFPS_ULTRASONIC,
+ TYPE_UDFPS_OPTICAL,
+ TYPE_POWER_BUTTON,
+ TYPE_HOME_BUTTON})
@Retention(RetentionPolicy.SOURCE)
public @interface SensorType {}
- public final int sensorId;
- public final @SensorType int sensorType;
- // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
- // cannot be checked
- public final boolean resetLockoutRequiresHardwareAuthToken;
- // Maximum number of enrollments a user/profile can have.
- public final int maxTemplatesAllowed;
+ @SensorType final int mSensorType;
/**
- * Initializes SensorProperties with specified values
+ * Constructs a {@link FingerprintSensorProperties} from the internal parcelable representation.
+ * @hide
*/
- public FingerprintSensorProperties(int sensorId, @SensorType int sensorType,
- boolean resetLockoutRequiresHardwareAuthToken, int maxTemplatesAllowed) {
- this.sensorId = sensorId;
- this.sensorType = sensorType;
- this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
- this.maxTemplatesAllowed = maxTemplatesAllowed;
- }
-
- protected FingerprintSensorProperties(Parcel in) {
- sensorId = in.readInt();
- sensorType = in.readInt();
- resetLockoutRequiresHardwareAuthToken = in.readBoolean();
- maxTemplatesAllowed = in.readInt();
+ public static FingerprintSensorProperties from(
+ FingerprintSensorPropertiesInternal internalProp) {
+ return new FingerprintSensorProperties(internalProp.sensorId,
+ internalProp.sensorStrength,
+ internalProp.sensorType);
}
- public static final Creator<FingerprintSensorProperties> CREATOR =
- new Creator<FingerprintSensorProperties>() {
- @Override
- public FingerprintSensorProperties createFromParcel(Parcel in) {
- return new FingerprintSensorProperties(in);
- }
-
- @Override
- public FingerprintSensorProperties[] newArray(int size) {
- return new FingerprintSensorProperties[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
+ /**
+ * @hide
+ */
+ public FingerprintSensorProperties(int sensorId, int sensorStrength,
+ @SensorType int sensorType) {
+ super(sensorId, sensorStrength);
+ mSensorType = sensorType;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(sensorId);
- dest.writeInt(sensorType);
- dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
- dest.writeInt(maxTemplatesAllowed);
+ /**
+ * @hide
+ * @return The sensor's type.
+ */
+ @SensorType
+ public int getSensorType() {
+ return mSensorType;
}
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.aidl b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.aidl
index 83d853dd4048..51705ef89ef3 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorProperties.aidl
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.aidl
@@ -15,4 +15,4 @@
*/
package android.hardware.fingerprint;
-parcelable FingerprintSensorProperties; \ No newline at end of file
+parcelable FingerprintSensorPropertiesInternal; \ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
new file mode 100644
index 000000000000..1f896cdf4298
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -0,0 +1,93 @@
+/*
+ * 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.hardware.fingerprint;
+
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
+
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.os.Parcel;
+
+/**
+ * Container for fingerprint sensor properties.
+ * @hide
+ */
+public class FingerprintSensorPropertiesInternal extends SensorPropertiesInternal {
+ public final @FingerprintSensorProperties.SensorType int sensorType;
+ // IBiometricsFingerprint@2.1 does not manage timeout below the HAL, so the Gatekeeper HAT
+ // cannot be checked
+ public final boolean resetLockoutRequiresHardwareAuthToken;
+
+ /**
+ * Initializes SensorProperties with specified values
+ */
+ public FingerprintSensorPropertiesInternal(int sensorId,
+ @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+ @FingerprintSensorProperties.SensorType int sensorType,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ super(sensorId, strength, maxEnrollmentsPerUser);
+ this.sensorType = sensorType;
+ this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ }
+
+ protected FingerprintSensorPropertiesInternal(Parcel in) {
+ super(in);
+ sensorType = in.readInt();
+ resetLockoutRequiresHardwareAuthToken = in.readBoolean();
+ }
+
+ public static final Creator<FingerprintSensorPropertiesInternal> CREATOR =
+ new Creator<FingerprintSensorPropertiesInternal>() {
+ @Override
+ public FingerprintSensorPropertiesInternal createFromParcel(Parcel in) {
+ return new FingerprintSensorPropertiesInternal(in);
+ }
+
+ @Override
+ public FingerprintSensorPropertiesInternal[] newArray(int size) {
+ return new FingerprintSensorPropertiesInternal[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(sensorType);
+ dest.writeBoolean(resetLockoutRequiresHardwareAuthToken);
+ }
+
+ public boolean isAnyUdfpsType() {
+ switch (sensorType) {
+ case TYPE_UDFPS_OPTICAL:
+ case TYPE_UDFPS_ULTRASONIC:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType;
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 0fae15648e15..cc086cf5656e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -17,11 +17,12 @@ package android.hardware.fingerprint;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.ITestSession;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.view.Surface;
import java.util.List;
@@ -30,8 +31,12 @@ import java.util.List;
* @hide
*/
interface IFingerprintService {
+
+ // Creates a test session with the specified sensorId
+ ITestSession createTestSession(int sensorId, String opPackageName);
+
// Retrieve static sensor properties for all fingerprint sensors
- List<FingerprintSensorProperties> getSensorProperties(String opPackageName);
+ List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Authenticate the given sessionId with a fingerprint. This is protected by
// USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
@@ -91,7 +96,7 @@ interface IFingerprintService {
void generateChallenge(IBinder token, int sensorId, IFingerprintServiceReceiver receiver, String opPackageName);
// Finish an enrollment sequence and invalidate the authentication token
- void revokeChallenge(IBinder token, String opPackageName);
+ void revokeChallenge(IBinder token, String opPackageName, long challenge);
// Determine if a user has at least one enrolled fingerprint
boolean hasEnrolledFingerprints(int userId, String opPackageName);
@@ -118,13 +123,13 @@ interface IFingerprintService {
void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
// Give FingerprintService its ID. See AuthService.java
- void initializeConfiguration(int sensorId);
+ void initializeConfiguration(int sensorId, int strength);
// Notifies about a finger touching the sensor area.
- void onFingerDown(int x, int y, float minor, float major);
+ void onPointerDown(int sensorId, int x, int y, float minor, float major);
// Notifies about a finger leaving the sensor area.
- void onFingerUp();
+ void onPointerUp(int sensorId);
// Sets the controller for managing the UDFPS overlay.
void setUdfpsOverlayController(in IUdfpsOverlayController controller);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index a57726c4afe4..58b7046ad991 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -21,11 +21,11 @@ package android.hardware.fingerprint;
*/
oneway interface IUdfpsOverlayController {
// Shows the overlay.
- void showUdfpsOverlay();
+ void showUdfpsOverlay(int sensorId);
// Hides the overlay.
- void hideUdfpsOverlay();
+ void hideUdfpsOverlay(int sensorId);
// Shows debug messages on the UDFPS overlay.
- void setDebugMessage(String message);
+ void setDebugMessage(int sensorId, String message);
}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index da4d8e06ad48..e5e73200c8ef 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -1006,8 +1006,12 @@ public final class HdmiControlManager {
return new IHdmiHotplugEventListener.Stub() {
@Override
public void onReceived(HdmiHotplugEvent event) {
- Binder.clearCallingIdentity();
- executor.execute(() -> listener.onReceived(event));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onReceived(event));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
};
}
@@ -1098,8 +1102,12 @@ public final class HdmiControlManager {
return new IHdmiControlStatusChangeListener.Stub() {
@Override
public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
- Binder.clearCallingIdentity();
- executor.execute(() -> listener.onStatusChange(isCecEnabled, isCecAvailable));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onStatusChange(isCecEnabled, isCecAvailable));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
};
}
@@ -1171,8 +1179,12 @@ public final class HdmiControlManager {
return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() {
@Override
public void onHdmiCecVolumeControlFeature(boolean enabled) {
- Binder.clearCallingIdentity();
- executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
};
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index dd820fae3f2d..25fec3208f65 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,26 +16,32 @@
package android.hardware.input;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.media.AudioAttributes;
import android.os.Binder;
+import android.os.BlockUntrustedTouchesMode;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
@@ -48,8 +54,10 @@ import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
+import android.view.WindowManager.LayoutParams;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -69,6 +77,13 @@ public final class InputManager {
private static final int MSG_DEVICE_REMOVED = 2;
private static final int MSG_DEVICE_CHANGED = 3;
+ /** @hide */
+ public static final int[] BLOCK_UNTRUSTED_TOUCHES_MODES = {
+ BlockUntrustedTouchesMode.DISABLED,
+ BlockUntrustedTouchesMode.PERMISSIVE,
+ BlockUntrustedTouchesMode.BLOCK
+ };
+
private static InputManager sInstance;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -168,11 +183,37 @@ public final class InputManager {
public static final int DEFAULT_POINTER_SPEED = 0;
/**
+ * The maximum allowed obscuring opacity by UID to propagate touches (0 <= x <= 1).
+ * @hide
+ */
+ public static final float DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH = .8f;
+
+ /**
+ * Default mode of the block untrusted touches mode feature.
+ * @hide
+ */
+ @BlockUntrustedTouchesMode
+ public static final int DEFAULT_BLOCK_UNTRUSTED_TOUCHES_MODE =
+ BlockUntrustedTouchesMode.DISABLED;
+
+ /**
+ * Prevent touches from being consumed by apps if these touches passed through a non-trusted
+ * window from a different UID and are considered unsafe.
+ *
+ * TODO(b/158002302): Turn the feature on by default
+ *
+ * @hide
+ */
+ @TestApi
+ @ChangeId
+ public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L;
+
+ /**
* Input Event Injection Synchronization Mode: None.
* Never blocks. Injection is asynchronous and is assumed always to be successful.
* @hide
*/
- public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h
+ public static final int INJECT_INPUT_EVENT_MODE_ASYNC = InputEventInjectionSync.NONE;
/**
* Input Event Injection Synchronization Mode: Wait for result.
@@ -182,7 +223,8 @@ public final class InputManager {
* by the application.
* @hide
*/
- public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; // see InputDispatcher.h
+ public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT =
+ InputEventInjectionSync.WAIT_FOR_RESULT;
/**
* Input Event Injection Synchronization Mode: Wait for finish.
@@ -190,7 +232,8 @@ public final class InputManager {
* @hide
*/
@UnsupportedAppUsage
- public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h
+ public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH =
+ InputEventInjectionSync.WAIT_FOR_FINISHED;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -832,6 +875,103 @@ public final class InputManager {
}
/**
+ * Returns the maximum allowed obscuring opacity by UID to propagate touches.
+ *
+ * For certain window types (eg. SAWs), the decision of honoring {@link LayoutParams
+ * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
+ * above the touch-consuming window.
+ *
+ * @see #setMaximumObscuringOpacityForTouch(Context, float)
+ *
+ * @hide
+ */
+ @TestApi
+ public float getMaximumObscuringOpacityForTouch(@NonNull Context context) {
+ return Settings.Global.getFloat(context.getContentResolver(),
+ Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
+ DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
+ }
+
+ /**
+ * Sets the maximum allowed obscuring opacity by UID to propagate touches.
+ *
+ * For certain window types (eg. SAWs), the decision of honoring {@link LayoutParams
+ * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
+ * above the touch-consuming window.
+ *
+ * For a certain UID:
+ * <ul>
+ * <li>If it's the same as the UID of the touch-consuming window, allow it to propagate
+ * the touch.
+ * <li>Otherwise take all its windows of eligible window types above the touch-consuming
+ * window, compute their combined obscuring opacity considering that {@code
+ * opacity(A, B) = 1 - (1 - opacity(A))*(1 - opacity(B))}. If the computed value is
+ * lesser than or equal to this setting and there are no other windows preventing the
+ * touch, allow the UID to propagate the touch.
+ * </ul>
+ *
+ * This value should be between 0 (inclusive) and 1 (inclusive).
+ *
+ * @see #getMaximumObscuringOpacityForTouch(Context)
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setMaximumObscuringOpacityForTouch(@NonNull Context context, float opacity) {
+ if (opacity < 0 || opacity > 1) {
+ throw new IllegalArgumentException(
+ "Maximum obscuring opacity for touch should be >= 0 and <= 1");
+ }
+ Settings.Global.putFloat(context.getContentResolver(),
+ Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH, opacity);
+ }
+
+ /**
+ * Returns the current mode of the block untrusted touches feature, one of:
+ * <ul>
+ * <li>{@link BlockUntrustedTouchesMode#DISABLED}
+ * <li>{@link BlockUntrustedTouchesMode#PERMISSIVE}
+ * <li>{@link BlockUntrustedTouchesMode#BLOCK}
+ * </ul>
+ *
+ * @hide
+ */
+ @TestApi
+ @BlockUntrustedTouchesMode
+ public int getBlockUntrustedTouchesMode(@NonNull Context context) {
+ int mode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.BLOCK_UNTRUSTED_TOUCHES_MODE, DEFAULT_BLOCK_UNTRUSTED_TOUCHES_MODE);
+ if (!ArrayUtils.contains(BLOCK_UNTRUSTED_TOUCHES_MODES, mode)) {
+ Log.w(TAG, "Unknown block untrusted touches feature mode " + mode + ", using "
+ + "default " + DEFAULT_BLOCK_UNTRUSTED_TOUCHES_MODE);
+ return DEFAULT_BLOCK_UNTRUSTED_TOUCHES_MODE;
+ }
+ return mode;
+ }
+
+ /**
+ * Sets the mode of the block untrusted touches feature to one of:
+ * <ul>
+ * <li>{@link BlockUntrustedTouchesMode#DISABLED}
+ * <li>{@link BlockUntrustedTouchesMode#PERMISSIVE}
+ * <li>{@link BlockUntrustedTouchesMode#BLOCK}
+ * </ul>
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setBlockUntrustedTouchesMode(@NonNull Context context,
+ @BlockUntrustedTouchesMode int mode) {
+ if (!ArrayUtils.contains(BLOCK_UNTRUSTED_TOUCHES_MODES, mode)) {
+ throw new IllegalArgumentException("Invalid feature mode " + mode);
+ }
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.BLOCK_UNTRUSTED_TOUCHES_MODE, mode);
+ }
+
+ /**
* Queries the framework about whether any physical keys exist on the
* any keyboard attached to the device that are capable of producing the given
* array of key codes.
@@ -885,9 +1025,9 @@ public final class InputManager {
*
* @param event The event to inject.
* @param mode The synchronization mode. One of:
- * {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
- * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
- * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
+ * {@link android.os.InputEventInjectionSync.NONE},
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+ * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
* @return True if input event injection succeeded.
*
* @hide
@@ -897,9 +1037,9 @@ public final class InputManager {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
- if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
- && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
- && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
+ if (mode != InputEventInjectionSync.NONE
+ && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+ && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
@@ -1295,8 +1435,8 @@ public final class InputManager {
* @hide
*/
@Override
- public void vibrate(int uid, String opPkg, VibrationEffect effect,
- String reason, AudioAttributes attributes) {
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
+ String reason, @NonNull VibrationAttributes attributes) {
try {
mIm.vibrate(mDeviceId, effect, mToken);
} catch (RemoteException ex) {
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index f6cd726dda4a..1173c311bd26 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -78,4 +78,26 @@ public abstract class InputManagerInternal {
*/
public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken);
+
+ /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */
+ public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks);
+
+ /**
+ * Unregisters a {@link LidSwitchCallback callback} previously registered with
+ * {@link #registerLidSwitchCallback(LidSwitchCallback)}.
+ */
+ public abstract void unregisterLidSwitchCallback(@NonNull LidSwitchCallback callbacks);
+
+ /** Callback interface for notifications relating to the lid switch. */
+ public interface LidSwitchCallback {
+ /**
+ * This callback is invoked when the lid switch changes state. Will be triggered once on
+ * registration of the callback with a {@code whenNanos} of 0 and then on every subsequent
+ * change in lid switch state.
+ *
+ * @param whenNanos the time when the change occurred
+ * @param lidOpen true if the lid is open
+ */
+ void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index c4d123ca4382..06b5b6745bd1 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -32,10 +32,12 @@ import android.media.soundtrigger_middleware.RecognitionMode;
import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
import android.system.ErrnoException;
import java.io.FileDescriptor;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.UUID;
@@ -109,7 +111,12 @@ class ConversionUtil {
aidlModel.type = apiModel.getType();
aidlModel.uuid = api2aidlUuid(apiModel.getUuid());
aidlModel.vendorUuid = api2aidlUuid(apiModel.getVendorUuid());
- aidlModel.data = byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel");
+ try {
+ aidlModel.data = ParcelFileDescriptor.dup(
+ byteArrayToSharedMemory(apiModel.getData(), "SoundTrigger SoundModel"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
aidlModel.dataSize = apiModel.getData().length;
return aidlModel;
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 80f35a0a2e32..0f1c2a59965b 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -16,6 +16,9 @@
package android.hardware.soundtrigger;
+import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.RECORD_AUDIO;
+import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOSYS;
@@ -27,6 +30,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -34,9 +38,13 @@ import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioFormat;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.Identity;
+import android.media.permission.SafeCloseable;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.media.soundtrigger_middleware.Status;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -1943,16 +1951,6 @@ public class SoundTrigger {
public static final int SERVICE_STATE_DISABLED = 1;
private static Object mServiceLock = new Object();
private static ISoundTriggerMiddlewareService mService;
- /**
- * @return returns current package name.
- */
- static String getCurrentOpPackageName() {
- String packageName = ActivityThread.currentOpPackageName();
- if (packageName == null) {
- return "";
- }
- return packageName;
- }
/**
* Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2005,17 +2003,81 @@ public class SoundTrigger {
* - {@link #STATUS_BAD_VALUE} if modules is null
* - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
*
+ * @deprecated Please use {@link #listModulesAsOriginator(ArrayList, Identity)} or
+ * {@link #listModulesAsMiddleman(ArrayList, Identity, Identity)}, based on whether the
+ * client is acting on behalf of its own identity or a separate identity.
* @hide
*/
@UnsupportedAppUsage
public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+ // TODO(ytai): This is a temporary hack to retain prior behavior, which makes
+ // assumptions about process affinity and Binder context, namely that the binder calling ID
+ // reliably reflects the originator identity.
+ Identity middlemanIdentity = new Identity();
+ middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
+
+ Identity originatorIdentity = new Identity();
+ originatorIdentity.pid = Binder.getCallingPid();
+ originatorIdentity.uid = Binder.getCallingUid();
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return listModulesAsMiddleman(modules, middlemanIdentity, originatorIdentity);
+ }
+ }
+
+ /**
+ * Returns a list of descriptors for all hardware modules loaded.
+ * This variant is intended for use when the caller itself is the originator of the operation.
+ * @param modules A ModuleProperties array where the list will be returned.
+ * @param originatorIdentity The identity of the originator, which will be used for permission
+ * purposes.
+ * @return - {@link #STATUS_OK} in case of success
+ * - {@link #STATUS_ERROR} in case of unspecified error
+ * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
+ * - {@link #STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link #STATUS_BAD_VALUE} if modules is null
+ * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
+ public static int listModulesAsOriginator(@NonNull ArrayList<ModuleProperties> modules,
+ @NonNull Identity originatorIdentity) {
try {
- SoundTriggerModuleDescriptor[] descs = getService().listModules();
- modules.clear();
- modules.ensureCapacity(descs.length);
- for (SoundTriggerModuleDescriptor desc : descs) {
- modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
- }
+ SoundTriggerModuleDescriptor[] descs = getService().listModulesAsOriginator(
+ originatorIdentity);
+ convertDescriptorsToModuleProperties(descs, modules);
+ return STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
+
+ /**
+ * Returns a list of descriptors for all hardware modules loaded.
+ * This variant is intended for use when the caller is acting on behalf of a different identity
+ * for permission purposes.
+ * @param modules A ModuleProperties array where the list will be returned.
+ * @param middlemanIdentity The identity of the caller, acting as middleman.
+ * @param originatorIdentity The identity of the originator, which will be used for permission
+ * purposes.
+ * @return - {@link #STATUS_OK} in case of success
+ * - {@link #STATUS_ERROR} in case of unspecified error
+ * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
+ * - {@link #STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link #STATUS_BAD_VALUE} if modules is null
+ * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
+ *
+ * @hide
+ */
+ @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY)
+ public static int listModulesAsMiddleman(@NonNull ArrayList<ModuleProperties> modules,
+ @NonNull Identity middlemanIdentity,
+ @NonNull Identity originatorIdentity) {
+ try {
+ SoundTriggerModuleDescriptor[] descs = getService().listModulesAsMiddleman(
+ middlemanIdentity, originatorIdentity);
+ convertDescriptorsToModuleProperties(descs, modules);
return STATUS_OK;
} catch (Exception e) {
return handleException(e);
@@ -2023,6 +2085,22 @@ public class SoundTrigger {
}
/**
+ * Converts an array of SoundTriggerModuleDescriptor into an (existing) ArrayList of
+ * ModuleProperties.
+ * @param descsIn The input descriptors.
+ * @param modulesOut The output list.
+ */
+ private static void convertDescriptorsToModuleProperties(
+ @NonNull SoundTriggerModuleDescriptor[] descsIn,
+ @NonNull ArrayList<ModuleProperties> modulesOut) {
+ modulesOut.clear();
+ modulesOut.ensureCapacity(descsIn.length);
+ for (SoundTriggerModuleDescriptor desc : descsIn) {
+ modulesOut.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+ }
+ }
+
+ /**
* Get an interface on a hardware module to control sound models and recognition on
* this module.
* @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory.
@@ -2031,15 +2109,85 @@ public class SoundTrigger {
* is OK.
* @return a valid sound module in case of success or null in case of error.
*
+ * @deprecated Please use
+ * {@link #attachModuleAsOriginator(int, StatusListener, Handler, Identity)} or
+ * {@link #attachModuleAsMiddleman(int, StatusListener, Handler, Identity, Identity)}, based
+ * on whether the client is acting on behalf of its own identity or a separate identity.
* @hide
*/
@UnsupportedAppUsage
- public static @NonNull SoundTriggerModule attachModule(int moduleId,
+ private static SoundTriggerModule attachModule(int moduleId,
@NonNull StatusListener listener,
@Nullable Handler handler) {
+ // TODO(ytai): This is a temporary hack to retain prior behavior, which makes
+ // assumptions about process affinity and Binder context, namely that the binder calling ID
+ // reliably reflects the originator identity.
+ Identity middlemanIdentity = new Identity();
+ middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
+
+ Identity originatorIdentity = new Identity();
+ originatorIdentity.pid = Binder.getCallingPid();
+ originatorIdentity.uid = Binder.getCallingUid();
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return attachModuleAsMiddleman(moduleId, listener, handler, middlemanIdentity,
+ originatorIdentity);
+ }
+ }
+
+ /**
+ * Get an interface on a hardware module to control sound models and recognition on
+ * this module.
+ * This variant is intended for use when the caller is acting on behalf of a different identity
+ * for permission purposes.
+ * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory.
+ * @param listener {@link StatusListener} interface. Mandatory.
+ * @param handler the Handler that will receive the callabcks. Can be null if default handler
+ * is OK.
+ * @param middlemanIdentity The identity of the caller, acting as middleman.
+ * @param originatorIdentity The identity of the originator, which will be used for permission
+ * purposes.
+ * @return a valid sound module in case of success or null in case of error.
+ *
+ * @hide
+ */
+ @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY)
+ public static SoundTriggerModule attachModuleAsMiddleman(int moduleId,
+ @NonNull SoundTrigger.StatusListener listener,
+ @Nullable Handler handler, Identity middlemanIdentity,
+ Identity originatorIdentity) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+ try {
+ return new SoundTriggerModule(getService(), moduleId, listener, looper,
+ middlemanIdentity, originatorIdentity);
+ } catch (Exception e) {
+ Log.e(TAG, "", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get an interface on a hardware module to control sound models and recognition on
+ * this module.
+ * This variant is intended for use when the caller itself is the originator of the operation.
+ * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory.
+ * @param listener {@link StatusListener} interface. Mandatory.
+ * @param handler the Handler that will receive the callabcks. Can be null if default handler
+ * is OK.
+ * @param originatorIdentity The identity of the originator, which will be used for permission
+ * purposes.
+ * @return a valid sound module in case of success or null in case of error.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
+ public static SoundTriggerModule attachModuleAsOriginator(int moduleId,
+ @NonNull SoundTrigger.StatusListener listener,
+ @Nullable Handler handler, @NonNull Identity originatorIdentity) {
Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
try {
- return new SoundTriggerModule(getService(), moduleId, listener, looper);
+ return new SoundTriggerModule(getService(), moduleId, listener, looper,
+ originatorIdentity);
} catch (Exception e) {
Log.e(TAG, "", e);
return null;
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index a2a15b30d578..05823bf14b63 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -19,6 +19,9 @@ package android.hardware.soundtrigger;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.Identity;
+import android.media.permission.SafeCloseable;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -50,12 +53,39 @@ public class SoundTriggerModule {
private EventHandlerDelegate mEventHandlerDelegate;
private ISoundTriggerModule mService;
+ /**
+ * This variant is intended for use when the caller is acting an originator, rather than on
+ * behalf of a different entity, as far as authorization goes.
+ */
SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
- int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
+ @NonNull Identity originatorIdentity)
throws RemoteException {
mId = moduleId;
mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
- mService = service.attach(moduleId, mEventHandlerDelegate);
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mService = service.attachAsOriginator(moduleId, originatorIdentity,
+ mEventHandlerDelegate);
+ }
+ mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
+ }
+
+ /**
+ * This variant is intended for use when the caller is acting as a middleman, i.e. on behalf of
+ * a different entity, as far as authorization goes.
+ */
+ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
+ @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity)
+ throws RemoteException {
+ mId = moduleId;
+ mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mService = service.attachAsMiddleman(moduleId, middlemanIdentity, originatorIdentity,
+ mEventHandlerDelegate);
+ }
mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index d7ca63a54987..3ca5207d867c 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -193,7 +193,17 @@ public abstract class AbstractInputMethodService extends Service
* needed for a new client of the input method.
*/
public abstract AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
-
+
+ /**
+ * Dumps the internal state of IME to a protocol buffer output stream initialized using the
+ * given {@link FileDescriptor}.
+ *
+ * @param fd The file descriptor to which proto dump should be written.
+ * @param args The arguments passed to the dump method.
+ * @hide
+ */
+ abstract void dumpProtoInternal(FileDescriptor fd, String[] args);
+
/**
* Implement this to handle {@link android.os.Binder#dump Binder.dump()}
* calls on your input method.
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index a298c856a0fb..0512305e71a2 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -16,6 +16,8 @@
package android.inputmethodservice;
+import static android.util.imetracing.ImeTracing.PROTO_ARG;
+
import android.annotation.BinderThread;
import android.annotation.MainThread;
import android.annotation.Nullable;
@@ -37,8 +39,8 @@ import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
@@ -155,9 +157,20 @@ class IInputMethodWrapper extends IInputMethod.Stub
return;
}
SomeArgs args = (SomeArgs)msg.obj;
+ String[] dumpArgs = (String[]) args.arg3;
+ boolean protoDumpRequested = false;
+ for (String arg : dumpArgs) {
+ if (arg.equals(PROTO_ARG)) {
+ protoDumpRequested = true;
+ break;
+ }
+ }
try {
- target.dump((FileDescriptor)args.arg1,
- (PrintWriter)args.arg2, (String[])args.arg3);
+ if (protoDumpRequested) {
+ target.dumpProtoInternal((FileDescriptor) args.arg1, dumpArgs);
+ } else {
+ target.dump((FileDescriptor) args.arg1, (PrintWriter) args.arg2, dumpArgs);
+ }
} catch (RuntimeException e) {
((PrintWriter)args.arg2).println("Exception: " + e);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 60ddd8ab22a0..78cc71a782a5 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,10 +16,41 @@
package android.inputmethodservice;
+import static android.graphics.Color.TRANSPARENT;
+import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VIEW_STARTED;
+import static android.inputmethodservice.InputMethodServiceProto.CANDIDATES_VISIBILITY;
+import static android.inputmethodservice.InputMethodServiceProto.CAN_PRE_RENDER;
+import static android.inputmethodservice.InputMethodServiceProto.CONFIGURATION;
+import static android.inputmethodservice.InputMethodServiceProto.DECOR_VIEW_VISIBLE;
+import static android.inputmethodservice.InputMethodServiceProto.DECOR_VIEW_WAS_VISIBLE;
+import static android.inputmethodservice.InputMethodServiceProto.EXTRACTED_TOKEN;
+import static android.inputmethodservice.InputMethodServiceProto.EXTRACT_VIEW_HIDDEN;
+import static android.inputmethodservice.InputMethodServiceProto.FULLSCREEN_APPLIED;
+import static android.inputmethodservice.InputMethodServiceProto.INPUT_BINDING;
+import static android.inputmethodservice.InputMethodServiceProto.INPUT_EDITOR_INFO;
+import static android.inputmethodservice.InputMethodServiceProto.INPUT_STARTED;
+import static android.inputmethodservice.InputMethodServiceProto.INPUT_VIEW_STARTED;
+import static android.inputmethodservice.InputMethodServiceProto.IN_SHOW_WINDOW;
+import static android.inputmethodservice.InputMethodServiceProto.IS_FULLSCREEN;
+import static android.inputmethodservice.InputMethodServiceProto.IS_INPUT_VIEW_SHOWN;
+import static android.inputmethodservice.InputMethodServiceProto.IS_PRE_RENDERED;
+import static android.inputmethodservice.InputMethodServiceProto.InsetsProto.CONTENT_TOP_INSETS;
+import static android.inputmethodservice.InputMethodServiceProto.InsetsProto.TOUCHABLE_INSETS;
+import static android.inputmethodservice.InputMethodServiceProto.InsetsProto.TOUCHABLE_REGION;
+import static android.inputmethodservice.InputMethodServiceProto.InsetsProto.VISIBLE_TOP_INSETS;
+import static android.inputmethodservice.InputMethodServiceProto.LAST_COMPUTED_INSETS;
+import static android.inputmethodservice.InputMethodServiceProto.LAST_SHOW_INPUT_REQUESTED;
+import static android.inputmethodservice.InputMethodServiceProto.SETTINGS_OBSERVER;
+import static android.inputmethodservice.InputMethodServiceProto.SHOW_INPUT_FLAGS;
+import static android.inputmethodservice.InputMethodServiceProto.SHOW_INPUT_REQUESTED;
+import static android.inputmethodservice.InputMethodServiceProto.SOFT_INPUT_WINDOW;
+import static android.inputmethodservice.InputMethodServiceProto.STATUS_ICON;
+import static android.inputmethodservice.InputMethodServiceProto.TOKEN;
+import static android.inputmethodservice.InputMethodServiceProto.VIEWS_CREATED;
+import static android.inputmethodservice.InputMethodServiceProto.WINDOW_VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowInsets.Type.navigationBars;
-import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -59,6 +90,7 @@ import android.text.method.MovementMethod;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -69,7 +101,6 @@ import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.Window;
-import android.view.WindowInsets;
import android.view.WindowInsets.Side;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
@@ -105,6 +136,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
+import java.util.Objects;
/**
* InputMethodService provides a standard implementation of an InputMethod,
@@ -1042,6 +1074,15 @@ public class InputMethodService extends AbstractInputMethodService {
* or {@link #TOUCHABLE_INSETS_REGION}.
*/
public int touchableInsets;
+
+ private void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(CONTENT_TOP_INSETS, contentTopInsets);
+ proto.write(VISIBLE_TOP_INSETS, visibleTopInsets);
+ proto.write(TOUCHABLE_INSETS, touchableInsets);
+ proto.write(TOUCHABLE_REGION, touchableRegion.toString());
+ proto.end(token);
+ }
}
/**
@@ -1204,25 +1245,22 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
- mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
+ mWindow.getWindow().getAttributes().setFitInsetsTypes(navigationBars());
mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true);
- // IME layout should always be inset by navigation bar, no matter its current visibility,
- // unless automotive requests it. Automotive devices may request the navigation bar to be
- // hidden when the IME shows up (controlled via config_automotiveHideNavBarForKeyboard)
- // in order to maximize the visible screen real estate. When this happens, the IME window
- // should animate from the bottom of the screen to reduce the jank that happens from the
- // lack of synchronization between the bottom system window and the IME window.
+ // Our window will extend into the status bar area no matter the bar is visible or not.
+ // We don't want the ColorView to be visible when status bar is shown.
+ mWindow.getWindow().setStatusBarColor(TRANSPARENT);
+
+ // Automotive devices may request the navigation bar to be hidden when the IME shows up
+ // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible
+ // screen real estate. When this happens, the IME window should animate from the bottom of
+ // the screen to reduce the jank that happens from the lack of synchronization between the
+ // bottom system window and the IME window.
if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
mWindow.getWindow().setDecorFitsSystemWindows(false);
}
- mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener(
- (v, insets) -> v.onApplyWindowInsets(
- new WindowInsets.Builder(insets).setInsets(
- navigationBars(),
- insets.getInsetsIgnoringVisibility(navigationBars()))
- .build()));
// For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
// by default (but IME developers can opt this out later if they want a new behavior).
@@ -1384,7 +1422,7 @@ public class InputMethodService extends AbstractInputMethodService {
public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
return new InputMethodSessionImpl();
}
-
+
public LayoutInflater getLayoutInflater() {
return mInflater;
}
@@ -3298,7 +3336,7 @@ public class InputMethodService extends AbstractInputMethodService {
} else {
p.println(" mInputEditorInfo: null");
}
-
+
p.println(" mShowInputRequested=" + mShowInputRequested
+ " mLastShowInputRequested=" + mLastShowInputRequested
+ " mCanPreRender=" + mCanPreRender
@@ -3308,7 +3346,7 @@ public class InputMethodService extends AbstractInputMethodService {
+ " mFullscreenApplied=" + mFullscreenApplied
+ " mIsFullscreen=" + mIsFullscreen
+ " mExtractViewHidden=" + mExtractViewHidden);
-
+
if (mExtractedText != null) {
p.println(" mExtractedText:");
p.println(" text=" + mExtractedText.text.length() + " chars"
@@ -3329,4 +3367,42 @@ public class InputMethodService extends AbstractInputMethodService {
+ " touchableRegion=" + mTmpInsets.touchableRegion);
p.println(" mSettingsObserver=" + mSettingsObserver);
}
+
+ /**
+ * @hide
+ */
+ @Override
+ final void dumpProtoInternal(FileDescriptor fd, String[] args) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ mWindow.dumpDebug(proto, SOFT_INPUT_WINDOW);
+ proto.write(VIEWS_CREATED, mViewsCreated);
+ proto.write(DECOR_VIEW_VISIBLE, mDecorViewVisible);
+ proto.write(DECOR_VIEW_WAS_VISIBLE, mDecorViewWasVisible);
+ proto.write(WINDOW_VISIBLE, mWindowVisible);
+ proto.write(IN_SHOW_WINDOW, mInShowWindow);
+ proto.write(CONFIGURATION, getResources().getConfiguration().toString());
+ proto.write(TOKEN, Objects.toString(mToken));
+ proto.write(INPUT_BINDING, Objects.toString(mInputBinding));
+ proto.write(INPUT_STARTED, mInputStarted);
+ proto.write(INPUT_VIEW_STARTED, mInputViewStarted);
+ proto.write(CANDIDATES_VIEW_STARTED, mCandidatesViewStarted);
+ if (mInputEditorInfo != null) {
+ mInputEditorInfo.dumpDebug(proto, INPUT_EDITOR_INFO);
+ }
+ proto.write(SHOW_INPUT_REQUESTED, mShowInputRequested);
+ proto.write(LAST_SHOW_INPUT_REQUESTED, mLastShowInputRequested);
+ proto.write(CAN_PRE_RENDER, mCanPreRender);
+ proto.write(IS_PRE_RENDERED, mIsPreRendered);
+ proto.write(SHOW_INPUT_FLAGS, mShowInputFlags);
+ proto.write(CANDIDATES_VISIBILITY, mCandidatesVisibility);
+ proto.write(FULLSCREEN_APPLIED, mFullscreenApplied);
+ proto.write(IS_FULLSCREEN, mIsFullscreen);
+ proto.write(EXTRACT_VIEW_HIDDEN, mExtractViewHidden);
+ proto.write(EXTRACTED_TOKEN, mExtractedToken);
+ proto.write(IS_INPUT_VIEW_SHOWN, mIsInputViewShown);
+ proto.write(STATUS_ICON, mStatusIcon);
+ mTmpInsets.dumpDebug(proto, LAST_COMPUTED_INSETS);
+ proto.write(SETTINGS_OBSERVER, Objects.toString(mSettingsObserver));
+ proto.flush();
+ }
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 6efd03c44b9f..bc0b37ed626d 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -16,6 +16,13 @@
package android.inputmethodservice;
+import static android.inputmethodservice.SoftInputWindowProto.BOUNDS;
+import static android.inputmethodservice.SoftInputWindowProto.GRAVITY;
+import static android.inputmethodservice.SoftInputWindowProto.NAME;
+import static android.inputmethodservice.SoftInputWindowProto.TAKES_FOCUS;
+import static android.inputmethodservice.SoftInputWindowProto.WINDOW_STATE;
+import static android.inputmethodservice.SoftInputWindowProto.WINDOW_TYPE;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -25,6 +32,7 @@ import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -362,4 +370,15 @@ public class SoftInputWindow extends Dialog {
throw new IllegalStateException("Unknown state=" + state);
}
}
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(NAME, mName);
+ proto.write(WINDOW_TYPE, mWindowType);
+ proto.write(GRAVITY, mGravity);
+ proto.write(TAKES_FOCUS, mTakesFocus);
+ mBounds.dumpDebug(proto, BOUNDS);
+ proto.write(WINDOW_STATE, mWindowState);
+ proto.end(token);
+ }
}
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index e21cb44f72d8..5877f1f4e269 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -22,9 +22,10 @@ import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.net.util.IpUtils;
import android.util.Log;
+import com.android.net.module.util.IpUtils;
+
import java.net.InetAddress;
/**
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index 22288b6205d7..c4f8fc281f25 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -22,11 +22,12 @@ import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.system.OsConstants;
+import com.android.net.module.util.IpUtils;
+
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
index 75086cf82b57..d31218d9b67b 100644
--- a/core/java/android/net/NetworkProvider.java
+++ b/core/java/android/net/NetworkProvider.java
@@ -30,7 +30,7 @@ import android.util.Log;
/**
* Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
- * to networks and makes them available to to the core network stack by creating
+ * to networks and makes them available to the core network stack by creating
* {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
* with via networking APIs such as {@link ConnectivityManager}.
*
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index a190c473f0a0..0ba266345a60 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -511,18 +511,26 @@ public class NetworkScoreManager {
@Override
public void updateScores(@NonNull List<ScoredNetwork> networks) {
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> {
- mCallback.onScoresUpdated(networks);
- });
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onScoresUpdated(networks);
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void clearScores() {
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> {
- mCallback.onScoresInvalidated();
- });
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ mCallback.onScoresInvalidated();
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
}
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 160259e39813..6ef496b1f6fe 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -22,10 +22,14 @@ import android.annotation.SystemApi;
/**
* Describes specific properties of a requested network for use in a {@link NetworkRequest}.
*
- * Applications cannot instantiate this class by themselves, but can obtain instances of
- * subclasses of this class via other APIs.
+ * This as an abstract class. Applications shouldn't instantiate this class by themselves, but can
+ * obtain instances of subclasses of this class via other APIs.
*/
public abstract class NetworkSpecifier {
+ /**
+ * Create a placeholder object. Please use subclasses of this class in a {@link NetworkRequest}
+ * to request a network.
+ */
public NetworkSpecifier() {}
/**
diff --git a/core/java/android/net/util/IpUtils.java b/core/java/android/net/util/IpUtils.java
deleted file mode 100644
index e037c4035aca..000000000000
--- a/core/java/android/net/util/IpUtils.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.nio.BufferOverflowException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ShortBuffer;
-
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-
-/**
- * @hide
- */
-public class IpUtils {
- /**
- * Converts a signed short value to an unsigned int value. Needed
- * because Java does not have unsigned types.
- */
- private static int intAbs(short v) {
- return v & 0xFFFF;
- }
-
- /**
- * Performs an IP checksum (used in IP header and across UDP
- * payload) on the specified portion of a ByteBuffer. The seed
- * allows the checksum to commence with a specified value.
- */
- private static int checksum(ByteBuffer buf, int seed, int start, int end) {
- int sum = seed;
- final int bufPosition = buf.position();
-
- // set position of original ByteBuffer, so that the ShortBuffer
- // will be correctly initialized
- buf.position(start);
- ShortBuffer shortBuf = buf.asShortBuffer();
-
- // re-set ByteBuffer position
- buf.position(bufPosition);
-
- final int numShorts = (end - start) / 2;
- for (int i = 0; i < numShorts; i++) {
- sum += intAbs(shortBuf.get(i));
- }
- start += numShorts * 2;
-
- // see if a singleton byte remains
- if (end != start) {
- short b = buf.get(start);
-
- // make it unsigned
- if (b < 0) {
- b += 256;
- }
-
- sum += b * 256;
- }
-
- sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
- sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
- int negated = ~sum;
- return intAbs((short) negated);
- }
-
- private static int pseudoChecksumIPv4(
- ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
- int partial = protocol + transportLen;
- partial += intAbs(buf.getShort(headerOffset + 12));
- partial += intAbs(buf.getShort(headerOffset + 14));
- partial += intAbs(buf.getShort(headerOffset + 16));
- partial += intAbs(buf.getShort(headerOffset + 18));
- return partial;
- }
-
- private static int pseudoChecksumIPv6(
- ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
- int partial = protocol + transportLen;
- for (int offset = 8; offset < 40; offset += 2) {
- partial += intAbs(buf.getShort(headerOffset + offset));
- }
- return partial;
- }
-
- private static byte ipversion(ByteBuffer buf, int headerOffset) {
- return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4);
- }
-
- public static short ipChecksum(ByteBuffer buf, int headerOffset) {
- byte ihl = (byte) (buf.get(headerOffset) & 0x0f);
- return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4);
- }
-
- private static short transportChecksum(ByteBuffer buf, int protocol,
- int ipOffset, int transportOffset, int transportLen) {
- if (transportLen < 0) {
- throw new IllegalArgumentException("Transport length < 0: " + transportLen);
- }
- int sum;
- byte ver = ipversion(buf, ipOffset);
- if (ver == 4) {
- sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen);
- } else if (ver == 6) {
- sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen);
- } else {
- throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6");
- }
-
- sum = checksum(buf, sum, transportOffset, transportOffset + transportLen);
- if (protocol == IPPROTO_UDP && sum == 0) {
- sum = (short) 0xffff;
- }
- return (short) sum;
- }
-
- public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) {
- int transportLen = intAbs(buf.getShort(transportOffset + 4));
- return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen);
- }
-
- public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset,
- int transportLen) {
- return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen);
- }
-
- public static String addressAndPortToString(InetAddress address, int port) {
- return String.format(
- (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
- address.getHostAddress(), port);
- }
-
- public static boolean isValidUdpOrTcpPort(int port) {
- return port > 0 && port < 65536;
- }
-}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index fdbf79a9f5d2..d889b155cf64 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -21,6 +21,7 @@ import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.job.JobParameters;
import android.compat.annotation.UnsupportedAppUsage;
@@ -2889,14 +2890,17 @@ public abstract class BatteryStats implements Parcelable {
/**
* Returns the approximate CPU time (in microseconds) spent by the system server handling
- * incoming service calls from apps.
+ * incoming service calls from apps. The result is returned as an array of longs,
+ * organized as a sequence like this:
+ * <pre>
+ * cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
+ * </pre>
*
- * @param cluster the index of the CPU cluster.
- * @param step the index of the CPU speed. This is not the actual speed of the CPU.
* @see com.android.internal.os.PowerProfile#getNumCpuClusters()
* @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
*/
- public abstract long getSystemServiceTimeAtCpuSpeed(int cluster, int step);
+ @Nullable
+ public abstract long[] getSystemServiceTimeAtCpuSpeeds();
/**
* Returns the total, last, or current battery uptime in microseconds.
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index bec96f98dbcd..d492e0870b2e 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -391,8 +391,8 @@ public class Binder implements IBinder {
* @hide
*/
public static final void withCleanCallingIdentity(@NonNull ThrowingRunnable action) {
- long callingIdentity = clearCallingIdentity();
Throwable throwableToPropagate = null;
+ final long callingIdentity = clearCallingIdentity();
try {
action.runOrThrow();
} catch (Throwable throwable) {
@@ -415,8 +415,8 @@ public class Binder implements IBinder {
* @hide
*/
public static final <T> T withCleanCallingIdentity(@NonNull ThrowingSupplier<T> action) {
- long callingIdentity = clearCallingIdentity();
Throwable throwableToPropagate = null;
+ final long callingIdentity = clearCallingIdentity();
try {
return action.getOrThrow();
} catch (Throwable throwable) {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1db544c29101..78ba7f0eec28 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -141,8 +141,8 @@ public class Build {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 4b2cfe222dd6..a2e53e29193c 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -2551,14 +2551,16 @@ public final class Debug
public static native long getZramFreeKb();
/**
- * Return memory size in kilobytes allocated for ION heaps.
+ * Return memory size in kilobytes allocated for ION heaps or -1 if
+ * /sys/kernel/ion/total_heaps_kb could not be read.
*
* @hide
*/
public static native long getIonHeapsSizeKb();
/**
- * Return memory size in kilobytes allocated for ION pools.
+ * Return memory size in kilobytes allocated for ION pools or -1 if
+ * /sys/kernel/ion/total_pools_kb could not be read.
*
* @hide
*/
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
index 21fd819f3d94..ab5637cbb878 100644
--- a/core/java/android/os/FileBridge.java
+++ b/core/java/android/os/FileBridge.java
@@ -16,7 +16,6 @@
package android.os;
-import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.SOCK_STREAM;
import android.system.ErrnoException;
@@ -58,17 +57,19 @@ public class FileBridge extends Thread {
/** CMD_CLOSE */
private static final int CMD_CLOSE = 3;
- private FileDescriptor mTarget;
+ private ParcelFileDescriptor mTarget;
- private final FileDescriptor mServer = new FileDescriptor();
- private final FileDescriptor mClient = new FileDescriptor();
+ private ParcelFileDescriptor mServer;
+ private ParcelFileDescriptor mClient;
private volatile boolean mClosed;
public FileBridge() {
try {
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
- } catch (ErrnoException e) {
+ ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair(SOCK_STREAM);
+ mServer = fds[0];
+ mClient = fds[1];
+ } catch (IOException e) {
throw new RuntimeException("Failed to create bridge");
}
}
@@ -80,15 +81,14 @@ public class FileBridge extends Thread {
public void forceClose() {
IoUtils.closeQuietly(mTarget);
IoUtils.closeQuietly(mServer);
- IoUtils.closeQuietly(mClient);
mClosed = true;
}
- public void setTargetFile(FileDescriptor target) {
+ public void setTargetFile(ParcelFileDescriptor target) {
mTarget = target;
}
- public FileDescriptor getClientSocket() {
+ public ParcelFileDescriptor getClientSocket() {
return mClient;
}
@@ -96,32 +96,33 @@ public class FileBridge extends Thread {
public void run() {
final byte[] temp = new byte[8192];
try {
- while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
+ while (IoBridge.read(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH) == MSG_LENGTH) {
final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
if (cmd == CMD_WRITE) {
// Shuttle data into local file
int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
while (len > 0) {
- int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
+ int n = IoBridge.read(mServer.getFileDescriptor(), temp, 0,
+ Math.min(temp.length, len));
if (n == -1) {
throw new IOException(
"Unexpected EOF; still expected " + len + " bytes");
}
- IoBridge.write(mTarget, temp, 0, n);
+ IoBridge.write(mTarget.getFileDescriptor(), temp, 0, n);
len -= n;
}
} else if (cmd == CMD_FSYNC) {
// Sync and echo back to confirm
- Os.fsync(mTarget);
- IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+ Os.fsync(mTarget.getFileDescriptor());
+ IoBridge.write(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH);
} else if (cmd == CMD_CLOSE) {
// Close and echo back to confirm
- Os.fsync(mTarget);
- Os.close(mTarget);
+ Os.fsync(mTarget.getFileDescriptor());
+ mTarget.close();
mClosed = true;
- IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+ IoBridge.write(mServer.getFileDescriptor(), temp, 0, MSG_LENGTH);
break;
}
}
@@ -143,17 +144,11 @@ public class FileBridge extends Thread {
mClient = clientPfd.getFileDescriptor();
}
- public FileBridgeOutputStream(FileDescriptor client) {
- mClientPfd = null;
- mClient = client;
- }
-
@Override
public void close() throws IOException {
try {
writeCommandAndBlock(CMD_CLOSE, "close()");
} finally {
- IoBridge.closeAndSignalBlockedThreads(mClient);
IoUtils.closeQuietly(mClientPfd);
}
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index df1f1b21eba3..6ba1627dde47 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -29,6 +29,7 @@ import android.content.pm.ResolveInfo;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@@ -43,11 +44,17 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-/** @hide */
+/**
+ * GraphicsEnvironment sets up necessary properties for the graphics environment of the
+ * application process.
+ * GraphicsEnvironment uses a bunch of settings global variables to determine the setup,
+ * the change of settings global variables will only take effect before setup() is called,
+ * and any subsequent change will not impact the current running processes.
+ *
+ * @hide
+ */
public class GraphicsEnvironment {
private static final GraphicsEnvironment sInstance = new GraphicsEnvironment();
@@ -64,27 +71,35 @@ public class GraphicsEnvironment {
private static final String SYSTEM_DRIVER_NAME = "system";
private static final String SYSTEM_DRIVER_VERSION_NAME = "";
private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+
+ // System properties related to updatable graphics drivers.
private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
+
+ // Metadata flags within the <application> tag in the AndroidManifest.xml file.
private static final String METADATA_DRIVER_BUILD_TIME =
"com.android.graphics.driver.build_time";
private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
"com.android.graphics.developerdriver.enable";
private static final String METADATA_INJECT_LAYERS_ENABLE =
"com.android.graphics.injectLayers.enable";
+
+ private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
+ private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+
+ // ANGLE related properties.
private static final String ANGLE_RULES_FILE = "a4a_rules.json";
private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
"android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
private static final String INTENT_KEY_A4A_TOAST_MESSAGE = "A4A Toast Message";
- private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
- private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
+
private static final int VULKAN_1_0 = 0x00400000;
private static final int VULKAN_1_1 = 0x00401000;
- // UPDATABLE_DRIVER_ALL_APPS
+ // Values for UPDATABLE_DRIVER_ALL_APPS
// 0: Default (Invalid values fallback to default as well)
// 1: All apps use updatable production driver
// 2: All apps use updatable prerelease driver
@@ -94,10 +109,21 @@ public class GraphicsEnvironment {
private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER = 2;
private static final int UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF = 3;
+ // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE
+ private static final int ANGLE_GL_DRIVER_ALL_ANGLE_ON = 1;
+ private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0;
+
+ // Values for GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES
+ private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
+ private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
+ private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
+
private ClassLoader mClassLoader;
private String mLibrarySearchPaths;
private String mLibraryPermittedPaths;
+ private int mAngleOptInIndex = -1;
+
/**
* Set up GraphicsEnvironment
*/
@@ -106,12 +132,15 @@ public class GraphicsEnvironment {
final String packageName = context.getPackageName();
final ApplicationInfo appInfoWithMetaData =
getAppInfoWithMetadata(context, pm, packageName);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
setupAngle(context, coreSettings, pm, packageName);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
@@ -122,49 +151,37 @@ public class GraphicsEnvironment {
}
/**
- * Hint for GraphicsEnvironment that an activity is launching on the process.
- * Then the app process is allowed to send stats to GpuStats module.
- */
- public static native void hintActivityLaunch();
-
- /**
* Query to determine if ANGLE should be used
*/
- public static boolean shouldUseAngle(Context context, Bundle coreSettings,
+ private boolean shouldUseAngle(Context context, Bundle coreSettings,
String packageName) {
- if (packageName.isEmpty()) {
- Log.v(TAG, "No package name available yet, ANGLE should not be used");
+ if (TextUtils.isEmpty(packageName)) {
+ Log.v(TAG, "No package name specified, ANGLE should not be used");
return false;
}
- final String devOptIn = getDriverForPkg(context, coreSettings, packageName);
- if (DEBUG) {
- Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
- + "set to: '" + devOptIn + "'");
- }
+ final String devOptIn = getDriverForPackage(context, coreSettings, packageName);
+ Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ + "set to: '" + devOptIn + "'");
- // We only want to use ANGLE if the app is allowlisted or the developer has
+ // We only want to use ANGLE if the app is in the allowlist, or the developer has
// explicitly chosen something other than default driver.
// The allowlist will be generated by the ANGLE APK at both boot time and
// ANGLE update time. It will only include apps mentioned in the rules file.
- final boolean allowlisted = checkAngleAllowlist(context, coreSettings, packageName);
- final boolean requested = devOptIn.equals(sDriverMap.get(OpenGlDriverChoice.ANGLE));
- final boolean useAngle = (allowlisted || requested);
- if (!useAngle) {
- return false;
- }
+ final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+ final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
- if (allowlisted) {
+ if (allowed) {
Log.v(TAG, "ANGLE allowlist includes " + packageName);
}
if (requested) {
Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
}
- return true;
+ return allowed || requested;
}
- private static int getVulkanVersion(PackageManager pm) {
+ private int getVulkanVersion(PackageManager pm) {
// PackageManager doesn't have an API to retrieve the version of a specific feature, and we
// need to avoid retrieving all system features here and looping through them.
if (pm.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_VERSION, VULKAN_1_1)) {
@@ -181,7 +198,7 @@ public class GraphicsEnvironment {
/**
* Check whether application is has set the manifest metadata for layer injection.
*/
- private static boolean canInjectLayers(ApplicationInfo ai) {
+ private boolean canInjectLayers(ApplicationInfo ai) {
return (ai.metaData != null && ai.metaData.getBoolean(METADATA_INJECT_LAYERS_ENABLE)
&& setInjectLayersPrSetDumpable());
}
@@ -239,7 +256,7 @@ public class GraphicsEnvironment {
/**
* Return the debug layer app's on-disk and in-APK lib directories
*/
- private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) {
+ private String getDebugLayerAppPaths(IPackageManager pm, String packageName) {
final ApplicationInfo appInfo;
try {
appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL,
@@ -249,6 +266,7 @@ public class GraphicsEnvironment {
}
if (appInfo == null) {
Log.w(TAG, "Debug layer app '" + packageName + "' not installed");
+ return "";
}
final String abi = chooseAbi(appInfo);
@@ -315,23 +333,6 @@ public class GraphicsEnvironment {
setLayerPaths(mClassLoader, layerPaths);
}
- enum OpenGlDriverChoice {
- DEFAULT,
- NATIVE,
- ANGLE
- }
-
- private static final Map<OpenGlDriverChoice, String> sDriverMap = buildMap();
- private static Map<OpenGlDriverChoice, String> buildMap() {
- final Map<OpenGlDriverChoice, String> map = new HashMap<>();
- map.put(OpenGlDriverChoice.DEFAULT, "default");
- map.put(OpenGlDriverChoice.ANGLE, "angle");
- map.put(OpenGlDriverChoice.NATIVE, "native");
-
- return map;
- }
-
-
private static List<String> getGlobalSettingsString(ContentResolver contentResolver,
Bundle bundle,
String globalSetting) {
@@ -353,11 +354,10 @@ public class GraphicsEnvironment {
return valueList;
}
- private static int getGlobalSettingsPkgIndex(String pkgName,
- List<String> globalSettingsDriverPkgs) {
- for (int pkgIndex = 0; pkgIndex < globalSettingsDriverPkgs.size(); pkgIndex++) {
- if (globalSettingsDriverPkgs.get(pkgIndex).equals(pkgName)) {
- return pkgIndex;
+ private static int getPackageIndex(String packageName, List<String> packages) {
+ for (int idx = 0; idx < packages.size(); idx++) {
+ if (packages.get(idx).equals(packageName)) {
+ return idx;
}
}
@@ -378,50 +378,54 @@ public class GraphicsEnvironment {
return ai;
}
- private static String getDriverForPkg(Context context, Bundle bundle, String packageName) {
- final String allUseAngle;
+ private String getDriverForPackage(Context context, Bundle bundle, String packageName) {
+ final int allUseAngle;
if (bundle != null) {
allUseAngle =
- bundle.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+ bundle.getInt(Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
} else {
ContentResolver contentResolver = context.getContentResolver();
- allUseAngle = Settings.Global.getString(contentResolver,
- Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE);
+ allUseAngle = Settings.Global.getInt(contentResolver,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE,
+ ANGLE_GL_DRIVER_ALL_ANGLE_OFF);
}
- if ((allUseAngle != null) && allUseAngle.equals("1")) {
- return sDriverMap.get(OpenGlDriverChoice.ANGLE);
+ if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
+ Log.v(TAG, "Turn on ANGLE for all applications.");
+ return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ }
+
+ // Make sure we have a good package name
+ if (TextUtils.isEmpty(packageName)) {
+ return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
}
final ContentResolver contentResolver = context.getContentResolver();
- final List<String> globalSettingsDriverPkgs =
+ final List<String> optInPackages =
getGlobalSettingsString(contentResolver, bundle,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS);
- final List<String> globalSettingsDriverValues =
+ final List<String> optInValues =
getGlobalSettingsString(contentResolver, bundle,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES);
- // Make sure we have a good package name
- if ((packageName == null) || (packageName.isEmpty())) {
- return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
- }
// Make sure we have good settings to use
- if (globalSettingsDriverPkgs.size() != globalSettingsDriverValues.size()) {
+ if (optInPackages.size() != optInValues.size()) {
Log.w(TAG,
"Global.Settings values are invalid: "
- + "globalSettingsDriverPkgs.size = "
- + globalSettingsDriverPkgs.size() + ", "
- + "globalSettingsDriverValues.size = "
- + globalSettingsDriverValues.size());
- return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+ + "number of packages: "
+ + optInPackages.size() + ", "
+ + "number of values: "
+ + optInValues.size());
+ return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
}
- final int pkgIndex = getGlobalSettingsPkgIndex(packageName, globalSettingsDriverPkgs);
+ final int pkgIndex = getPackageIndex(packageName, optInPackages);
if (pkgIndex < 0) {
- return sDriverMap.get(OpenGlDriverChoice.DEFAULT);
+ return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
}
+ mAngleOptInIndex = pkgIndex;
- return globalSettingsDriverValues.get(pkgIndex);
+ return optInValues.get(pkgIndex);
}
/**
@@ -446,27 +450,28 @@ public class GraphicsEnvironment {
}
/**
- * Check for ANGLE debug package, but only for apps that can load them (dumpable)
+ * Check for ANGLE debug package, but only for apps that can load them.
+ * An application can load ANGLE debug package if it is a debuggable application, or
+ * the device is debuggable.
*/
private String getAngleDebugPackage(Context context, Bundle coreSettings) {
- if (isDebuggable()) {
- String debugPackage;
-
- if (coreSettings != null) {
- debugPackage =
- coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
- } else {
- ContentResolver contentResolver = context.getContentResolver();
- debugPackage = Settings.Global.getString(contentResolver,
- Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
- }
-
- if ((debugPackage != null) && (!debugPackage.isEmpty())) {
- return debugPackage;
- }
+ if (!isDebuggable()) {
+ return "";
}
+ final String debugPackage;
- return "";
+ if (coreSettings != null) {
+ debugPackage =
+ coreSettings.getString(Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+ } else {
+ ContentResolver contentResolver = context.getContentResolver();
+ debugPackage = Settings.Global.getString(contentResolver,
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE);
+ }
+ if (TextUtils.isEmpty(debugPackage)) {
+ return "";
+ }
+ return debugPackage;
}
/**
@@ -474,7 +479,7 @@ public class GraphicsEnvironment {
* True: Temporary rules file was loaded.
* False: Temporary rules file was *not* loaded.
*/
- private static boolean setupAngleWithTempRulesFile(Context context,
+ private boolean setupAngleWithTempRulesFile(Context context,
String packageName,
String paths,
String devOptIn) {
@@ -491,7 +496,7 @@ public class GraphicsEnvironment {
final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
- if ((angleTempRules == null) || angleTempRules.isEmpty()) {
+ if (TextUtils.isEmpty(angleTempRules)) {
Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
return false;
}
@@ -510,7 +515,8 @@ public class GraphicsEnvironment {
final long rulesLength = stream.getChannel().size();
Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
- setAngleInfo(paths, packageName, devOptIn, rulesFd, rulesOffset, rulesLength);
+ setAngleInfo(paths, packageName, devOptIn, null,
+ rulesFd, rulesOffset, rulesLength);
stream.close();
@@ -534,12 +540,13 @@ public class GraphicsEnvironment {
* True: APK rules file was loaded.
* False: APK rules file was *not* loaded.
*/
- private static boolean setupAngleRulesApk(String anglePkgName,
+ private boolean setupAngleRulesApk(String anglePkgName,
ApplicationInfo angleInfo,
PackageManager pm,
String packageName,
String paths,
- String devOptIn) {
+ String devOptIn,
+ String[] features) {
// Pass the rules file to loader for ANGLE decisions
try {
final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
@@ -547,7 +554,7 @@ public class GraphicsEnvironment {
try {
final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
- setAngleInfo(paths, packageName, devOptIn, assetsFd.getFileDescriptor(),
+ setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(),
assetsFd.getStartOffset(), assetsFd.getLength());
assetsFd.close();
@@ -567,7 +574,7 @@ public class GraphicsEnvironment {
/**
* Pull ANGLE allowlist from GlobalSettings and compare against current package
*/
- private static boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
+ private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
final ContentResolver contentResolver = context.getContentResolver();
final List<String> angleAllowlist =
getGlobalSettingsString(contentResolver, bundle,
@@ -583,11 +590,12 @@ public class GraphicsEnvironment {
*
* @param context
* @param bundle
- * @param packageName
+ * @param pm
+ * @param packageName - package name of the application.
* @return true: ANGLE setup successfully
* false: ANGLE not setup (not on allowlist, ANGLE not present, etc.)
*/
- public boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
+ private boolean setupAngle(Context context, Bundle bundle, PackageManager pm,
String packageName) {
if (!shouldUseAngle(context, bundle, packageName)) {
@@ -612,18 +620,18 @@ public class GraphicsEnvironment {
// Otherwise, check to see if ANGLE is properly installed
if (angleInfo == null) {
anglePkgName = getAnglePackageName(pm);
- if (!anglePkgName.isEmpty()) {
- Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
- try {
- // Production ANGLE libraries must be pre-installed as a system app
- angleInfo = pm.getApplicationInfo(anglePkgName,
- PackageManager.MATCH_SYSTEM_ONLY);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
- return false;
- }
- } else {
- Log.e(TAG, "Failed to find ANGLE package.");
+ if (TextUtils.isEmpty(anglePkgName)) {
+ Log.w(TAG, "Failed to find ANGLE package.");
+ return false;
+ }
+
+ Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
+ try {
+ // Production ANGLE libraries must be pre-installed as a system app
+ angleInfo = pm.getApplicationInfo(anglePkgName,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
return false;
}
}
@@ -645,15 +653,18 @@ public class GraphicsEnvironment {
// load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
// and can confidently answer yes/no based on the previously set developer
// option value.
- final String devOptIn = getDriverForPkg(context, bundle, packageName);
+ final String devOptIn = getDriverForPackage(context, bundle, packageName);
if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
// We setup ANGLE with a temp rules file, so we're done here.
return true;
}
- if (setupAngleRulesApk(anglePkgName, angleInfo, pm, packageName, paths, devOptIn)) {
- // We setup ANGLE with rules from the APK, so we're done here.
+ String[] features = getAngleEglFeatures(context, bundle);
+
+ if (setupAngleRulesApk(
+ anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) {
+ // ANGLE with rules is set up from the APK, hence return.
return true;
}
@@ -719,10 +730,23 @@ public class GraphicsEnvironment {
}
}
+ private String[] getAngleEglFeatures(Context context, Bundle coreSettings) {
+ if (mAngleOptInIndex < 0) {
+ return null;
+ }
+
+ final List<String> featuresLists = getGlobalSettingsString(
+ context.getContentResolver(), coreSettings, Settings.Global.ANGLE_EGL_FEATURES);
+ if (featuresLists.size() <= mAngleOptInIndex) {
+ return null;
+ }
+ return featuresLists.get(mAngleOptInIndex).split(":");
+ }
+
/**
* Return the driver package name to use. Return null for system driver.
*/
- private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
+ private String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
final String productionDriver = SystemProperties.get(PROPERTY_GFX_DRIVER_PRODUCTION);
final boolean hasProductionDriver = productionDriver != null && !productionDriver.isEmpty();
@@ -730,18 +754,18 @@ public class GraphicsEnvironment {
final boolean hasPrereleaseDriver = prereleaseDriver != null && !prereleaseDriver.isEmpty();
if (!hasProductionDriver && !hasPrereleaseDriver) {
- if (DEBUG) {
- Log.v(TAG,
- "Neither updatable production driver nor prerelease driver is supported.");
- }
+ Log.v(TAG, "Neither updatable production driver nor prerelease driver is supported.");
return null;
}
- // To minimize risk of driver updates crippling the device beyond user repair, never use an
- // updated driver for privileged or non-updated system apps. Presumably pre-installed apps
- // were tested thoroughly with the pre-installed driver.
+ // To minimize risk of driver updates crippling the device beyond user repair, never use the
+ // updatable drivers for privileged or non-updated system apps. Presumably pre-installed
+ // apps were tested thoroughly with the system driver.
if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
- if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app.");
+ if (DEBUG) {
+ Log.v(TAG,
+ "Ignore updatable driver package for privileged/non-updated system app.");
+ }
return null;
}
@@ -758,13 +782,13 @@ public class GraphicsEnvironment {
// 6. UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST
switch (coreSettings.getInt(Settings.Global.UPDATABLE_DRIVER_ALL_APPS, 0)) {
case UPDATABLE_DRIVER_GLOBAL_OPT_IN_OFF:
- if (DEBUG) Log.v(TAG, "updatable driver is turned off on this device.");
+ Log.v(TAG, "The updatable driver is turned off on this device.");
return null;
case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRODUCTION_DRIVER:
- if (DEBUG) Log.v(TAG, "All apps opt in to use updatable production driver.");
+ Log.v(TAG, "All apps opt in to use updatable production driver.");
return hasProductionDriver ? productionDriver : null;
case UPDATABLE_DRIVER_GLOBAL_OPT_IN_PRERELEASE_DRIVER:
- if (DEBUG) Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
+ Log.v(TAG, "All apps opt in to use updatable prerelease driver.");
return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
case UPDATABLE_DRIVER_GLOBAL_OPT_IN_DEFAULT:
default:
@@ -775,20 +799,20 @@ public class GraphicsEnvironment {
if (getGlobalSettingsString(null, coreSettings,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_OUT_APPS)
.contains(appPackageName)) {
- if (DEBUG) Log.v(TAG, "App opts out for updatable production driver.");
+ Log.v(TAG, "App opts out for updatable production driver.");
return null;
}
if (getGlobalSettingsString(
null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS)
.contains(appPackageName)) {
- if (DEBUG) Log.v(TAG, "App opts in for updatable prerelease driver.");
+ Log.v(TAG, "App opts in for updatable prerelease driver.");
return hasPrereleaseDriver && enablePrereleaseDriver ? prereleaseDriver : null;
}
// Early return here since the rest logic is only for updatable production Driver.
if (!hasProductionDriver) {
- if (DEBUG) Log.v(TAG, "Updatable production driver is not supported on the device.");
+ Log.v(TAG, "Updatable production driver is not supported on the device.");
return null;
}
@@ -801,7 +825,7 @@ public class GraphicsEnvironment {
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST);
if (!isOptIn && allowlist.indexOf(UPDATABLE_DRIVER_ALLOWLIST_ALL) != 0
&& !allowlist.contains(appPackageName)) {
- if (DEBUG) Log.v(TAG, "App is not on the allowlist for updatable production driver.");
+ Log.v(TAG, "App is not on the allowlist for updatable production driver.");
return null;
}
@@ -811,7 +835,7 @@ public class GraphicsEnvironment {
&& getGlobalSettingsString(
null, coreSettings, Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST)
.contains(appPackageName)) {
- if (DEBUG) Log.v(TAG, "App is on the denylist for updatable production driver.");
+ Log.v(TAG, "App is on the denylist for updatable production driver.");
return null;
}
@@ -821,7 +845,7 @@ public class GraphicsEnvironment {
/**
* Choose whether the current process should use the builtin or an updated driver.
*/
- private static boolean chooseDriver(
+ private boolean chooseDriver(
Context context, Bundle coreSettings, PackageManager pm, String packageName,
ApplicationInfo ai) {
final String driverPackageName = chooseDriverInternal(coreSettings, ai);
@@ -834,7 +858,7 @@ public class GraphicsEnvironment {
driverPackageInfo = pm.getPackageInfo(driverPackageName,
PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "driver package '" + driverPackageName + "' not installed");
+ Log.w(TAG, "updatable driver package '" + driverPackageName + "' not installed");
return false;
}
@@ -843,7 +867,7 @@ public class GraphicsEnvironment {
final ApplicationInfo driverAppInfo = driverPackageInfo.applicationInfo;
if (driverAppInfo.targetSdkVersion < Build.VERSION_CODES.O) {
if (DEBUG) {
- Log.w(TAG, "updated driver package is not known to be compatible with O");
+ Log.w(TAG, "updatable driver package is not compatible with O");
}
return false;
}
@@ -853,7 +877,7 @@ public class GraphicsEnvironment {
if (DEBUG) {
// This is the normal case for the pre-installed empty driver package, don't spam
if (driverAppInfo.isUpdatedSystemApp()) {
- Log.w(TAG, "updated driver package has no compatible native libraries");
+ Log.w(TAG, "Updatable driver package has no compatible native libraries");
}
}
return false;
@@ -867,11 +891,8 @@ public class GraphicsEnvironment {
.append(abi);
final String paths = sb.toString();
final String sphalLibraries = getSphalLibraries(context, driverPackageName);
- if (DEBUG) {
- Log.v(TAG,
- "gfx driver package search path: " + paths
- + ", required sphal libraries: " + sphalLibraries);
- }
+ Log.v(TAG, "Updatable driver package search path: " + paths
+ + ", required sphal libraries: " + sphalLibraries);
setDriverPathAndSphalLibraries(paths, sphalLibraries);
if (driverAppInfo.metaData == null) {
@@ -880,7 +901,7 @@ public class GraphicsEnvironment {
String driverBuildTime = driverAppInfo.metaData.getString(METADATA_DRIVER_BUILD_TIME);
if (driverBuildTime == null || driverBuildTime.length() <= 1) {
- Log.v(TAG, "com.android.graphics.driver.build_time is not set");
+ Log.w(TAG, "com.android.graphics.driver.build_time is not set");
driverBuildTime = "L0";
}
// driver_build_time in the meta-data is in "L<Unix epoch timestamp>" format. e.g. L123456.
@@ -904,7 +925,7 @@ public class GraphicsEnvironment {
return null;
}
- private static String getSphalLibraries(Context context, String driverPackageName) {
+ private String getSphalLibraries(Context context, String driverPackageName) {
try {
final Context driverContext =
context.createPackageContext(driverPackageName, Context.CONTEXT_RESTRICTED);
@@ -935,7 +956,13 @@ public class GraphicsEnvironment {
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
private static native void setAngleInfo(String path, String appPackage, String devOptIn,
- FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+ String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength);
private static native boolean getShouldUseAngle(String packageName);
private static native boolean setInjectLayersPrSetDumpable();
+
+ /**
+ * Hint for GraphicsEnvironment that an activity is launching on the process.
+ * Then the app process is allowed to send stats to GpuStats module.
+ */
+ public static native void hintActivityLaunch();
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index a92d91bdefc5..e996809f1299 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -98,6 +98,8 @@ interface IPowerManager
boolean isAmbientDisplaySuppressedForToken(String token);
// returns whether ambient display is suppressed by any app with any token.
boolean isAmbientDisplaySuppressed();
+ // returns whether ambient display is suppressed by the given app with the given token.
+ boolean isAmbientDisplaySuppressedForTokenByApp(String token, int appUid);
// Forces the system to suspend even if there are held wakelocks.
boolean forceSuspend();
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
new file mode 100644
index 000000000000..e821e3194d21
--- /dev/null
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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;
+
+import android.os.VibrationAttributes;
+
+/** {@hide} */
+interface IVibratorManagerService {
+ int[] getVibratorIds();
+}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index b05ea39850f8..c39fd4d1bc43 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -155,6 +155,7 @@ public final class Looper {
/**
* Poll and deliver single message, return true if the outer loop should continue.
*/
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
@@ -255,6 +256,7 @@ public final class Looper {
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
final Looper me = myLooper();
if (me == null) {
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 6d8ab6d70cdf..6bb016519a84 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -16,8 +16,6 @@
package android.os;
-import android.media.AudioAttributes;
-
/**
* Vibrator implementation that does nothing.
*
@@ -50,7 +48,7 @@ public class NullVibrator extends Vibrator {
@Override
public void vibrate(int uid, String opPkg, VibrationEffect effect,
- String reason, AudioAttributes attributes) {
+ String reason, VibrationAttributes attributes) {
}
@Override
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 40c291f14b67..91eb2a502638 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -3,6 +3,7 @@ per-file ExternalVibration.aidl = michaelwr@google.com
per-file ExternalVibration.java = michaelwr@google.com
per-file IExternalVibrationController.aidl = michaelwr@google.com
per-file IExternalVibratorService.aidl = michaelwr@google.com
+per-file IVibratorManagerService.aidl = michaelwr@google.com
per-file IVibratorService.aidl = michaelwr@google.com
per-file NullVibrator.java = michaelwr@google.com
per-file SystemVibrator.java = michaelwr@google.com
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 1bddc49f839c..13d5f6a9c9d7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -673,11 +673,11 @@ public final class Parcel {
* {@link #dataPosition}. This is used to validate that the marshalled
* transaction is intended for the target interface.
*/
- public final void writeInterfaceToken(String interfaceName) {
+ public final void writeInterfaceToken(@NonNull String interfaceName) {
nativeWriteInterfaceToken(mNativePtr, interfaceName);
}
- public final void enforceInterface(String interfaceName) {
+ public final void enforceInterface(@NonNull String interfaceName) {
nativeEnforceInterface(mNativePtr, interfaceName);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 3265829e5061..50f0c28cb8f8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -2191,6 +2191,27 @@ public final class PowerManager {
}
/**
+ * Returns true if ambient display is suppressed by the given {@code appUid} with the given
+ * {@code token}.
+ *
+ * <p>This method will return false if {@link #isAmbientDisplayAvailable()} is false.
+ *
+ * @param token The identifier of the ambient display suppression.
+ * @param appUid The uid of the app that suppressed ambient display.
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.READ_DREAM_STATE,
+ android.Manifest.permission.READ_DREAM_SUPPRESSION })
+ public boolean isAmbientDisplaySuppressedForTokenByApp(@NonNull String token, int appUid) {
+ try {
+ return mService.isAmbientDisplaySuppressedForTokenByApp(token, appUid);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the reason the phone was last shutdown. Calling app must have the
* {@link android.Manifest.permission#DEVICE_POWER} permission to request this information.
* @return Reason for shutdown as an int, {@link #SHUTDOWN_REASON_UNKNOWN} if the file could
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1d957fcf1c9d..65da31825e6a 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -852,12 +852,11 @@ public class Process {
/**
* Set the priority of a thread, based on Linux priorities.
- *
- * @param tid The identifier of the thread/process to change. It should be
- * the native thread id but not the managed id of {@link java.lang.Thread}.
+ *
+ * @param tid The identifier of the thread/process to change.
* @param priority A Linux priority level, from -20 for highest scheduling
* priority to 19 for lowest scheduling priority.
- *
+ *
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
* @throws SecurityException Throws SecurityException if your process does
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index b654707a683b..35e7bad83736 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -235,6 +235,21 @@ public final class ServiceManager {
}
/**
+ * Returns the list of declared instances for an interface.
+ *
+ * @return true if the service is declared somewhere (eg. VINTF manifest) and
+ * waitForService should always be able to return the service.
+ */
+ public static String[] getDeclaredInstances(@NonNull String iface) {
+ try {
+ return getIServiceManager().getDeclaredInstances(iface);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in getDeclaredInstances", e);
+ return null;
+ }
+ }
+
+ /**
* Returns the specified service from the service manager.
*
* If the service is not running, servicemanager will attempt to start it, and this function
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 91b56fbbc38e..b70b6b5d209e 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -90,6 +90,10 @@ class ServiceManagerProxy implements IServiceManager {
return mServiceManager.isDeclared(name);
}
+ public String[] getDeclaredInstances(String iface) throws RemoteException {
+ return mServiceManager.getDeclaredInstances(iface);
+ }
+
public void registerClientCallback(String name, IBinder service, IClientCallback cb)
throws RemoteException {
throw new RemoteException();
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 2dba8dce62da..5c9067ac3c3e 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -221,18 +221,14 @@ public class SystemVibrator extends Vibrator {
}
@Override
- public void vibrate(int uid, String opPkg, VibrationEffect effect,
- String reason, AudioAttributes attributes) {
+ public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
+ String reason, @NonNull VibrationAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- if (attributes == null) {
- attributes = new AudioAttributes.Builder().build();
- }
- VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
- mService.vibrate(uid, opPkg, effect, atr, reason, mToken);
+ mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 8a7cf608cfb8..d77861fb71b2 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -140,13 +140,12 @@ public final class VibrationAttributes implements Parcelable {
private final int mUsage;
private final int mFlags;
+ private final int mOriginalAudioUsage;
- private final AudioAttributes mAudioAttributes;
-
- private VibrationAttributes(int usage, int flags, @NonNull AudioAttributes audio) {
+ private VibrationAttributes(int usage, int audioUsage, int flags) {
mUsage = usage;
+ mOriginalAudioUsage = audioUsage;
mFlags = flags & FLAG_ALL_SUPPORTED;
- mAudioAttributes = audio;
}
/**
@@ -182,14 +181,31 @@ public final class VibrationAttributes implements Parcelable {
}
/**
- * Return AudioAttributes equivalent to this VibrationAttributes.
- * @deprecated Temporary support of AudioAttributes, will be removed when out of WIP
+ * Return {@link AudioAttributes} usage equivalent to {@link #getUsage()}.
+ * @return one of {@link AudioAttributes#SDK_USAGES} that represents {@link #getUsage()}
* @hide
*/
- @Deprecated
@TestApi
- public @NonNull AudioAttributes getAudioAttributes() {
- return mAudioAttributes;
+ public int getAudioUsage() {
+ if (mOriginalAudioUsage != AudioAttributes.USAGE_UNKNOWN) {
+ // Return same audio usage set in the Builder.
+ return mOriginalAudioUsage;
+ }
+ // Return correct audio usage based on the vibration usage set in the Builder.
+ switch (mUsage) {
+ case USAGE_NOTIFICATION:
+ return AudioAttributes.USAGE_NOTIFICATION;
+ case USAGE_COMMUNICATION_REQUEST:
+ return AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
+ case USAGE_RINGTONE:
+ return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+ case USAGE_TOUCH:
+ return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+ case USAGE_ALARM:
+ return AudioAttributes.USAGE_ALARM;
+ default:
+ return AudioAttributes.USAGE_UNKNOWN;
+ }
}
@Override
@@ -200,15 +216,14 @@ public final class VibrationAttributes implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mUsage);
+ dest.writeInt(mOriginalAudioUsage);
dest.writeInt(mFlags);
- dest.writeParcelable(mAudioAttributes, flags);
}
private VibrationAttributes(Parcel src) {
mUsage = src.readInt();
+ mOriginalAudioUsage = src.readInt();
mFlags = src.readInt();
- mAudioAttributes = (AudioAttributes) src.readParcelable(
- AudioAttributes.class.getClassLoader());
}
public static final @NonNull Parcelable.Creator<VibrationAttributes>
@@ -230,18 +245,20 @@ public final class VibrationAttributes implements Parcelable {
return false;
}
VibrationAttributes rhs = (VibrationAttributes) o;
- return mUsage == rhs.mUsage && mFlags == rhs.mFlags;
+ return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
+ && mFlags == rhs.mFlags;
}
@Override
public int hashCode() {
- return Objects.hash(mUsage, mFlags);
+ return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
}
@Override
public String toString() {
return "VibrationAttributes:"
+ " Usage=" + usageToString()
+ + " Audio Usage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+ " Flags=" + mFlags;
}
@@ -280,10 +297,9 @@ public final class VibrationAttributes implements Parcelable {
*/
public static final class Builder {
private int mUsage = USAGE_UNKNOWN;
+ private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
private int mFlags = 0x0;
- private AudioAttributes mAudioAttributes = new AudioAttributes.Builder().build();
-
/**
* Constructs a new Builder with the defaults.
*/
@@ -296,8 +312,8 @@ public final class VibrationAttributes implements Parcelable {
public Builder(@Nullable VibrationAttributes vib) {
if (vib != null) {
mUsage = vib.mUsage;
+ mOriginalAudioUsage = vib.mOriginalAudioUsage;
mFlags = vib.mFlags;
- mAudioAttributes = vib.mAudioAttributes;
}
}
@@ -306,9 +322,7 @@ public final class VibrationAttributes implements Parcelable {
* @hide
*/
@TestApi
- public Builder(@NonNull AudioAttributes audio,
- @Nullable VibrationEffect effect) {
- mAudioAttributes = audio;
+ public Builder(@NonNull AudioAttributes audio, @Nullable VibrationEffect effect) {
setUsage(audio);
setFlags(audio);
applyHapticFeedbackHeuristics(effect);
@@ -342,6 +356,7 @@ public final class VibrationAttributes implements Parcelable {
}
private void setUsage(@NonNull AudioAttributes audio) {
+ mOriginalAudioUsage = audio.getUsage();
switch (audio.getUsage()) {
case AudioAttributes.USAGE_NOTIFICATION:
case AudioAttributes.USAGE_NOTIFICATION_EVENT:
@@ -379,8 +394,7 @@ public final class VibrationAttributes implements Parcelable {
* @return a new {@link VibrationAttributes} object
*/
public @NonNull VibrationAttributes build() {
- VibrationAttributes ans = new VibrationAttributes(mUsage, mFlags,
- mAudioAttributes);
+ VibrationAttributes ans = new VibrationAttributes(mUsage, mOriginalAudioUsage, mFlags);
return ans;
}
@@ -396,6 +410,7 @@ public final class VibrationAttributes implements Parcelable {
* @return the same Builder instance.
*/
public @NonNull Builder setUsage(int usage) {
+ mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
mUsage = usage;
return this;
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 86d009e8607e..0c0e6b5d73a6 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -344,8 +344,24 @@ public abstract class Vibrator {
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
- public abstract void vibrate(int uid, String opPkg, VibrationEffect vibe,
- String reason, AudioAttributes attributes);
+ public final void vibrate(int uid, String opPkg, VibrationEffect vibe,
+ String reason, AudioAttributes attributes) {
+ if (attributes == null) {
+ attributes = new AudioAttributes.Builder().build();
+ }
+ VibrationAttributes attr = new VibrationAttributes.Builder(attributes, vibe).build();
+ vibrate(uid, opPkg, vibe, reason, attr);
+ }
+
+ /**
+ * Like {@link #vibrate(int, String, VibrationEffect, String, AudioAttributes)}, but allows the
+ * caller to specify {@link VibrationAttributes} instead of {@link AudioAttributes}.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public abstract void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe,
+ String reason, @NonNull VibrationAttributes attributes);
/**
* Query whether the vibrator supports the given effects.
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 9e332e9b0456..06203ff15094 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -657,16 +657,8 @@ public class ZygoteProcess {
argsForZygote.add("--runtime-flags=" + runtimeFlags);
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
argsForZygote.add("--mount-external-default");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
- argsForZygote.add("--mount-external-read");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
- argsForZygote.add("--mount-external-write");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {
- argsForZygote.add("--mount-external-full");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
argsForZygote.add("--mount-external-installer");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_LEGACY) {
- argsForZygote.add("--mount-external-legacy");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
argsForZygote.add("--mount-external-pass-through");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
index 121fd333d17f..fc17002ba056 100644
--- a/core/java/android/os/connectivity/CellularBatteryStats.java
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -109,7 +109,7 @@ public final class CellularBatteryStats implements Parcelable {
CellSignalStrength.getNumSignalStrengthLevels()));
mTxTimeMs = Arrays.copyOfRange(
txTimeMs, 0,
- Math.min(txTimeMs.length, ModemActivityInfo.TX_POWER_LEVELS));
+ Math.min(txTimeMs.length, ModemActivityInfo.getNumTxPowerLevels()));
mMonitoredRailChargeConsumedMaMs = monitoredRailChargeConsumedMaMs;
}
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index ad9a16231f78..52475e9cd89d 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -69,7 +69,7 @@ interface IIncrementalService {
/**
* Creates a file under a storage.
*/
- int makeFile(int storageId, in @utf8InCpp String path, in IncrementalNewFileParams params);
+ int makeFile(int storageId, in @utf8InCpp String path, in IncrementalNewFileParams params, in @nullable byte[] content);
/**
* Creates a file under a storage. Content of the file is from a range inside another file.
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index dcbbd712914a..284c2df2ee7b 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -42,6 +42,7 @@ import android.text.TextUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
+import java.util.UUID;
/**
* This class manages storage instances used during a package installation session.
@@ -139,7 +140,7 @@ public final class IncrementalFileStorages {
final String apkName = apk.name;
final File targetFile = new File(mStageDir, apkName);
if (!targetFile.exists()) {
- mDefaultStorage.makeFile(apkName, apk.size, null, apk.metadata, apk.signature);
+ mDefaultStorage.makeFile(apkName, apk.size, null, apk.metadata, apk.signature, null);
}
}
@@ -153,6 +154,13 @@ public final class IncrementalFileStorages {
}
/**
+ * Creates file in default storage and sets its content.
+ */
+ public void makeFile(@NonNull String name, @NonNull byte[] content) throws IOException {
+ mDefaultStorage.makeFile(name, content.length, UUID.randomUUID(), null, null, content);
+ }
+
+ /**
* Permanently disables readlogs.
*/
public void disableReadLogs() {
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index f83541293330..a1c3cc697e02 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -170,9 +170,10 @@ public final class IncrementalStorage {
* @param size Size of the new file in bytes.
* @param metadata Metadata bytes.
* @param v4signatureBytes Serialized V4SignatureProto.
+ * @param content Optionally set file content.
*/
public void makeFile(@NonNull String path, long size, @Nullable UUID id,
- @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes)
+ @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes, @Nullable byte[] content)
throws IOException {
try {
if (id == null && metadata == null) {
@@ -184,7 +185,7 @@ public final class IncrementalStorage {
params.metadata = (metadata == null ? new byte[0] : metadata);
params.fileId = idToBytes(id);
params.signature = v4signatureBytes;
- int res = mService.makeFile(mId, path, params);
+ int res = mService.makeFile(mId, path, params, content);
if (res != 0) {
throw new IOException("makeFile() failed with errno " + -res);
}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index a79a1cfcd64f..4379ce1055ef 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -70,13 +70,6 @@ public abstract class StorageManagerInternal {
public abstract void addExternalStoragePolicy(ExternalStorageMountPolicy policy);
/**
- * Notify the mount service that the mount policy for a UID changed.
- * @param uid The UID for which policy changed.
- * @param packageName The package in the UID for making the call.
- */
- public abstract void onExternalStoragePolicyChanged(int uid, String packageName);
-
- /**
* Gets the mount mode to use for a given UID as determined by consultin all
* policies.
*
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 17a78a8f301e..d6c95db95e85 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -308,7 +308,7 @@ public final class PermissionControllerManager {
revokeRuntimePermissionsResult);
return revokeRuntimePermissionsResult;
}).whenCompleteAsync((revoked, err) -> {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
Log.e(TAG, "Failure when revoking runtime permissions " + revoked, err);
@@ -358,7 +358,7 @@ public final class PermissionControllerManager {
setRuntimePermissionGrantStateResult);
return setRuntimePermissionGrantStateResult;
}).whenCompleteAsync((setRuntimePermissionGrantStateResult, err) -> {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
Log.e(TAG, "Error setting permissions state for device admin " + packageName,
@@ -477,7 +477,7 @@ public final class PermissionControllerManager {
applyStagedRuntimePermissionBackupResult);
return applyStagedRuntimePermissionBackupResult;
}).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (err != null) {
Log.e(TAG, "Error restoring delayed permissions for " + packageName, err);
@@ -623,7 +623,7 @@ public final class PermissionControllerManager {
Log.e(TAG, "Error getting permission usages", err);
callback.onPermissionUsageResult(Collections.emptyList());
} else {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
callback.onPermissionUsageResult(
CollectionUtils.emptyIfNull(getPermissionUsagesResult));
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index b15109e67086..d80a7e794220 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -34,7 +34,6 @@ import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.permission.SplitPermissionInfoParcelable;
-import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -544,15 +543,10 @@ public final class PermissionManager {
+ permission);
return PackageManager.PERMISSION_DENIED;
}
- // Clear Binder.callingUid in case this is called inside the system server. See
- // more extensive comment in checkPackageNamePermissionUncached
- long token = Binder.clearCallingIdentity();
try {
return am.checkPermission(permission, pid, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
@@ -685,20 +679,11 @@ public final class PermissionManager {
/* @hide */
private static int checkPackageNamePermissionUncached(
String permName, String pkgName, @UserIdInt int userId) {
- // Makeing the binder call "checkPermission" usually sets Binder.callingUid to the calling
- // processes UID. Hence clearing the calling UID is superflous.
- // If the call is inside the system server though "checkPermission" is not a binder all, it
- // is only a method call. Hence Binder.callingUid might still be set to the app that called
- // the system server. This can lead to problems as not every app can check the same
- // permissions the system server can check.
- long token = Binder.clearCallingIdentity();
try {
return ActivityThread.getPermissionManager().checkPermission(
permName, pkgName, userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
- } finally {
- Binder.restoreCallingIdentity(token);
}
}
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index d8ab618ce876..4224b7a1aae8 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -203,7 +203,7 @@ decision is made the activity is called by via `Activity.onPermissionGranted`.
During development and testing a runtime permission can be granted via the `pm` shell command or by
using the `UiAutomator.grantRuntimePermission` API call. Please note that this does _not_ grant the
-[app-op](#runtime-permissions-and-app-ops) synchronously. Unless the app needs to test the actual
+[app-op](#runtime-permissions-and-app_ops) synchronously. Unless the app needs to test the actual
permission grant flow it is recommended to grant the runtime permissions during install using
`adb install -g /my/package.apk`.
@@ -262,7 +262,7 @@ crashing the app the special `PERMISSION_DENIED_APP_OP` mandates that the API sh
silently fail.
A secondary use case of the `AppOpsManager.noteOp` calls is to
-[track](../app/AppOps.md#Appops-for-tracking) which apps perform what runtime protected actions.
+[track](../app/AppOps.md#app_ops-for-tracking) which apps perform what runtime protected actions.
#### Verifying an app has a runtime time permission
@@ -471,7 +471,7 @@ This is currently (Mar 2020) reworked and will behave like [location](#location)
##### Location
-As described [above](#runtime-permissions-and-app-ops) the app-op mode for granted permissions is
+As described [above](#runtime-permissions-and-app_ops) the app-op mode for granted permissions is
`MODE_ALLOWED` to allow access or `MODE_IGNORED` to suppress access.
The important case is the case where the permission is granted and the app-op is `MODE_IGNORED`. In
@@ -848,7 +848,7 @@ private val whitelistedPkgs = listOf("my.whitelisted.package")
@Test
fun onlySomeAppsAreAllowedToHavePermissionGranted() {
- assertThat(whitelistedPkgs).containsAllIn(
+ assertThat(whitelistedPkgs).containsAtLeastElementsIn(
context.packageManager.getInstalledPackages(MATCH_ALL)
.filter { pkg ->
context.checkPermission(android.Manifest.permission.MY_PRIVILEGED_PERMISSION, -1,
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 1db6231581ad..e45e7a1aa3b9 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -2625,7 +2625,8 @@ public final class CalendarContract {
intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
intent.putExtra(ALARM_TIME, alarmTime);
intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
- PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTime, pi);
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7a03953f2e9b..99ffee3e2ad9 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -84,6 +84,15 @@ public final class DeviceConfig {
"activity_manager_native_boot";
/**
+ * Namespace for AlarmManager configurations.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
+
+ /**
* Namespace for all app compat related features. These features will be applied
* immediately upon change.
*
diff --git a/core/java/android/provider/FontRequest.java b/core/java/android/provider/FontRequest.java
index 34114bcce195..1f67e8f24a65 100644
--- a/core/java/android/provider/FontRequest.java
+++ b/core/java/android/provider/FontRequest.java
@@ -20,13 +20,14 @@ import android.util.Base64;
import com.android.internal.util.Preconditions;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Information about a font request that may be sent to a Font Provider.
+ * @deprecated Use the {@link androidx.core.provider.FontRequest}
*/
+@Deprecated
public final class FontRequest {
private final String mProviderAuthority;
private final String mProviderPackage;
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index fcbda5f68f96..a15f3aeedf67 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -69,7 +69,11 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* Utility class to deal with Font ContentProviders.
+ * @deprecated Use the <a href="{@docRoot}jetpack">Jetpack Core Library</a>
+ * {@link androidx.core.provider.FontsContractCompat} for consistent behavior across all
+ * devices.
*/
+@Deprecated
public class FontsContract {
private static final String TAG = "FontsContract";
@@ -79,7 +83,11 @@ public class FontsContract {
* This should point to a real file or shared memory, as the client will mmap the given file
* descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
* client application.
+ *
+ * @deprecated Use the {@link androidx.core.provider.FontsContractCompat.Columns} for consistent
+ * behavior across all devices.
*/
+ @Deprecated
public static final class Columns implements BaseColumns {
// Do not instantiate.
@@ -174,7 +182,11 @@ public class FontsContract {
/**
* Object represent a font entry in the family returned from {@link #fetchFonts}.
+ *
+ * @deprecated Use the {@link androidx.core.provider.FontsContractCompat.FontInfo} for
+ * consistent behavior across all devices
*/
+ @Deprecated
public static class FontInfo {
private final Uri mUri;
private final int mTtcIndex;
@@ -251,7 +263,11 @@ public class FontsContract {
/**
* Object returned from {@link #fetchFonts}.
+ *
+ * @deprecated Use the {@link androidx.core.provider.FontsContractCompat.FontFamilyResult} for
+ * consistent behavior across all devices
*/
+ @Deprecated
public static class FontFamilyResult {
/**
* Constant represents that the font was successfully retrieved. Note that when this value
@@ -412,7 +428,11 @@ public class FontsContract {
/**
* Interface used to receive asynchronously fetched typefaces.
+ *
+ * @deprecated Use the {@link androidx.core.provider.FontsContractCompat.FontRequestCallback}
+ * for consistent behavior across all devices
*/
+ @Deprecated
public static class FontRequestCallback {
/**
* Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d84f54a37b98..428af25bc8c6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -81,6 +81,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MemoryIntArray;
import android.view.Display;
+import android.view.WindowManager.LayoutParams;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -326,6 +327,21 @@ public final class Settings {
"android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Reduce Bright Colors.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REDUCE_BRIGHT_COLORS_SETTINGS =
+ "android.settings.REDUCE_BRIGHT_COLORS_SETTINGS";
+
+ /**
* Activity Action: Show settings to control access to usage information.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -3569,6 +3585,9 @@ public final class Settings {
if (outConfig.fontScale < 0) {
outConfig.fontScale = DEFAULT_FONT_SCALE;
}
+ outConfig.forceBoldText = Settings.Secure.getIntForUser(
+ cr, Settings.Secure.FORCE_BOLD_TEXT, Configuration.FORCE_BOLD_TEXT_NO,
+ userHandle);
final String localeValue =
Settings.System.getStringForUser(cr, SYSTEM_LOCALES, userHandle);
@@ -3599,6 +3618,7 @@ public final class Settings {
if (!inoutConfig.userSetLocale && !inoutConfig.getLocales().isEmpty()) {
inoutConfig.clearLocales();
}
+ inoutConfig.forceBoldText = Configuration.FORCE_BOLD_TEXT_UNDEFINED;
}
/**
@@ -3622,7 +3642,11 @@ public final class Settings {
DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
- /** @hide */
+ /**
+ * Convenience function for checking if settings should be overwritten with config changes.
+ * @see #putConfigurationForUser(ContentResolver, Configuration, int)
+ * @hide
+ */
public static boolean hasInterestingConfigurationChanges(int changes) {
return (changes & ActivityInfo.CONFIG_FONT_SCALE) != 0 ||
(changes & ActivityInfo.CONFIG_LOCALE) != 0;
@@ -6814,6 +6838,12 @@ public final class Settings {
/**
* Whether to draw text in bold.
*
+ * <p>Values:
+ * 1 - Text is not displayed in bold. (Default)
+ * 2 - Text is displayed in bold.
+ *
+ * @see Configuration#FORCE_BOLD_TEXT_NO
+ * @see Configuration#FORCE_BOLD_TEXT_YES
* @hide
*/
public static final String FORCE_BOLD_TEXT = "force_bold_text";
@@ -7126,6 +7156,33 @@ public final class Settings {
public static final String ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS =
"accessibility_interactive_ui_timeout_ms";
+
+ /**
+ * Setting that specifies whether Reduce Bright Colors, or brightness dimming by color
+ * adjustment, is enabled.
+ *
+ * @hide
+ */
+ public static final String REDUCE_BRIGHT_COLORS_ACTIVATED =
+ "reduce_bright_colors_activated";
+
+ /**
+ * Setting that specifies the level of Reduce Bright Colors in intensity. The range is
+ * [0, 100].
+ *
+ * @hide
+ */
+ public static final String REDUCE_BRIGHT_COLORS_LEVEL =
+ "reduce_bright_colors_level";
+
+ /**
+ * Setting that specifies whether Reduce Bright Colors should persist across reboots.
+ *
+ * @hide
+ */
+ public static final String REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS =
+ "reduce_bright_colors_persist_across_reboots";
+
/**
* List of the enabled print services.
*
@@ -7674,6 +7731,8 @@ public final class Settings {
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
+ @SuppressLint("NoSettingsProvider")
public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker";
/**
@@ -7684,6 +7743,8 @@ public final class Settings {
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
+ @SuppressLint("NoSettingsProvider")
public static final String SELECTED_SPELL_CHECKER_SUBTYPE =
"selected_spell_checker_subtype";
@@ -7965,6 +8026,12 @@ public final class Settings {
public static final String UI_NIGHT_MODE_OVERRIDE_ON = "ui_night_mode_override_on";
/**
+ * The last computed night mode bool the last time the phone was on
+ * @hide
+ */
+ public static final String UI_NIGHT_MODE_LAST_COMPUTED = "ui_night_mode_last_computed";
+
+ /**
* The current night mode that has been overridden to turn off by the system. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
@@ -9032,6 +9099,7 @@ public final class Settings {
* @see#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
* @hide
*/
+ @TestApi
public static final String ACCESSIBILITY_MAGNIFICATION_MODE =
"accessibility_magnification_mode";
@@ -9039,12 +9107,14 @@ public final class Settings {
* Magnification mode value that magnifies whole display.
* @hide
*/
+ @TestApi
public static final int ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN = 0x1;
/**
* Magnification mode value that magnifies magnify particular region in a window
* @hide
*/
+ @TestApi
public static final int ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW = 0x2;
/**
@@ -9052,6 +9122,7 @@ public final class Settings {
* region in a window.
* @hide
*/
+ @TestApi
public static final int ACCESSIBILITY_MAGNIFICATION_MODE_ALL = 0x3;
/**
@@ -9063,6 +9134,7 @@ public final class Settings {
* @see#ACCESSIBILITY_MAGNIFICATION_MODE_ALL
* @hide
*/
+ @TestApi
public static final String ACCESSIBILITY_MAGNIFICATION_CAPABILITY =
"accessibility_magnification_capability";
@@ -9089,6 +9161,22 @@ public final class Settings {
};
/**
+ * How long Assistant handles have enabled in milliseconds.
+ *
+ * @hide
+ */
+ public static final String ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS =
+ "reminder_exp_learning_time_elapsed";
+
+ /**
+ * How many times the Assistant has been triggered using the touch gesture.
+ *
+ * @hide
+ */
+ public static final String ASSIST_HANDLES_LEARNING_EVENT_COUNT =
+ "reminder_exp_learning_event_count";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
@@ -9697,6 +9785,14 @@ public final class Settings {
public static final String DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR =
"render_shadows_in_compositor";
+ /**
+ * If true, submit buffers using blast in SurfaceView.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String DEVELOPMENT_USE_BLAST_ADAPTER_SV =
+ "use_blast_adapter_sv";
+
/**
* Whether user has enabled development settings.
*/
@@ -11821,29 +11917,6 @@ public final class Settings {
public static final String POWER_MANAGER_CONSTANTS = "power_manager_constants";
/**
- * Alarm manager specific settings.
- * This is encoded as a key=value list, separated by commas. Ex:
- *
- * "min_futurity=5000,allow_while_idle_short_time=4500"
- *
- * The following keys are supported:
- *
- * <pre>
- * min_futurity (long)
- * min_interval (long)
- * allow_while_idle_short_time (long)
- * allow_while_idle_long_time (long)
- * allow_while_idle_whitelist_duration (long)
- * </pre>
- *
- * <p>
- * Type: string
- * @hide
- * @see com.android.server.AlarmManagerService.Constants
- */
- public static final String ALARM_MANAGER_CONSTANTS = "alarm_manager_constants";
-
- /**
* Job scheduler QuotaController specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
@@ -12331,6 +12404,15 @@ public final class Settings {
"angle_allowlist";
/**
+ * Lists of ANGLE EGL features for debugging.
+ * Each list of features is separated by a comma, each feature in each list is separated by
+ * a colon.
+ * e.g. feature1:feature2:feature3,feature1:feature3:feature5
+ * @hide
+ */
+ public static final String ANGLE_EGL_FEATURES = "angle_egl_features";
+
+ /**
* Show the "ANGLE In Use" dialog box to the user when ANGLE is the OpenGL driver.
* The value is a boolean (1 or 0).
* @hide
@@ -13343,6 +13425,7 @@ public final class Settings {
*
* @hide
*/
+ @TestApi
public static final String HIDDEN_API_POLICY = "hidden_api_policy";
/**
@@ -13400,15 +13483,6 @@ public final class Settings {
"power_button_very_long_press";
/**
- * Global settings that shouldn't be persisted.
- *
- * @hide
- */
- public static final String[] TRANSIENT_SETTINGS = {
- LOCATION_GLOBAL_KILL_SWITCH,
- };
-
- /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
@@ -14478,6 +14552,66 @@ public final class Settings {
*/
public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE =
"nr_nsa_tracking_screen_off_mode";
+
+ /**
+ * Whether to show People Space.
+ * Values are:
+ * 0: Disabled (default)
+ * 1: Enabled
+ * @hide
+ */
+ public static final String SHOW_PEOPLE_SPACE = "show_people_space";
+
+ /**
+ * Whether to show new lockscreen & AOD UI.
+ * Values are:
+ * 0: Disabled (default)
+ * 1: Enabled
+ * @hide
+ */
+ public static final String SHOW_NEW_LOCKSCREEN = "show_new_lockscreen";
+
+ /**
+ * Block untrusted touches mode.
+ *
+ * Can be one of:
+ * <ul>
+ * <li>0 = {@link BlockUntrustedTouchesMode#DISABLED}: Feature is off.
+ * <li>1 = {@link BlockUntrustedTouchesMode#PERMISSIVE}: Untrusted touches are flagged
+ * but not blocked
+ * <li>2 = {@link BlockUntrustedTouchesMode#BLOCK}: Untrusted touches are blocked
+ * </ul>
+ *
+ * @hide
+ */
+ public static final String BLOCK_UNTRUSTED_TOUCHES_MODE = "block_untrusted_touches";
+
+ /**
+ * The maximum allowed obscuring opacity by UID to propagate touches.
+ *
+ * For certain window types (eg. SAWs), the decision of honoring {@link LayoutParams
+ * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
+ * above the touch-consuming window.
+ *
+ * For a certain UID:
+ * <ul>
+ * <li>If it's the same as the UID of the touch-consuming window, allow it to propagate
+ * the touch.
+ * <li>Otherwise take all its windows of eligible window types above the touch-consuming
+ * window, compute their combined obscuring opacity considering that {@code
+ * opacity(A, B) = 1 - (1 - opacity(A))*(1 - opacity(B))}. If the computed value is
+ * lesser than or equal to this setting and there are no other windows preventing the
+ * touch, allow the UID to propagate the touch.
+ * </ul>
+ *
+ * @see android.hardware.input.InputManager#getMaximumObscuringOpacityForTouch(Context)
+ * @see android.hardware.input.InputManager#setMaximumObscuringOpacityForTouch(Context,
+ * float)
+ *
+ * @hide
+ */
+ public static final String MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH =
+ "maximum_obscuring_opacity_for_touch";
}
/**
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 5d34c476a4a6..8f119fc25fd5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -64,6 +64,7 @@ import com.android.internal.os.SomeArgs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -1802,7 +1803,7 @@ public abstract class NotificationListenerService extends Service {
* {@link NotificationAssistantService}
*/
public @NonNull List<Notification.Action> getSmartActions() {
- return mSmartActions;
+ return mSmartActions == null ? Collections.emptyList() : mSmartActions;
}
/**
@@ -1810,7 +1811,7 @@ public abstract class NotificationListenerService extends Service {
* {@link NotificationAssistantService}
*/
public @NonNull List<CharSequence> getSmartReplies() {
- return mSmartReplies;
+ return mSmartReplies == null ? Collections.emptyList() : mSmartReplies;
}
/**
@@ -1864,8 +1865,9 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * Returns whether this notification is a conversation notification.
- * @hide
+ * Returns whether this notification is a conversation notification, and would appear
+ * in the conversation section of the notification shade, on devices that separate that
+ * type of notification.
*/
public boolean isConversation() {
return mIsConversation;
@@ -1880,7 +1882,10 @@ public abstract class NotificationListenerService extends Service {
}
/**
- * @hide
+ * Returns the shortcut information associated with this notification, if it is a
+ * {@link #isConversation() conversation notification}.
+ * <p>This might be null even if the notification is a conversation notification, if
+ * the posting app hasn't opted into the full conversation feature set yet.</p>
*/
public @Nullable ShortcutInfo getShortcutInfo() {
return mShortcutInfo;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 6f941121771e..8f8e6cc3d84a 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -16,9 +16,15 @@
package android.service.voice;
+import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.RECORD_AUDIO;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -32,6 +38,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
+import android.media.permission.Identity;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
@@ -39,6 +46,7 @@ import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -48,7 +56,12 @@ import java.util.Locale;
/**
* A class that lets a VoiceInteractionService implementation interact with
* always-on keyphrase detection APIs.
+ *
+ * @hide
+ * TODO(b/168605867): Once Metalava supports expressing a removed public, but current system API,
+ * mark and track it as such.
*/
+@SystemApi
public class AlwaysOnHotwordDetector {
//---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----//
/**
@@ -228,6 +241,7 @@ public class AlwaysOnHotwordDetector {
private KeyphraseMetadata mKeyphraseMetadata;
private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo;
private final IVoiceInteractionManagerService mModelManagementService;
+ private final IVoiceInteractionSoundTriggerSession mSoundTriggerSession;
private final SoundTriggerListener mInternalCallback;
private final Callback mExternalCallback;
private final Object mLock = new Object();
@@ -425,6 +439,14 @@ public class AlwaysOnHotwordDetector {
mHandler = new MyHandler();
mInternalCallback = new SoundTriggerListener(mHandler);
mModelManagementService = modelManagementService;
+ try {
+ Identity identity = new Identity();
+ identity.packageName = ActivityThread.currentOpPackageName();
+ mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator(
+ identity);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
new RefreshAvailabiltyTask().execute();
}
@@ -485,7 +507,7 @@ public class AlwaysOnHotwordDetector {
private int getSupportedAudioCapabilitiesLocked() {
try {
ModuleProperties properties =
- mModelManagementService.getDspModuleProperties();
+ mSoundTriggerSession.getDspModuleProperties();
if (properties != null) {
return properties.getAudioCapabilities();
}
@@ -513,6 +535,7 @@ public class AlwaysOnHotwordDetector {
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
synchronized (mLock) {
@@ -543,6 +566,7 @@ public class AlwaysOnHotwordDetector {
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
public boolean stopRecognition() {
if (DBG) Slog.d(TAG, "stopRecognition()");
synchronized (mLock) {
@@ -577,6 +601,7 @@ public class AlwaysOnHotwordDetector {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
* if API is not supported by HAL
*/
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
public int setParameter(@ModelParams int modelParam, int value) {
if (DBG) {
Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
@@ -604,6 +629,7 @@ public class AlwaysOnHotwordDetector {
* @param modelParam {@link ModelParams}
* @return value of parameter
*/
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
public int getParameter(@ModelParams int modelParam) {
if (DBG) {
Slog.d(TAG, "getParameter(" + modelParam + ")");
@@ -628,6 +654,7 @@ public class AlwaysOnHotwordDetector {
* @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
*/
+ @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD})
@Nullable
public ModelParamRange queryParameter(@ModelParams int modelParam) {
if (DBG) {
@@ -658,6 +685,7 @@ public class AlwaysOnHotwordDetector {
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
+ @Nullable
public Intent createEnrollIntent() {
if (DBG) Slog.d(TAG, "createEnrollIntent");
synchronized (mLock) {
@@ -679,6 +707,7 @@ public class AlwaysOnHotwordDetector {
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
+ @Nullable
public Intent createUnEnrollIntent() {
if (DBG) Slog.d(TAG, "createUnEnrollIntent");
synchronized (mLock) {
@@ -700,6 +729,7 @@ public class AlwaysOnHotwordDetector {
* This may happen if another detector has been instantiated or the
* {@link VoiceInteractionService} hosting this detector has been shut down.
*/
+ @Nullable
public Intent createReEnrollIntent() {
if (DBG) Slog.d(TAG, "createReEnrollIntent");
synchronized (mLock) {
@@ -782,7 +812,7 @@ public class AlwaysOnHotwordDetector {
int code;
try {
- code = mModelManagementService.startRecognition(
+ code = mSoundTriggerSession.startRecognition(
mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
recognitionExtra, null /* additional data */, audioCapabilities));
@@ -799,7 +829,7 @@ public class AlwaysOnHotwordDetector {
private int stopRecognitionLocked() {
int code;
try {
- code = mModelManagementService.stopRecognition(mKeyphraseMetadata.getId(),
+ code = mSoundTriggerSession.stopRecognition(mKeyphraseMetadata.getId(),
mInternalCallback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -813,7 +843,7 @@ public class AlwaysOnHotwordDetector {
private int setParameterLocked(@ModelParams int modelParam, int value) {
try {
- int code = mModelManagementService.setParameter(mKeyphraseMetadata.getId(), modelParam,
+ int code = mSoundTriggerSession.setParameter(mKeyphraseMetadata.getId(), modelParam,
value);
if (code != STATUS_OK) {
@@ -828,7 +858,7 @@ public class AlwaysOnHotwordDetector {
private int getParameterLocked(@ModelParams int modelParam) {
try {
- return mModelManagementService.getParameter(mKeyphraseMetadata.getId(), modelParam);
+ return mSoundTriggerSession.getParameter(mKeyphraseMetadata.getId(), modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -838,7 +868,7 @@ public class AlwaysOnHotwordDetector {
private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
try {
SoundTrigger.ModelParamRange modelParamRange =
- mModelManagementService.queryParameter(mKeyphraseMetadata.getId(), modelParam);
+ mSoundTriggerSession.queryParameter(mKeyphraseMetadata.getId(), modelParam);
if (modelParamRange == null) {
return null;
@@ -972,7 +1002,7 @@ public class AlwaysOnHotwordDetector {
ModuleProperties dspModuleProperties;
try {
dspModuleProperties =
- mModelManagementService.getDspModuleProperties();
+ mSoundTriggerSession.getDspModuleProperties();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 45d3465fdae8..fb03ed45113e 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.compat.annotation.UnsupportedAppUsage;
@@ -237,9 +238,8 @@ public class VoiceInteractionService extends Service {
/**
* Called during service initialization to tell you when the system is ready
* to receive interaction from it. You should generally do initialization here
- * rather than in {@link #onCreate}. Methods such as {@link #showSession} and
- * {@link #createAlwaysOnHotwordDetector}
- * will not be operational until this point.
+ * rather than in {@link #onCreate}. Methods such as {@link #showSession} will
+ * not be operational until this point.
*/
public void onReady() {
mSystemService = IVoiceInteractionManagerService.Stub.asInterface(
@@ -309,9 +309,15 @@ public class VoiceInteractionService extends Service {
* @param locale The locale for which the enrollment needs to be performed.
* @param callback The callback to notify of detection events.
* @return An always-on hotword detector for the given keyphrase and locale.
+ *
+ * @hide
*/
+ @SystemApi
+ @NonNull
public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(
- String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) {
+ @SuppressLint("MissingNullability") String keyphrase, // TODO: annotate nullability properly
+ @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale,
+ @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index de8710b12c4a..25e245e99d77 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -879,7 +879,7 @@ public abstract class WallpaperService extends Service {
com.android.internal.R.style.Animation_Wallpaper;
InputChannel inputChannel = new InputChannel();
- if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
+ if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mWinFrames.frame, mWinFrames.contentInsets,
mWinFrames.stableInsets, mWinFrames.displayCutout, inputChannel,
mInsetsState, mTempControls) < 0) {
@@ -903,7 +903,7 @@ public abstract class WallpaperService extends Service {
}
final int relayoutResult = mSession.relayout(
- mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
+ mWindow, mLayout, mWidth, mHeight,
View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl,
mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl);
if (mSurfaceControl.isValid()) {
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index e07f69a98a85..c3ca2866c71c 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -45,7 +45,8 @@ public class CellBroadcastIntents {
private static final String EXTRA_MESSAGE = "message";
/**
- * Broadcast intent action for notifying area information has been updated. The information
+ * Broadcast intent action for notifying area information has been updated. broadcast is also
+ * sent when the user turns off area info alerts. The information
* can be retrieved by {@link CellBroadcastService#getCellBroadcastAreaInfo(int)}. The
* associated SIM slot index of updated area information can be retrieved through the extra
* {@link SubscriptionManager#EXTRA_SLOT_INDEX}.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 16826865987e..70c11f282a2b 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -936,18 +936,21 @@ public class PhoneStateListener {
/**
* Callback invoked when the current emergency number list has changed on the registered
* subscription.
- * Note, the registration subId comes from {@link TelephonyManager} object which registers
- * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ *
+ * Note, the registered subscription is associated with {@link TelephonyManager} object
+ * on which {@link TelephonyManager#listen(PhoneStateListener, int)} was called.
* If this TelephonyManager object was created with
* {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
- * subId. Otherwise, this callback applies to
+ * given subId. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
- * @param emergencyNumberList Map including the key as the active subscription ID
- * (Note: if there is no active subscription, the key is
- * {@link SubscriptionManager#getDefaultSubscriptionId})
- * and the value as the list of {@link EmergencyNumber};
- * null if this information is not available.
+ * @param emergencyNumberList Map associating all active subscriptions on the device with the
+ * list of emergency numbers originating from that subscription.
+ * If there are no active subscriptions, the map will contain a
+ * single entry with
+ * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} as
+ * the key and a list of emergency numbers as the value. If no
+ * emergency number information is available, the value will be null.
*/
public void onEmergencyNumberListChanged(
@NonNull Map<Integer, List<EmergencyNumber>> emergencyNumberList) {
diff --git a/core/java/android/text/StyledTextShaper.java b/core/java/android/text/StyledTextShaper.java
new file mode 100644
index 000000000000..bf906143bc56
--- /dev/null
+++ b/core/java/android/text/StyledTextShaper.java
@@ -0,0 +1,67 @@
+/*
+ * 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.text;
+
+import android.annotation.NonNull;
+import android.graphics.Paint;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextShaper;
+
+import java.util.List;
+
+/**
+ * Provides text shaping for multi-styled text.
+ *
+ * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ * @see StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic, TextPaint)
+ */
+public class StyledTextShaper {
+ private StyledTextShaper() {}
+
+
+ /**
+ * Shape multi-styled text.
+ *
+ * @param text a styled text.
+ * @param start a start index of shaping target in the text.
+ * @param count a length of shaping target in the text.
+ * @param dir a text direction.
+ * @param paint a paint
+ * @return a shape result.
+ */
+ public static @NonNull List<PositionedGlyphs> shapeText(
+ @NonNull CharSequence text, int start, int count,
+ @NonNull TextDirectionHeuristic dir, @NonNull TextPaint paint) {
+ MeasuredParagraph mp = MeasuredParagraph.buildForBidi(
+ text, start, start + count, dir, null);
+ TextLine tl = TextLine.obtain();
+ try {
+ tl.set(paint, text, start, start + count,
+ mp.getParagraphDir(),
+ mp.getDirections(start, start + count),
+ false /* tabstop is not supported */,
+ null,
+ -1, -1 // ellipsis is not supported.
+ );
+ return tl.shape();
+ } finally {
+ TextLine.recycle(tl);
+ }
+ }
+
+}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 3c51fa765263..b82683260985 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -23,6 +23,8 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextShaper;
import android.os.Build;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
@@ -35,6 +37,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
+import java.util.List;
/**
* Represents a line of styled text, for measuring in visual order and
@@ -307,6 +310,36 @@ public class TextLine {
}
/**
+ * Shape the TextLine.
+ */
+ List<PositionedGlyphs> shape() {
+ List<PositionedGlyphs> glyphs = new ArrayList<>();
+ float horizontal = 0;
+ float x = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ horizontal += shapeRun(glyphs, segStart, j, runIsRtl, x + horizontal,
+ runIndex != (runCount - 1) || j != mLen);
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ horizontal = mDir * nextTab(horizontal * mDir);
+ }
+ segStart = j + 1;
+ }
+ }
+ }
+ return glyphs;
+ }
+
+ /**
* Returns the signed graphical offset from the leading margin.
*
* Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a
@@ -483,12 +516,12 @@ public class TextLine {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
float w = -measureRun(start, limit, limit, runIsRtl, null);
- handleRun(start, limit, limit, runIsRtl, c, x + w, top,
+ handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
y, bottom, null, false);
return w;
}
- return handleRun(start, limit, limit, runIsRtl, c, x, top,
+ return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
y, bottom, null, needWidth);
}
@@ -507,9 +540,34 @@ public class TextLine {
*/
private float measureRun(int start, int offset, int limit, boolean runIsRtl,
FontMetricsInt fmi) {
- return handleRun(start, offset, limit, runIsRtl, null, 0, 0, 0, 0, fmi, true);
+ return handleRun(start, offset, limit, runIsRtl, null, null, 0, 0, 0, 0, fmi, true);
+ }
+
+ /**
+ * Shape a unidirectional (but possibly multi-styled) run of text.
+ *
+ * @param glyphs the output positioned glyphs list
+ * @param start the line-relative start
+ * @param limit the line-relative limit
+ * @param runIsRtl true if the run is right-to-left
+ * @param x the position of the run that is closest to the leading margin
+ * @param needWidth true if the width value is required.
+ * @return the signed width of the run, based on the paragraph direction.
+ * Only valid if needWidth is true.
+ */
+ private float shapeRun(List<PositionedGlyphs> glyphs, int start,
+ int limit, boolean runIsRtl, float x, boolean needWidth) {
+
+ if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
+ float w = -measureRun(start, limit, limit, runIsRtl, null);
+ handleRun(start, limit, limit, runIsRtl, null, glyphs, x + w, 0, 0, 0, null, false);
+ return w;
+ }
+
+ return handleRun(start, limit, limit, runIsRtl, null, glyphs, x, 0, 0, 0, null, needWidth);
}
+
/**
* Walk the cursor through this line, skipping conjuncts and
* zero-width characters.
@@ -841,6 +899,7 @@ public class TextLine {
* @param end the end of the text
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null if rendering is not needed
+ * @param glyphs the output positioned glyph list, can be null if not necessary
* @param x the edge of the run closest to the leading margin
* @param top the top of the line
* @param y the baseline
@@ -854,7 +913,7 @@ public class TextLine {
*/
private float handleText(TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl,
- Canvas c, float x, int top, int y, int bottom,
+ Canvas c, List<PositionedGlyphs> glyphs, float x, int top, int y, int bottom,
FontMetricsInt fmi, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations) {
@@ -878,16 +937,20 @@ public class TextLine {
totalWidth = getRunAdvance(wp, start, end, contextStart, contextEnd, runIsRtl, offset);
}
- if (c != null) {
- final float leftX, rightX;
- if (runIsRtl) {
- leftX = x - totalWidth;
- rightX = x;
- } else {
- leftX = x;
- rightX = x + totalWidth;
- }
+ final float leftX, rightX;
+ if (runIsRtl) {
+ leftX = x - totalWidth;
+ rightX = x;
+ } else {
+ leftX = x;
+ rightX = x + totalWidth;
+ }
+
+ if (glyphs != null) {
+ shapeTextRun(glyphs, wp, start, end, contextStart, contextEnd, runIsRtl, leftX);
+ }
+ if (c != null) {
if (wp.bgColor != 0) {
int previousColor = wp.getColor();
Paint.Style previousStyle = wp.getStyle();
@@ -1072,6 +1135,7 @@ public class TextLine {
* @param limit the limit of the run
* @param runIsRtl true if the run is right-to-left
* @param c the canvas, can be null
+ * @param glyphs the output positioned glyphs, can be null
* @param x the end of the run closest to the leading margin
* @param top the top of the line
* @param y the baseline
@@ -1082,7 +1146,8 @@ public class TextLine {
* valid if needWidth is true
*/
private float handleRun(int start, int measureLimit,
- int limit, boolean runIsRtl, Canvas c, float x, int top, int y,
+ int limit, boolean runIsRtl, Canvas c,
+ List<PositionedGlyphs> glyphs, float x, int top, int y,
int bottom, FontMetricsInt fmi, boolean needWidth) {
if (measureLimit < start || measureLimit > limit) {
@@ -1115,7 +1180,7 @@ public class TextLine {
wp.set(mPaint);
wp.setStartHyphenEdit(adjustStartHyphenEdit(start, wp.getStartHyphenEdit()));
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
- return handleText(wp, start, limit, start, limit, runIsRtl, c, x, top,
+ return handleText(wp, start, limit, start, limit, runIsRtl, c, glyphs, x, top,
y, bottom, fmi, needWidth, measureLimit, null);
}
@@ -1196,8 +1261,8 @@ public class TextLine {
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
activePaint.setEndHyphenEdit(
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
- x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
- top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c,
+ glyphs, x, top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations);
activeStart = j;
@@ -1223,7 +1288,7 @@ public class TextLine {
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
activePaint.setEndHyphenEdit(
adjustEndHyphenEdit(activeEnd, mPaint.getEndHyphenEdit()));
- x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, x,
+ x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, glyphs, x,
top, y, bottom, fmi, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations);
}
@@ -1260,6 +1325,45 @@ public class TextLine {
}
/**
+ * Shape a text run with the set-up paint.
+ *
+ * @param glyphs the output positioned glyphs list
+ * @param paint the paint used to render the text
+ * @param start the start of the run
+ * @param end the end of the run
+ * @param contextStart the start of context for the run
+ * @param contextEnd the end of the context for the run
+ * @param runIsRtl true if the run is right-to-left
+ * @param x the x position of the left edge of the run
+ */
+ private void shapeTextRun(List<PositionedGlyphs> glyphs, TextPaint paint,
+ int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x) {
+
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ if (mCharsValid) {
+ glyphs.add(TextShaper.shapeTextRun(
+ mChars,
+ start, count,
+ contextStart, contextCount,
+ x, 0f,
+ runIsRtl,
+ paint
+ ));
+ } else {
+ glyphs.add(TextShaper.shapeTextRun(
+ mText,
+ mStart + start, count,
+ mStart + contextStart, contextCount,
+ x, 0f,
+ runIsRtl,
+ paint
+ ));
+ }
+ }
+
+
+ /**
* Returns the next tab position.
*
* @param h the (unsigned) offset from the leading margin
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 984acfd2c860..d441e6bbf8cb 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2173,7 +2173,7 @@ public class TextUtils {
public static <T extends CharSequence> T trimToLengthWithEllipsis(@Nullable T text,
@IntRange(from = 1) int size) {
T trimmed = trimToSize(text, size);
- if (trimmed.length() < text.length()) {
+ if (text != null && trimmed.length() < text.length()) {
trimmed = (T) (trimmed.toString() + "...");
}
return trimmed;
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index be01bfb94f21..ac8005b39469 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -71,6 +71,12 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
public static final int FLAG_AUTO_CORRECTION = 0x0004;
/**
+ * Sets this flag if the suggestions apply to a grammar error. This type of suggestion is
+ * rendered differently to highlight the error.
+ */
+ public static final int FLAG_GRAMMAR_ERROR = 0x0008;
+
+ /**
* This action is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
*
* @deprecated For IMEs to receive this kind of user interaction signals, implement IMEs' own
@@ -136,6 +142,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private float mAutoCorrectionUnderlineThickness;
private int mAutoCorrectionUnderlineColor;
+ private float mGrammarErrorUnderlineThickness;
+ private int mGrammarErrorUnderlineColor;
+
/**
* @param context Context for the application
* @param suggestions Suggestions for the string under the span
@@ -190,9 +199,11 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private void initStyle(Context context) {
if (context == null) {
mMisspelledUnderlineThickness = 0;
+ mGrammarErrorUnderlineThickness = 0;
mEasyCorrectUnderlineThickness = 0;
mAutoCorrectionUnderlineThickness = 0;
mMisspelledUnderlineColor = Color.BLACK;
+ mGrammarErrorUnderlineColor = Color.BLACK;
mEasyCorrectUnderlineColor = Color.BLACK;
mAutoCorrectionUnderlineColor = Color.BLACK;
return;
@@ -206,6 +217,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mMisspelledUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+ defStyleAttr = com.android.internal.R.attr.textAppearanceGrammarErrorSuggestion;
+ typedArray = context.obtainStyledAttributes(
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0);
+ mGrammarErrorUnderlineThickness = typedArray.getDimension(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
+ mGrammarErrorUnderlineColor = typedArray.getColor(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+
defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
typedArray = context.obtainStyledAttributes(
null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0);
@@ -235,6 +254,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mMisspelledUnderlineThickness = src.readFloat();
mAutoCorrectionUnderlineColor = src.readInt();
mAutoCorrectionUnderlineThickness = src.readFloat();
+ mGrammarErrorUnderlineColor = src.readInt();
+ mGrammarErrorUnderlineThickness = src.readFloat();
}
/**
@@ -313,6 +334,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
dest.writeFloat(mMisspelledUnderlineThickness);
dest.writeInt(mAutoCorrectionUnderlineColor);
dest.writeFloat(mAutoCorrectionUnderlineThickness);
+ dest.writeInt(mGrammarErrorUnderlineColor);
+ dest.writeFloat(mGrammarErrorUnderlineThickness);
}
@Override
@@ -362,13 +385,19 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0;
final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0;
+ final boolean grammarError = (mFlags & FLAG_GRAMMAR_ERROR) != 0;
if (easy) {
- if (!misspelled) {
+ if (!misspelled && !grammarError) {
tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness);
} else if (tp.underlineColor == 0) {
// Spans are rendered in an arbitrary order. Since misspelled is less prioritary
// than just easy, do not apply misspelled if an easy (or a mispelled) has been set
- tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness);
+ if (grammarError) {
+ tp.setUnderlineText(
+ mGrammarErrorUnderlineColor, mGrammarErrorUnderlineThickness);
+ } else {
+ tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness);
+ }
}
} else if (autoCorrection) {
tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness);
@@ -384,11 +413,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0;
final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0;
+ final boolean grammarError = (mFlags & FLAG_GRAMMAR_ERROR) != 0;
if (easy) {
- if (!misspelled) {
- return mEasyCorrectUnderlineColor;
- } else {
+ if (grammarError) {
+ return mGrammarErrorUnderlineColor;
+ } else if (misspelled) {
return mMisspelledUnderlineColor;
+ } else {
+ return mEasyCorrectUnderlineColor;
}
} else if (autoCorrection) {
return mAutoCorrectionUnderlineColor;
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
index 721e6b39d24a..5eeb12273661 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -48,7 +48,7 @@ public class IconDrawableFactory {
}
protected boolean needsBadging(ApplicationInfo appInfo, @UserIdInt int userId) {
- return appInfo.isInstantApp() || mUm.isManagedProfile(userId);
+ return appInfo.isInstantApp() || mUm.hasBadge(userId);
}
@UnsupportedAppUsage
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 5a74ec0e52c0..b77265b0ebf6 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -144,6 +144,17 @@ public class IntArray implements Cloneable {
}
/**
+ * Adds the values in the specified array to this array.
+ */
+ public void addAll(int[] values) {
+ final int count = values.length;
+ ensureCapacity(count);
+
+ System.arraycopy(values, 0, mValues, mSize, count);
+ mSize += count;
+ }
+
+ /**
* Ensures capacity to append at least <code>count</code> values.
*/
private void ensureCapacity(int count) {
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 2ded473930f7..7a117f14d9b5 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -227,7 +227,7 @@ public final class Log {
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23
- * for Nougat (7.0) releases (API <= 23) and prior, there is no
+ * for Nougat (7.0) and prior releases (API <= 25), there is no
* tag limit of concern after this API level.
*/
@FastNative
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
new file mode 100644
index 000000000000..865d5608a40a
--- /dev/null
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util.imetracing;
+
+import android.app.ActivityThread;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.ShellCommand;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.view.IInputMethodManager;
+
+/**
+ *
+ * An abstract class that declares the methods for ime trace related operations - enable trace,
+ * schedule trace and add new trace to buffer. Both the client and server side classes can use
+ * it by getting an implementation through {@link ImeTracing#getInstance()}.
+ *
+ * @hide
+ */
+public abstract class ImeTracing {
+
+ static final String TAG = "imeTracing";
+ public static final String PROTO_ARG = "--proto-com-android-imetracing";
+
+ private static ImeTracing sInstance;
+ static boolean sEnabled = false;
+ IInputMethodManager mService;
+
+ ImeTracing() throws ServiceNotFoundException {
+ mService = IInputMethodManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
+ }
+
+ /**
+ * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class
+ * and an instance of {@link ImeTracingClientImpl} when called from a client side class.
+ * Useful to schedule a dump for next frame or save a dump when certain methods are called.
+ *
+ * @return Instance of one of the children classes of {@link ImeTracing}
+ */
+ public static ImeTracing getInstance() {
+ if (sInstance == null) {
+ try {
+ sInstance = isSystemProcess()
+ ? new ImeTracingServerImpl() : new ImeTracingClientImpl();
+ } catch (RemoteException | ServiceNotFoundException e) {
+ Log.e(TAG, "Exception while creating ImeTracing instance", e);
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Sends request to start proto dump to {@link ImeTracingServerImpl} when called from a
+ * server process and to {@link ImeTracingClientImpl} when called from a client process.
+ */
+ public abstract void triggerDump();
+
+ /**
+ * @param proto dump to be added to the buffer
+ */
+ public abstract void addToBuffer(ProtoOutputStream proto);
+
+ /**
+ * @param shell The shell command to process
+ * @return {@code 0} if the command was successfully processed, {@code -1} otherwise
+ */
+ public abstract int onShellCommand(ShellCommand shell);
+
+ /**
+ * Sets whether ime tracing is enabled.
+ *
+ * @param enabled Tells whether ime tracing should be enabled or disabled.
+ */
+ public void setEnabled(boolean enabled) {
+ sEnabled = enabled;
+ }
+
+ /**
+ * @return {@code true} if dumping is enabled, {@code false} otherwise.
+ */
+ public boolean isEnabled() {
+ return sEnabled;
+ }
+
+ /**
+ * @return {@code true} if tracing is available, {@code false} otherwise.
+ */
+ public boolean isAvailable() {
+ return mService != null;
+ }
+
+ private static boolean isSystemProcess() {
+ return ActivityThread.isSystem();
+ }
+}
diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java
new file mode 100644
index 000000000000..e5d7d3380d02
--- /dev/null
+++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util.imetracing;
+
+import android.os.RemoteException;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.ShellCommand;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.inputmethod.InputMethodManager;
+
+/**
+ * @hide
+ */
+class ImeTracingClientImpl extends ImeTracing {
+
+ private boolean mDumpInProgress;
+ private final Object mDumpInProgressLock = new Object();
+
+ ImeTracingClientImpl() throws ServiceNotFoundException, RemoteException {
+ sEnabled = mService.isImeTraceEnabled();
+ }
+
+ @Override
+ public void addToBuffer(ProtoOutputStream proto) {
+ }
+
+ @Override
+ public int onShellCommand(ShellCommand shell) {
+ return -1;
+ }
+
+ @Override
+ public void triggerDump() {
+ if (isAvailable() && isEnabled()) {
+ boolean doDump = false;
+ synchronized (mDumpInProgressLock) {
+ if (!mDumpInProgress) {
+ mDumpInProgress = true;
+ doDump = true;
+ }
+ }
+
+ if (doDump) {
+ try {
+ ProtoOutputStream proto = new ProtoOutputStream();
+ InputMethodManager.dumpProto(proto);
+ mService.startProtoDump(proto.getBytes());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while sending ime-related client dump to server", e);
+ } finally {
+ mDumpInProgress = false;
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java
new file mode 100644
index 000000000000..350cf5721148
--- /dev/null
+++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.util.imetracing;
+
+import static android.os.Build.IS_USER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_H;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_L;
+
+import android.os.RemoteException;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.ShellCommand;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.TraceBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @hide
+ */
+class ImeTracingServerImpl extends ImeTracing {
+ private static final String TRACE_FILENAME = "/data/misc/wmtrace/ime_trace.pb";
+ private static final int BUFFER_CAPACITY = 4096 * 1024;
+
+ // Needed for winscope to auto-detect the dump type. Explained further in
+ // core.proto.android.view.inputmethod.inputmethodeditortrace.proto
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final TraceBuffer mBuffer;
+ private final File mTraceFile;
+ private final Object mEnabledLock = new Object();
+
+ ImeTracingServerImpl() throws ServiceNotFoundException {
+ mBuffer = new TraceBuffer<>(BUFFER_CAPACITY);
+ mTraceFile = new File(TRACE_FILENAME);
+ }
+
+ /**
+ * The provided dump is added to the current dump buffer {@link ImeTracingServerImpl#mBuffer}.
+ *
+ * @param proto dump to be added to the buffer
+ */
+ @Override
+ public void addToBuffer(ProtoOutputStream proto) {
+ if (isAvailable() && isEnabled()) {
+ mBuffer.add(proto);
+ }
+ }
+
+ /**
+ * Responds to a shell command of the format "adb shell cmd input_method ime tracing <command>"
+ *
+ * @param shell The shell command to process
+ * @return {@code 0} if the command was valid and successfully processed, {@code -1} otherwise
+ */
+ @Override
+ public int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArgRequired();
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ pw.println("Input method trace options:");
+ pw.println(" start: Start tracing");
+ pw.println(" stop: Stop tracing");
+ return -1;
+ }
+ }
+
+ @Override
+ public void triggerDump() {
+ if (isAvailable() && isEnabled()) {
+ try {
+ mService.startProtoDump(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while triggering proto dump", e);
+ }
+ }
+ }
+
+ private void writeTraceToFileLocked() {
+ try {
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ mBuffer.writeTraceToFile(mTraceFile, proto);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
+ }
+ }
+
+ @GuardedBy("mEnabledLock")
+ private void startTrace(PrintWriter pw) {
+ if (IS_USER) {
+ Log.w(TAG, "Warn: Tracing is not supported on user builds.");
+ return;
+ }
+
+ synchronized (mEnabledLock) {
+ if (isAvailable() && isEnabled()) {
+ Log.w(TAG, "Warn: Tracing is already started.");
+ return;
+ }
+
+ pw.println("Starting tracing to " + mTraceFile + ".");
+ sEnabled = true;
+ mBuffer.resetBuffer();
+ }
+ }
+
+ @GuardedBy("mEnabledLock")
+ private void stopTrace(PrintWriter pw) {
+ if (IS_USER) {
+ Log.w(TAG, "Warn: Tracing is not supported on user builds.");
+ return;
+ }
+
+ synchronized (mEnabledLock) {
+ if (!isAvailable() || !isEnabled()) {
+ Log.w(TAG, "Warn: Tracing is not available or not started.");
+ return;
+ }
+
+ pw.println("Stopping tracing and writing traces to " + mTraceFile + ".");
+ sEnabled = false;
+ writeTraceToFileLocked();
+ mBuffer.resetBuffer();
+ }
+ }
+}
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
new file mode 100644
index 000000000000..8097dc6dca11
--- /dev/null
+++ b/core/java/android/uwb/UwbManager.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 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.uwb;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides a way to perform Ultra Wideband (UWB) operations such as querying the
+ * device's capabilities and determining the distance and angle between the local device and a
+ * remote device.
+ *
+ * <p>To get a {@link UwbManager}, call the <code>Context.getSystemService(UwbManager.class)</code>.
+ *
+ * @hide
+ */
+public final class UwbManager {
+ /**
+ * Interface for receiving UWB adapter state changes
+ */
+ public interface AdapterStateCallback {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ STATE_CHANGED_REASON_SESSION_STARTED,
+ STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED,
+ STATE_CHANGED_REASON_SYSTEM_POLICY,
+ STATE_CHANGED_REASON_SYSTEM_BOOT,
+ STATE_CHANGED_REASON_ERROR_UNKNOWN})
+ @interface StateChangedReason {}
+
+ /**
+ * Indicates that the state change was due to opening of first UWB session
+ */
+ int STATE_CHANGED_REASON_SESSION_STARTED = 0;
+
+ /**
+ * Indicates that the state change was due to closure of all UWB sessions
+ */
+ int STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED = 1;
+
+ /**
+ * Indicates that the state change was due to changes in system policy
+ */
+ int STATE_CHANGED_REASON_SYSTEM_POLICY = 2;
+
+ /**
+ * Indicates that the current state is due to a system boot
+ */
+ int STATE_CHANGED_REASON_SYSTEM_BOOT = 3;
+
+ /**
+ * Indicates that the state change was due to some unknown error
+ */
+ int STATE_CHANGED_REASON_ERROR_UNKNOWN = 4;
+
+ /**
+ * Invoked when underlying UWB adapter's state is changed
+ * <p>Invoked with the adapter's current state after registering an
+ * {@link AdapterStateCallback} using
+ * {@link UwbManager#registerAdapterStateCallback(Executor, AdapterStateCallback)}.
+ *
+ * <p>Possible values for the state to change are
+ * {@link #STATE_CHANGED_REASON_SESSION_STARTED},
+ * {@link #STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED},
+ * {@link #STATE_CHANGED_REASON_SYSTEM_POLICY},
+ * {@link #STATE_CHANGED_REASON_SYSTEM_BOOT},
+ * {@link #STATE_CHANGED_REASON_ERROR_UNKNOWN}.
+ *
+ * @param isEnabled true when UWB adapter is enabled, false when it is disabled
+ * @param reason the reason for the state change
+ */
+ void onStateChanged(boolean isEnabled, @StateChangedReason int reason);
+ }
+
+ /**
+ * Use <code>Context.getSystemService(UwbManager.class)</code> to get an instance.
+ */
+ private UwbManager() {
+ throw new UnsupportedOperationException();
+ }
+ /**
+ * Register an {@link AdapterStateCallback} to listen for UWB adapter state changes
+ * <p>The provided callback will be invoked by the given {@link Executor}.
+ *
+ * <p>When first registering a callback, the callbacks's
+ * {@link AdapterStateCallback#onStateChanged(boolean, int)} is immediately invoked to indicate
+ * the current state of the underlying UWB adapter with the most recent
+ * {@link AdapterStateCallback.StateChangedReason} that caused the change.
+ *
+ * @param executor an {@link Executor} to execute given callback
+ * @param callback user implementation of the {@link AdapterStateCallback}
+ */
+ public void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Unregister the specified {@link AdapterStateCallback}
+ * <p>The same {@link AdapterStateCallback} object used when calling
+ * {@link #registerAdapterStateCallback(Executor, AdapterStateCallback)} must be used.
+ *
+ * <p>Callbacks are automatically unregistered when application process goes away
+ *
+ * @param callback user implementation of the {@link AdapterStateCallback}
+ */
+ public void unregisterAdapterStateCallback(AdapterStateCallback callback) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get a {@link PersistableBundle} with the supported UWB protocols and parameters.
+ * <p>The {@link PersistableBundle} should be parsed using a support library
+ *
+ * <p>Android reserves the '^android.*' namespace</p>
+ *
+ * @return {@link PersistableBundle} of the device's supported UWB protocols and parameters
+ */
+ @NonNull
+ public PersistableBundle getSpecificationInfo() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Check if ranging is supported, regardless of ranging method
+ *
+ * @return true if ranging is supported
+ */
+ public boolean isRangingSupported() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE,
+ ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D,
+ ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL,
+ ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL})
+ public @interface AngleOfArrivalSupportType {}
+
+ /**
+ * Indicate absence of support for angle of arrival measurement
+ */
+ public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE = 1;
+
+ /**
+ * Indicate support for planar angle of arrival measurement, due to antenna
+ * limitation. Typically requires at least two antennas.
+ */
+ public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2;
+
+ /**
+ * Indicate support for three dimensional angle of arrival measurement.
+ * Typically requires at least three antennas. However, due to antenna
+ * arrangement, a platform may only support hemi-spherical azimuth angles
+ * ranging from -pi/2 to pi/2
+ */
+ public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 2;
+
+ /**
+ * Indicate support for three dimensional angle of arrival measurement.
+ * Typically requires at least three antennas. This mode supports full
+ * azimuth angles ranging from -pi to pi.
+ */
+ public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 3;
+
+
+ /**
+ * Gets the {@link AngleOfArrivalSupportType} supported on this platform
+ * <p>Possible return values are
+ * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE},
+ * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D},
+ * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL},
+ * {@link #ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL}.
+ *
+ * @return angle of arrival type supported
+ */
+ @AngleOfArrivalSupportType
+ public int getAngleOfArrivalSupport() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get a {@link List} of supported channel numbers based on the device's current location
+ * <p>The returned values are ordered by the system's desired ordered of use, with the first
+ * entry being the most preferred.
+ *
+ * <p>Channel numbers are defined based on the IEEE 802.15.4z standard for UWB.
+ *
+ * @return {@link List} of supported channel numbers ordered by preference
+ */
+ @NonNull
+ public List<Integer> getSupportedChannelNumbers() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get a {@link List} of supported preamble code indices
+ * <p> Preamble code indices are defined based on the IEEE 802.15.4z standard for UWB.
+ *
+ * @return {@link List} of supported preamble code indices
+ */
+ @NonNull
+ public Set<Integer> getSupportedPreambleCodeIndices() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the timestamp resolution for events in nanoseconds
+ * <p>This value defines the maximum error of all timestamps for events reported to
+ * {@link RangingSession.Callback}.
+ *
+ * @return the timestamp resolution in nanoseconds
+ */
+ @SuppressLint("MethodNameUnits")
+ public long elapsedRealtimeResolutionNanos() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the number of simultaneous sessions allowed in the system
+ *
+ * @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
+ */
+ public int getMaxSimultaneousSessions() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the maximum number of remote devices in a {@link RangingSession} when the local device
+ * is the initiator.
+ *
+ * @return the maximum number of remote devices per {@link RangingSession}
+ */
+ public int getMaxRemoteDevicesPerInitiatorSession() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the maximum number of remote devices in a {@link RangingSession} when the local device
+ * is a responder.
+ *
+ * @return the maximum number of remote devices per {@link RangingSession}
+ */
+ public int getMaxRemoteDevicesPerResponderSession() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 8bd1235b9398..5ff68541887b 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -188,6 +188,8 @@ public final class Choreographer {
private long mFrameIntervalNanos;
private boolean mDebugPrintNextFrameTimeDelta;
private int mFPSDivisor = 1;
+ private DisplayEventReceiver.VsyncEventData mLastVsyncEventData =
+ new DisplayEventReceiver.VsyncEventData();
private int mTouchMoveNum = -1;
private int mMotionEventType = -1;
private boolean mConsumedMove = false;
@@ -195,6 +197,7 @@ public final class Choreographer {
private boolean mIsVsyncScheduled = false;
private long mLastTouchOptTimeNanos = 0;
private boolean mIsDoFrameProcessing = false;
+
/**
* Contains information about the current frame for jank-tracking,
* mainly timings of key events along with a bit of metadata about
@@ -729,13 +732,37 @@ public final class Choreographer {
}
}
+ /**
+ * Returns the vsync id of the last frame callback. Client are expected to call
+ * this function from their frame callback function to get the vsyncId and pass
+ * it together with a buffer or transaction to the Surface Composer. Calling
+ * this function from anywhere else will return an undefined value.
+ *
+ * @hide
+ */
+ public long getVsyncId() {
+ return mLastVsyncEventData.id;
+ }
+
+ /**
+ * Returns the frame deadline in {@link System#nanoTime()} timebase that it is allotted for the
+ * frame to be completed. Client are expected to call this function from their frame callback
+ * function. Calling this function from anywhere else will return an undefined value.
+ *
+ * @hide
+ */
+ public long getFrameDeadline() {
+ return mLastVsyncEventData.frameDeadline;
+ }
+
void setFPSDivisor(int divisor) {
if (divisor <= 0) divisor = 1;
mFPSDivisor = divisor;
ThreadedRenderer.setFPSDivisor(divisor);
}
- void doFrame(long frameTimeNanos, int frame, long frameTimelineVsyncId) {
+ void doFrame(long frameTimeNanos, int frame,
+ DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
synchronized (mLock) {
mIsVsyncScheduled = false;
@@ -786,9 +813,11 @@ public final class Choreographer {
}
}
- mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, frameTimelineVsyncId);
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+ vsyncEventData.frameDeadline);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
+ mLastVsyncEventData = vsyncEventData;
}
try {
@@ -979,7 +1008,7 @@ public final class Choreographer {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
- doFrame(System.nanoTime(), 0, FrameInfo.INVALID_VSYNC_ID);
+ doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
@@ -996,7 +1025,7 @@ public final class Choreographer {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
- private long mFrameTimelineVsyncId;
+ private VsyncEventData mLastVsyncEventData = new VsyncEventData();
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, CONFIG_CHANGED_EVENT_SUPPRESS);
@@ -1007,7 +1036,7 @@ public final class Choreographer {
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
- long frameTimelineVsyncId) {
+ VsyncEventData vsyncEventData) {
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
@@ -1030,8 +1059,8 @@ public final class Choreographer {
mTimestampNanos = timestampNanos;
mFrame = frame;
+ mLastVsyncEventData = vsyncEventData;
ScrollOptimizer.setVsyncTime(mTimestampNanos);
- mFrameTimelineVsyncId = frameTimelineVsyncId;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
@@ -1040,7 +1069,7 @@ public final class Choreographer {
@Override
public void run() {
mHavePendingVsync = false;
- doFrame(mTimestampNanos, mFrame, mFrameTimelineVsyncId);
+ doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index c726bee9f402..7c01f7a8739a 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -130,14 +130,16 @@ public class DisplayAdjustments {
w = metrics.noncompatWidthPixels;
metrics.noncompatWidthPixels = metrics.noncompatHeightPixels;
metrics.noncompatHeightPixels = w;
+ }
- float x = metrics.xdpi;
- metrics.xdpi = metrics.ydpi;
- metrics.ydpi = x;
-
- x = metrics.noncompatXdpi;
- metrics.noncompatXdpi = metrics.noncompatYdpi;
- metrics.noncompatYdpi = x;
+ /** Adjusts global display metrics that is available to applications. */
+ public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) {
+ final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+ if (rotationAdjustments == null) {
+ return;
+ }
+ metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth;
+ metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight;
}
/** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */
@@ -178,7 +180,7 @@ public class DisplayAdjustments {
/**
* An application can be launched in different rotation than the real display. This class
- * provides the information to adjust the values returned by {@link #Display}.
+ * provides the information to adjust the values returned by {@link Display}.
* @hide
*/
public static class FixedRotationAdjustments implements Parcelable {
@@ -186,12 +188,24 @@ public class DisplayAdjustments {
@Surface.Rotation
final int mRotation;
+ /**
+ * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according
+ * to rotation because it minus the region of screen decorations.
+ */
+ final int mAppWidth;
+
+ /** The rotated {@link DisplayInfo#appHeight}. */
+ final int mAppHeight;
+
/** Non-null if the device has cutout. */
@Nullable
final DisplayCutout mRotatedDisplayCutout;
- public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) {
+ public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight,
+ DisplayCutout cutout) {
mRotation = rotation;
+ mAppWidth = appWidth;
+ mAppHeight = appHeight;
mRotatedDisplayCutout = cutout;
}
@@ -199,6 +213,8 @@ public class DisplayAdjustments {
public int hashCode() {
int hash = 17;
hash = hash * 31 + mRotation;
+ hash = hash * 31 + mAppWidth;
+ hash = hash * 31 + mAppHeight;
hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout);
return hash;
}
@@ -210,12 +226,14 @@ public class DisplayAdjustments {
}
final FixedRotationAdjustments other = (FixedRotationAdjustments) o;
return mRotation == other.mRotation
+ && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight
&& Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout);
}
@Override
public String toString() {
return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation)
+ + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight
+ " cutout=" + mRotatedDisplayCutout + "}";
}
@@ -227,12 +245,16 @@ public class DisplayAdjustments {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mRotation);
+ dest.writeInt(mAppWidth);
+ dest.writeInt(mAppHeight);
dest.writeTypedObject(
new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags);
}
private FixedRotationAdjustments(Parcel in) {
mRotation = in.readInt();
+ mAppWidth = in.readInt();
+ mAppHeight = in.readInt();
final DisplayCutout.ParcelableWrapper cutoutWrapper =
in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR);
mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null;
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 51474d3c39c8..467d93ef3aaf 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -17,6 +17,7 @@
package android.view;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.FrameInfo;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
@@ -145,6 +146,26 @@ public abstract class DisplayEventReceiver {
mMessageQueue = null;
}
+ static final class VsyncEventData {
+ // The frame timeline vsync id, used to correlate a frame
+ // produced by HWUI with the timeline data stored in Surface Flinger.
+ public final long id;
+
+ // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
+ // allotted for the frame to be completed.
+ public final long frameDeadline;
+
+ VsyncEventData(long id, long frameDeadline) {
+ this.id = id;
+ this.frameDeadline = frameDeadline;
+ }
+
+ VsyncEventData() {
+ this.id = FrameInfo.INVALID_VSYNC_ID;
+ this.frameDeadline = Long.MAX_VALUE;
+ }
+ }
+
/**
* Called when a vertical sync pulse is received.
* The recipient should render a frame and then call {@link #scheduleVsync}
@@ -154,11 +175,10 @@ public abstract class DisplayEventReceiver {
* timebase.
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param frame The frame number. Increases by one for each vertical sync interval.
- * @param frameTimelineVsyncId The frame timeline vsync id, used to correlate a frame
- * produced by HWUI with the timeline data stored in Surface Flinger.
+ * @param vsyncEventData The vsync event data.
*/
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
- long frameTimelineVsyncId) {
+ VsyncEventData vsyncEventData) {
}
/**
@@ -201,8 +221,9 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
- long frameTimelineVsyncId) {
- onVsync(timestampNanos, physicalDisplayId, frame, frameTimelineVsyncId);
+ long frameTimelineVsyncId, long frameDeadline) {
+ onVsync(timestampNanos, physicalDisplayId, frame,
+ new VsyncEventData(frameTimelineVsyncId, frameDeadline));
}
// Called from native code.
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 280a1c0d95d2..387787e3b0be 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -198,6 +198,7 @@ public final class FrameMetrics {
Index.ANIMATION_START,
Index.PERFORM_TRAVERSALS_START,
Index.DRAW_START,
+ Index.FRAME_DEADLINE,
Index.SYNC_QUEUED,
Index.SYNC_START,
Index.ISSUE_DRAW_COMMANDS_START,
@@ -216,13 +217,15 @@ public final class FrameMetrics {
int ANIMATION_START = 7;
int PERFORM_TRAVERSALS_START = 8;
int DRAW_START = 9;
- int SYNC_QUEUED = 10;
- int SYNC_START = 11;
- int ISSUE_DRAW_COMMANDS_START = 12;
- int SWAP_BUFFERS = 13;
- int FRAME_COMPLETED = 14;
+ int FRAME_DEADLINE = 10;
+ int SYNC_QUEUED = 11;
+ int SYNC_START = 12;
+ int ISSUE_DRAW_COMMANDS_START = 13;
+ int SWAP_BUFFERS = 14;
+ int FRAME_COMPLETED = 15;
- int FRAME_STATS_COUNT = 18; // must always be last
+ int FRAME_STATS_COUNT = 19; // must always be last and in sync with
+ // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
}
/*
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 94e641c62b25..193e674dd1b0 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -120,12 +120,6 @@ oneway interface IWindow {
void updatePointerIcon(float x, float y);
/**
- * System chrome visibility changes
- */
- void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
- int localValue, int localChanges);
-
- /**
* Called for non-application windows when the enter animation has completed.
*/
void dispatchWindowShown();
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 8e875d7a889d..daab70ae336f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -378,11 +378,6 @@ interface IWindowManager
boolean requestAssistScreenshot(IAssistDataReceiver receiver);
/**
- * Called by the status bar to notify Views of changes to System UI visiblity.
- */
- oneway void statusBarVisibilityChanged(int displayId, int visibility);
-
- /**
* Called by System UI to notify Window Manager to hide transient bars.
*/
oneway void hideTransientBars(int displayId);
@@ -757,4 +752,10 @@ interface IWindowManager
*/
void requestScrollCapture(int displayId, IBinder behindClient, int taskId,
IScrollCaptureController controller);
+
+ /**
+ * Holds the WM lock for the specified amount of milliseconds.
+ * Intended for use by the tests that need to imitate lock contention.
+ */
+ void holdLock(in int durationMs);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 609f80253ce6..69a5fafbb0db 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -44,17 +44,17 @@ import java.util.List;
* {@hide}
*/
interface IWindowSession {
- int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out Rect outFrame,
out Rect outContentInsets, out Rect outStableInsets,
out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls);
- int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets,
out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
out InsetsState insetsState, out InsetsSourceControl[] activeControls);
- int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out Rect outContentInsets,
out Rect outStableInsets, out InsetsState insetsState);
@UnsupportedAppUsage
@@ -68,7 +68,6 @@ interface IWindowSession {
* to draw the window's contents.
*
* @param window The window being modified.
- * @param seq Ordering sequence number.
* @param attrs If non-null, new attributes to apply to the window.
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
@@ -106,7 +105,7 @@ interface IWindowSession {
* @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
* {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
*/
- int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
int flags, long frameNumber, out ClientWindowFrames outFrames,
out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
@@ -121,7 +120,7 @@ interface IWindowSession {
* @param childrenOnly Whether to only prepare child windows for replacement
* (for example when main windows are being reused via preservation).
*/
- void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly);
+ oneway void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly);
/**
* Called by a client to report that it ran out of graphics memory.
@@ -134,7 +133,7 @@ interface IWindowSession {
* to optimize compositing of this part of the window.
*/
@UnsupportedAppUsage
- void setTransparentRegion(IWindow window, in Region region);
+ oneway void setTransparentRegion(IWindow window, in Region region);
/**
* Tell the window manager about the content and visible insets of the
@@ -146,7 +145,7 @@ interface IWindowSession {
* frame can receive pointer events, as defined by
* {@link android.view.ViewTreeObserver.InternalInsetsInfo}.
*/
- void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
+ oneway void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
in Rect visibleInsets, in Region touchableRegion);
/**
@@ -157,10 +156,10 @@ interface IWindowSession {
* is null if there is no sync required.
*/
@UnsupportedAppUsage
- void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction);
+ oneway void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction);
@UnsupportedAppUsage
- void setInTouchMode(boolean showFocus);
+ oneway void setInTouchMode(boolean showFocus);
@UnsupportedAppUsage
boolean getInTouchMode();
@@ -192,24 +191,24 @@ interface IWindowSession {
* consumed is 'true' when the drop was accepted by a valid recipient,
* 'false' otherwise.
*/
- void reportDropResult(IWindow window, boolean consumed);
+ oneway void reportDropResult(IWindow window, boolean consumed);
/**
* Cancel the current drag operation.
* skipAnimation is 'true' when it should skip the drag cancel animation which brings the drag
* shadow image back to the drag start position.
*/
- void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation);
+ oneway void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation);
/**
* Tell the OS that we've just dragged into a View that is willing to accept the drop
*/
- void dragRecipientEntered(IWindow window);
+ oneway void dragRecipientEntered(IWindow window);
/**
* Tell the OS that we've just dragged *off* of a View that was willing to accept the drop
*/
- void dragRecipientExited(IWindow window);
+ oneway void dragRecipientExited(IWindow window);
/**
* For windows with the wallpaper behind them, and the wallpaper is
@@ -230,26 +229,26 @@ interface IWindowSession {
* scaled when setWallpaperZoomOut is called. If set to false, the WallpaperService will
* receive the zoom out value but the surface won't be scaled.
*/
- void setShouldZoomOutWallpaper(IBinder windowToken, boolean shouldZoom);
+ oneway void setShouldZoomOutWallpaper(IBinder windowToken, boolean shouldZoom);
@UnsupportedAppUsage
- void wallpaperOffsetsComplete(IBinder window);
+ oneway void wallpaperOffsetsComplete(IBinder window);
/**
* Apply a raw offset to the wallpaper service when shown behind this window.
*/
- void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
+ oneway void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, in Bundle extras, boolean sync);
@UnsupportedAppUsage
- void wallpaperCommandComplete(IBinder window, in Bundle result);
+ oneway void wallpaperCommandComplete(IBinder window, in Bundle result);
/**
* Notifies that a rectangle on the screen has been requested.
*/
- void onRectangleOnScreenRequested(IBinder token, in Rect rectangle);
+ oneway void onRectangleOnScreenRequested(IBinder token, in Rect rectangle);
IWindowId getWindowId(IBinder window);
@@ -276,9 +275,9 @@ interface IWindowSession {
*/
boolean startMovingTask(IWindow window, float startX, float startY);
- void finishMovingTask(IWindow window);
+ oneway void finishMovingTask(IWindow window);
- void updatePointerIcon(IWindow window);
+ oneway void updatePointerIcon(IWindow window);
/**
* Reparent the top layers for a display to the requested SurfaceControl. The display that is
@@ -292,7 +291,7 @@ interface IWindowSession {
* to.
* @param displayId The id of the display to be re-parented.
*/
- void reparentDisplayContent(IWindow window, in SurfaceControl sc, int displayId);
+ oneway void reparentDisplayContent(IWindow window, in SurfaceControl sc, int displayId);
/**
* Update the location of a child display in its parent window. This enables windows in the
@@ -303,14 +302,14 @@ interface IWindowSession {
* @param y The y coordinate in the parent window.
* @param displayId The id of the display to be notified.
*/
- void updateDisplayContentLocation(IWindow window, int x, int y, int displayId);
+ oneway void updateDisplayContentLocation(IWindow window, int x, int y, int displayId);
/**
* Update a tap exclude region identified by provided id in the window. Touches on this region
* will neither be dispatched to this window nor change the focus to this window. Passing an
* invalid region will remove the area from the exclude region of this window.
*/
- void updateTapExcludeRegion(IWindow window, in Region region);
+ oneway void updateTapExcludeRegion(IWindow window, in Region region);
/**
* Called when the client has changed the local insets state, and now the server should reflect
@@ -334,8 +333,8 @@ interface IWindowSession {
/**
* Update the flags on an input channel associated with a particular surface.
*/
- void updateInputChannel(in IBinder channelToken, int displayId, in SurfaceControl surface,
- int flags, int privateFlags, in Region region);
+ oneway void updateInputChannel(in IBinder channelToken, int displayId,
+ in SurfaceControl surface, int flags, int privateFlags, in Region region);
/**
* Transfer window focus to an embedded window if the calling window has focus.
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
index 92772c1d7a44..efc0bd2785f4 100644
--- a/core/java/android/view/ImeFocusController.java
+++ b/core/java/android/view/ImeFocusController.java
@@ -16,16 +16,23 @@
package android.view;
+import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS;
+import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW;
+import static android.view.ImeFocusControllerProto.SERVED_VIEW;
+
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.util.Log;
+import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
+import java.util.Objects;
+
/**
* Responsible for IME focus handling inside {@link ViewRootImpl}.
* @hide
@@ -280,4 +287,12 @@ public final class ImeFocusController {
boolean hasImeFocus() {
return mHasImeFocus;
}
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(HAS_IME_FOCUS, mHasImeFocus);
+ proto.write(SERVED_VIEW, Objects.toString(mServedView));
+ proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
+ proto.end(token);
+ }
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 82f60366a814..dd1a19458e1d 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,6 +16,9 @@
package android.view;
+import static android.view.ImeInsetsSourceConsumerProto.FOCUSED_EDITOR;
+import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
+import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsState.ITYPE_IME;
@@ -24,6 +27,7 @@ import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.Parcel;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -111,7 +115,6 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
public @ShowResult int requestShow(boolean fromIme) {
// TODO: ResultReceiver for IME.
// TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
-
if (getControl() == null) {
// If control is null, schedule to show IME when control is available.
mIsRequestedVisibleAwaitingControl = true;
@@ -227,6 +230,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray());
}
+ @Override
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
+ if (mFocusedEditor != null) {
+ mFocusedEditor.dumpDebug(proto, FOCUSED_EDITOR);
+ }
+ proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
+ proto.end(token);
+ }
+
private InputMethodManager getImm() {
return mController.getHost().getInputMethodManager();
}
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index e1b042160062..f2d3f5ad08bf 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -83,7 +83,7 @@ public final class InputChannel implements Parcelable {
*
* @hide
*/
- public void setNativeInputChannel(long nativeChannel) {
+ private void setNativeInputChannel(long nativeChannel) {
if (nativeChannel == 0) {
throw new IllegalArgumentException("Attempting to set native input channel to null.");
}
@@ -148,12 +148,11 @@ public final class InputChannel implements Parcelable {
}
/**
- * Transfers ownership of the internal state of the input channel to another
- * instance and invalidates this instance. This is used to pass an input channel
+ * Creates a copy of this instance to the outParameter. This is used to pass an input channel
* as an out parameter in a binder call.
* @param other The other input channel instance.
*/
- public void transferTo(InputChannel outParameter) {
+ public void copyTo(InputChannel outParameter) {
if (outParameter == null) {
throw new IllegalArgumentException("outParameter must not be null");
}
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 1ef701f732ff..d1a9a05d5bf1 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY;
import android.annotation.Nullable;
import android.graphics.Region;
import android.os.IBinder;
+import android.os.TouchOcclusionMode;
import java.lang.ref.WeakReference;
@@ -82,10 +83,18 @@ public final class InputWindowHandle {
// Window is trusted overlay.
public boolean trustedOverlay;
+ // What effect this window has on touch occlusion if it lets touches pass through
+ // By default windows will block touches if they are untrusted and from a different UID due to
+ // security concerns
+ public int touchOcclusionMode = TouchOcclusionMode.BLOCK_UNTRUSTED;
+
// Id of process and user that owns the window.
public int ownerPid;
public int ownerUid;
+ // Owner package of the window
+ public String packageName;
+
// Window input features.
public int inputFeatures;
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 6ffd892e4351..71899fab554c 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -17,6 +17,14 @@
package android.view;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED;
+import static android.view.InsetsAnimationControlImplProto.IS_FINISHED;
+import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA;
+import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION;
+import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS;
+import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH;
+import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
@@ -38,6 +46,8 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
@@ -48,6 +58,7 @@ import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Implements {@link WindowInsetsAnimationController}
@@ -122,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
mAnimationType = animationType;
mController.startAnimation(this, listener, types, mAnimation,
new Bounds(mHiddenInsets, mShownInsets));
+
+ if ((mTypes & WindowInsets.Type.ime()) != 0) {
+ ImeTracing.getInstance().triggerDump();
+ }
}
private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) {
@@ -285,6 +300,20 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
return mAnimation;
}
+ @Override
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(IS_CANCELLED, mCancelled);
+ proto.write(IS_FINISHED, mFinished);
+ proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix));
+ proto.write(PENDING_INSETS, Objects.toString(mPendingInsets));
+ proto.write(PENDING_FRACTION, mPendingFraction);
+ proto.write(SHOWN_ON_FINISH, mShownOnFinish);
+ proto.write(CURRENT_ALPHA, mCurrentAlpha);
+ proto.write(PENDING_ALPHA, mPendingAlpha);
+ proto.end(token);
+ }
+
WindowInsetsAnimationControlListener getListener() {
return mListener;
}
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 0711c3e166d8..0275b521a2a2 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -16,6 +16,7 @@
package android.view;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
@@ -53,4 +54,14 @@ public interface InsetsAnimationControlRunner {
* @return The animation type this runner is running.
*/
@AnimationType int getAnimationType();
+
+ /**
+ *
+ * Export the state of classes that implement this interface into a protocol buffer
+ * output stream.
+ *
+ * @param proto Stream to write the state to
+ * @param fieldId FieldId of the implementation class
+ */
+ void dumpDebug(ProtoOutputStream proto, long fieldId);
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 123604489da4..cc3cd278b267 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.Trace;
import android.util.Log;
import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
@@ -122,6 +123,12 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
@Override
@UiThread
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ mControl.dumpDebug(proto, fieldId);
+ }
+
+ @Override
+ @UiThread
public int getTypes() {
return mControl.getTypes();
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 92eade3affaa..652781a310b9 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsControllerProto.CONTROL;
+import static android.view.InsetsControllerProto.STATE;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.toInternalType;
@@ -41,6 +43,8 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
@@ -298,6 +302,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
+ if ((types & ime()) != 0) {
+ ImeTracing.getInstance().triggerDump();
+ }
+
mController = controller;
if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
@@ -812,6 +820,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@VisibleForTesting
public void show(@InsetsType int types, boolean fromIme) {
+ if (fromIme) {
+ ImeTracing.getInstance().triggerDump();
+ }
// Handle pending request ready in case there was one set.
if (fromIme && mPendingImeControlRequest != null) {
PendingControlRequest pendingRequest = mPendingImeControlRequest;
@@ -860,6 +871,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
void hide(@InsetsType int types, boolean fromIme) {
+ if (fromIme) {
+ ImeTracing.getInstance().triggerDump();
+ }
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -894,6 +908,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
listener.onCancelled(null);
return;
}
+ if (fromIme) {
+ ImeTracing.getInstance().triggerDump();
+ }
controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs,
interpolator, animationType, getLayoutInsetsDuringAnimationMode(types),
@@ -1292,6 +1309,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void hideDirectly(
@InsetsType int types, boolean animationFinished, @AnimationType int animationType) {
+ if ((types & ime()) != 0) {
+ ImeTracing.getInstance().triggerDump();
+ }
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
@@ -1299,6 +1319,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
private void showDirectly(@InsetsType int types) {
+ if ((types & ime()) != 0) {
+ ImeTracing.getInstance().triggerDump();
+ }
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
@@ -1318,6 +1341,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mState.dump(prefix + " ", pw);
}
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ mState.dumpDebug(proto, STATE);
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlRunner runner = mRunningAnimations.get(i).runner;
+ runner.dumpDebug(proto, CONTROL);
+ }
+ proto.end(token);
+ }
+
@VisibleForTesting
@Override
public void startAnimation(InsetsAnimationControlImpl controller,
@@ -1388,7 +1421,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
@Override
- public @Appearance int getSystemBarsBehavior() {
+ public @Behavior int getSystemBarsBehavior() {
return mHost.getSystemBarsBehavior();
}
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
index 385b0bf960ef..5a64a5d5b3a6 100644
--- a/core/java/android/view/InsetsFlags.java
+++ b/core/java/android/view/InsetsFlags.java
@@ -16,13 +16,6 @@
package android.view;
-import static android.view.View.NAVIGATION_BAR_TRANSLUCENT;
-import static android.view.View.NAVIGATION_BAR_TRANSPARENT;
-import static android.view.View.STATUS_BAR_TRANSLUCENT;
-import static android.view.View.STATUS_BAR_TRANSPARENT;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -76,44 +69,4 @@ public class InsetsFlags {
name = "SHOW_TRANSIENT_BARS_BY_SWIPE")
})
public @Behavior int behavior;
-
- /**
- * Converts system UI visibility to appearance.
- *
- * @param systemUiVisibility the system UI visibility to be converted.
- * @return the outcome {@link Appearance}
- */
- public static @Appearance int getAppearance(int systemUiVisibility) {
- int appearance = 0;
- appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LOW_PROFILE,
- APPEARANCE_LOW_PROFILE_BARS);
- appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
- APPEARANCE_LIGHT_STATUS_BARS);
- appearance |= convertFlag(systemUiVisibility, SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
- APPEARANCE_LIGHT_NAVIGATION_BARS);
- appearance |= convertNoFlag(systemUiVisibility,
- STATUS_BAR_TRANSLUCENT | STATUS_BAR_TRANSPARENT, APPEARANCE_OPAQUE_STATUS_BARS);
- appearance |= convertNoFlag(systemUiVisibility,
- NAVIGATION_BAR_TRANSLUCENT | NAVIGATION_BAR_TRANSPARENT,
- APPEARANCE_OPAQUE_NAVIGATION_BARS);
- return appearance;
- }
-
- /**
- * Converts the system UI visibility into an appearance flag if the given visibility contains
- * the given system UI flag.
- */
- private static @Appearance int convertFlag(int systemUiVisibility, int systemUiFlag,
- @Appearance int appearance) {
- return (systemUiVisibility & systemUiFlag) != 0 ? appearance : 0;
- }
-
- /**
- * Converts the system UI visibility into an appearance flag if the given visibility doesn't
- * contains the given system UI flag.
- */
- private static @Appearance int convertNoFlag(int systemUiVisibility, int systemUiFlag,
- @Appearance int appearance) {
- return (systemUiVisibility & systemUiFlag) == 0 ? appearance : 0;
- }
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index dbf75705c073..ec54007e9979 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,10 @@
package android.view;
+import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
+import static android.view.InsetsSourceProto.VISIBLE;
+import static android.view.InsetsSourceProto.VISIBLE_FRAME;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
@@ -25,6 +29,7 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
@@ -183,6 +188,23 @@ public class InsetsSource implements Parcelable {
return false;
}
+ /**
+ * Export the state of {@link InsetsSource} into a protocol buffer output stream.
+ *
+ * @param proto Stream to write the state to
+ * @param fieldId FieldId of InsetsSource as defined in the parent message
+ */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(TYPE, InsetsState.typeToString(mType));
+ mFrame.dumpDebug(proto, FRAME);
+ if (mVisibleFrame != null) {
+ mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
+ }
+ proto.write(VISIBLE, mVisible);
+ proto.end(token);
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index ba40459692f7..d7ceaf792198 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -19,6 +19,13 @@ package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
+import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS;
+import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE;
+import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE;
+import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
+import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
+import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.getDefaultVisibility;
import static android.view.InsetsState.toPublicType;
@@ -28,6 +35,8 @@ import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.Log;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
@@ -319,6 +328,9 @@ public class InsetsSourceConsumer {
@VisibleForTesting(visibility = PACKAGE)
public boolean notifyAnimationFinished() {
+ if (mType == ITYPE_IME) {
+ ImeTracing.getInstance().triggerDump();
+ }
if (mPendingFrame != null) {
InsetsSource source = mState.getSource(mType);
source.setFrame(mPendingFrame);
@@ -360,4 +372,21 @@ public class InsetsSourceConsumer {
t.apply();
onPerceptible(mRequestedVisible);
}
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType));
+ proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
+ proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible);
+ if (mSourceControl != null) {
+ mSourceControl.dumpDebug(proto, SOURCE_CONTROL);
+ }
+ if (mPendingFrame != null) {
+ mPendingFrame.dumpDebug(proto, PENDING_FRAME);
+ }
+ if (mPendingVisibleFrame != null) {
+ mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME);
+ }
+ proto.end(token);
+ }
}
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 51b49214387a..5ddbd029f113 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -16,10 +16,17 @@
package android.view;
+import static android.graphics.PointProto.X;
+import static android.graphics.PointProto.Y;
+import static android.view.InsetsSourceControlProto.LEASH;
+import static android.view.InsetsSourceControlProto.POSITION;
+import static android.view.InsetsSourceControlProto.TYPE;
+
import android.annotation.Nullable;
import android.graphics.Point;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
@@ -120,4 +127,25 @@ public class InsetsSourceControl implements Parcelable {
return new InsetsSourceControl[size];
}
};
+
+ /**
+ * Export the state of {@link InsetsSourceControl} into a protocol buffer output stream.
+ *
+ * @param proto Stream to write the state to
+ * @param fieldId FieldId of InsetsSource as defined in the parent message
+ */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(TYPE, InsetsState.typeToString(mType));
+
+ final long surfaceToken = proto.start(POSITION);
+ proto.write(X, mSurfacePosition.x);
+ proto.write(Y, mSurfacePosition.y);
+ proto.end(surfaceToken);
+
+ if (mLeash != null) {
+ mLeash.dumpDebug(proto, LEASH);
+ }
+ proto.end(token);
+ }
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index f462a990e958..eabb71851303 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsStateProto.DISPLAY_FRAME;
+import static android.view.InsetsStateProto.SOURCES;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
@@ -41,6 +43,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -76,6 +79,10 @@ public class InsetsState implements Parcelable {
ITYPE_BOTTOM_GESTURES,
ITYPE_LEFT_GESTURES,
ITYPE_RIGHT_GESTURES,
+ ITYPE_TOP_MANDATORY_GESTURES,
+ ITYPE_BOTTOM_MANDATORY_GESTURES,
+ ITYPE_LEFT_MANDATORY_GESTURES,
+ ITYPE_RIGHT_MANDATORY_GESTURES,
ITYPE_TOP_TAPPABLE_ELEMENT,
ITYPE_BOTTOM_TAPPABLE_ELEMENT,
ITYPE_LEFT_DISPLAY_CUTOUT,
@@ -104,20 +111,27 @@ public class InsetsState implements Parcelable {
public static final int ITYPE_BOTTOM_GESTURES = 4;
public static final int ITYPE_LEFT_GESTURES = 5;
public static final int ITYPE_RIGHT_GESTURES = 6;
- public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7;
- public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8;
- public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9;
- public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10;
- public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11;
- public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12;
+ /** Additional gesture inset types that map into {@link Type.MANDATORY_SYSTEM_GESTURES}. */
+ public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
+ public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
+ public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
+ public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
+
+ public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 11;
+ public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 12;
+
+ public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 13;
+ public static final int ITYPE_TOP_DISPLAY_CUTOUT = 14;
+ public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 15;
+ public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 16;
/** Input method window. */
- public static final int ITYPE_IME = 13;
+ public static final int ITYPE_IME = 17;
/** Additional system decorations inset type. */
- public static final int ITYPE_CLIMATE_BAR = 14;
- public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15;
+ public static final int ITYPE_CLIMATE_BAR = 18;
+ public static final int ITYPE_EXTRA_NAVIGATION_BAR = 19;
static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
public static final int SIZE = LAST_TYPE + 1;
@@ -447,9 +461,11 @@ public class InsetsState implements Parcelable {
final ArraySet<Integer> result = new ArraySet<>();
if ((types & Type.STATUS_BARS) != 0) {
result.add(ITYPE_STATUS_BAR);
+ result.add(ITYPE_CLIMATE_BAR);
}
if ((types & Type.NAVIGATION_BARS) != 0) {
result.add(ITYPE_NAVIGATION_BAR);
+ result.add(ITYPE_EXTRA_NAVIGATION_BAR);
}
if ((types & Type.CAPTION_BAR) != 0) {
result.add(ITYPE_CAPTION_BAR);
@@ -485,6 +501,10 @@ public class InsetsState implements Parcelable {
return Type.IME;
case ITYPE_TOP_GESTURES:
case ITYPE_BOTTOM_GESTURES:
+ case ITYPE_TOP_MANDATORY_GESTURES:
+ case ITYPE_BOTTOM_MANDATORY_GESTURES:
+ case ITYPE_LEFT_MANDATORY_GESTURES:
+ case ITYPE_RIGHT_MANDATORY_GESTURES:
return Type.MANDATORY_SYSTEM_GESTURES;
case ITYPE_LEFT_GESTURES:
case ITYPE_RIGHT_GESTURES:
@@ -528,6 +548,16 @@ public class InsetsState implements Parcelable {
}
}
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ InsetsSource source = mSources[ITYPE_IME];
+ if (source != null) {
+ source.dumpDebug(proto, SOURCES);
+ }
+ mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME);
+ proto.end(token);
+ }
+
public static String typeToString(@InternalInsetsType int type) {
switch (type) {
case ITYPE_STATUS_BAR:
@@ -544,6 +574,14 @@ public class InsetsState implements Parcelable {
return "ITYPE_LEFT_GESTURES";
case ITYPE_RIGHT_GESTURES:
return "ITYPE_RIGHT_GESTURES";
+ case ITYPE_TOP_MANDATORY_GESTURES:
+ return "ITYPE_TOP_MANDATORY_GESTURES";
+ case ITYPE_BOTTOM_MANDATORY_GESTURES:
+ return "ITYPE_BOTTOM_MANDATORY_GESTURES";
+ case ITYPE_LEFT_MANDATORY_GESTURES:
+ return "ITYPE_LEFT_MANDATORY_GESTURES";
+ case ITYPE_RIGHT_MANDATORY_GESTURES:
+ return "ITYPE_RIGHT_MANDATORY_GESTURES";
case ITYPE_TOP_TAPPABLE_ELEMENT:
return "ITYPE_TOP_TAPPABLE_ELEMENT";
case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
diff --git a/core/java/android/view/KeyEvent.aidl b/core/java/android/view/KeyEvent.aidl
index dc15ecfe4cc6..1f6d16ed60e5 100644
--- a/core/java/android/view/KeyEvent.aidl
+++ b/core/java/android/view/KeyEvent.aidl
@@ -17,4 +17,4 @@
package android.view;
-parcelable KeyEvent;
+@JavaOnlyStableParcelable parcelable KeyEvent;
diff --git a/core/java/android/view/OnReceiveContentCallback.java b/core/java/android/view/OnReceiveContentCallback.java
new file mode 100644
index 000000000000..73bcb93d39d0
--- /dev/null
+++ b/core/java/android/view/OnReceiveContentCallback.java
@@ -0,0 +1,377 @@
+/*
+ * 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.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Callback for apps to implement handling for insertion of content. "Content" here refers to both
+ * text and non-text (plain/styled text, HTML, images, videos, audio files, etc).
+ *
+ * <p>This callback can be attached to different types of UI components using
+ * {@link View#setOnReceiveContentCallback}.
+ *
+ * <p>For editable {@link android.widget.TextView} components, implementations can extend from
+ * {@link android.widget.TextViewOnReceiveContentCallback} to reuse default platform behavior for
+ * handling text.
+ *
+ * <p>Example implementation:<br>
+ * <pre class="prettyprint">
+ * public class MyOnReceiveContentCallback implements OnReceiveContentCallback&lt;TextView&gt; {
+ *
+ * private static final Set&lt;String&gt; SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
+ * Set.of("image/*", "video/*"));
+ *
+ * &#64;NonNull
+ * &#64;Override
+ * public Set&lt;String&gt; getSupportedMimeTypes() {
+ * return SUPPORTED_MIME_TYPES;
+ * }
+ *
+ * &#64;Override
+ * public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
+ * // ... app-specific logic to handle the content in the payload ...
+ * }
+ * }
+ * </pre>
+ *
+ * @param <T> The type of {@link View} with which this receiver can be associated.
+ */
+public interface OnReceiveContentCallback<T extends View> {
+ /**
+ * Receive the given content.
+ *
+ * <p>This function will only be invoked if the MIME type of the content is in the set of
+ * types returned by {@link #getSupportedMimeTypes}.
+ *
+ * <p>For text, if the view has a selection, the selection should be overwritten by the clip; if
+ * there's no selection, this method should insert the content at the current cursor position.
+ *
+ * <p>For non-text content (e.g. an image), the content may be inserted inline, or it may be
+ * added as an attachment (could potentially be shown in a completely separate view).
+ *
+ * @param view The view where the content insertion was requested.
+ * @param payload The content to insert and related metadata.
+ *
+ * @return Returns true if some or all of the content is accepted for insertion. If accepted,
+ * actual insertion may be handled asynchronously in the background and may or may not result in
+ * successful insertion. For example, the app may not end up inserting an accepted item if it
+ * exceeds the app's size limit for that type of content.
+ */
+ boolean onReceiveContent(@NonNull T view, @NonNull Payload payload);
+
+ /**
+ * Returns the MIME types that can be handled by this callback.
+ *
+ * <p>The {@link #onReceiveContent} callback method will only be invoked if the MIME type of the
+ * content is in the set of supported types returned here.
+ *
+ * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
+ * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
+ * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has
+ * a {@link OnReceiveContentCallback} set and the MIME types returned from this function don't
+ * include "image/gif".
+ *
+ * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC
+ * MIME types. As a result, you should always write your MIME types with lower case letters, or
+ * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower
+ * case.</em>
+ *
+ * @param view The target view.
+ * @return An immutable set with the MIME types supported by this callback. The returned MIME
+ * types may contain wildcards such as "text/*", "image/*", etc.
+ */
+ @SuppressLint("CallbackMethodName")
+ @NonNull
+ Set<String> getSupportedMimeTypes(@NonNull T view);
+
+ /**
+ * Returns true if at least one of the MIME types of the given clip is
+ * {@link #getSupportedMimeTypes supported} by this receiver.
+ *
+ * @hide
+ */
+ default boolean supports(@NonNull T view, @NonNull ClipDescription description) {
+ for (String supportedMimeType : getSupportedMimeTypes(view)) {
+ if (description.hasMimeType(supportedMimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Holds all the relevant data for a request to {@link OnReceiveContentCallback}.
+ */
+ final class Payload {
+
+ /**
+ * Specifies the UI through which content is being inserted.
+ *
+ * @hide
+ */
+ @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD,
+ SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Source {}
+
+ /**
+ * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
+ * "Paste as plain text" action in the insertion/selection menu).
+ */
+ public static final int SOURCE_CLIPBOARD = 0;
+
+ /**
+ * Specifies that the operation was triggered from the soft keyboard (also known as input
+ * method editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard
+ * for more info.
+ */
+ public static final int SOURCE_INPUT_METHOD = 1;
+
+ /**
+ * Specifies that the operation was triggered by the drag/drop framework. See
+ * https://developer.android.com/guide/topics/ui/drag-drop for more info.
+ */
+ public static final int SOURCE_DRAG_AND_DROP = 2;
+
+ /**
+ * Specifies that the operation was triggered by the autofill framework. See
+ * https://developer.android.com/guide/topics/text/autofill for more info.
+ */
+ public static final int SOURCE_AUTOFILL = 3;
+
+ /**
+ * Specifies that the operation was triggered by a result from a
+ * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection
+ * menu.
+ */
+ public static final int SOURCE_PROCESS_TEXT = 4;
+
+ /**
+ * Returns the symbolic name of the given source.
+ *
+ * @hide
+ */
+ static String sourceToString(@Source int source) {
+ switch (source) {
+ case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD";
+ case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD";
+ case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP";
+ case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL";
+ case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT";
+ }
+ return String.valueOf(source);
+ }
+
+ /**
+ * Flags to configure the insertion behavior.
+ *
+ * @hide
+ */
+ @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flags {}
+
+ /**
+ * Flag requesting that the content should be converted to plain text prior to inserting.
+ */
+ public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;
+
+ /**
+ * Returns the symbolic names of the set flags or {@code "0"} if no flags are set.
+ *
+ * @hide
+ */
+ static String flagsToString(@Flags int flags) {
+ if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
+ return "FLAG_CONVERT_TO_PLAIN_TEXT";
+ }
+ return String.valueOf(flags);
+ }
+
+ /**
+ * The data to be inserted.
+ */
+ @NonNull private final ClipData mClip;
+
+ /**
+ * The source of the operation. See {@code SOURCE_} constants.
+ */
+ private final @Source int mSource;
+
+ /**
+ * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
+ */
+ private final @Flags int mFlags;
+
+ /**
+ * Optional http/https URI for the content that may be provided by the IME. This is only
+ * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
+ * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
+ * IME.
+ */
+ @Nullable
+ private final Uri mLinkUri;
+
+ /**
+ * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
+ * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
+ * the IME.
+ */
+ @Nullable
+ private final Bundle mExtras;
+
+ private Payload(Builder b) {
+ this.mClip = Objects.requireNonNull(b.mClip);
+ this.mSource = Preconditions.checkArgumentInRange(b.mSource, 0, SOURCE_PROCESS_TEXT,
+ "source");
+ this.mFlags = Preconditions.checkFlagsArgument(b.mFlags, FLAG_CONVERT_TO_PLAIN_TEXT);
+ this.mLinkUri = b.mLinkUri;
+ this.mExtras = b.mExtras;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "Payload{"
+ + "clip=" + mClip.getDescription()
+ + ", source=" + sourceToString(mSource)
+ + ", flags=" + flagsToString(mFlags)
+ + ", linkUri=" + mLinkUri
+ + ", extras=" + mExtras
+ + "}";
+ }
+
+ /**
+ * The data to be inserted.
+ */
+ public @NonNull ClipData getClip() {
+ return mClip;
+ }
+
+ /**
+ * The source of the operation. See {@code SOURCE_} constants.
+ */
+ public @Source int getSource() {
+ return mSource;
+ }
+
+ /**
+ * Optional flags that control the insertion behavior. See {@code FLAG_} constants.
+ */
+ public @Flags int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Optional http/https URI for the content that may be provided by the IME. This is only
+ * populated if the source is {@link #SOURCE_INPUT_METHOD} and if a non-empty
+ * {@link android.view.inputmethod.InputContentInfo#getLinkUri linkUri} was passed by the
+ * IME.
+ */
+ public @Nullable Uri getLinkUri() {
+ return mLinkUri;
+ }
+
+ /**
+ * Optional additional metadata. If the source is {@link #SOURCE_INPUT_METHOD}, this will
+ * include the {@link android.view.inputmethod.InputConnection#commitContent opts} passed by
+ * the IME.
+ */
+ public @Nullable Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Builder for {@link Payload}.
+ */
+ public static final class Builder {
+ @NonNull private final ClipData mClip;
+ private final @Source int mSource;
+ private @Flags int mFlags;
+ @Nullable private Uri mLinkUri;
+ @Nullable private Bundle mExtras;
+
+ /**
+ * Creates a new builder.
+ * @param clip The data to insert.
+ * @param source The source of the operation. See {@code SOURCE_} constants.
+ */
+ public Builder(@NonNull ClipData clip, @Source int source) {
+ mClip = clip;
+ mSource = source;
+ }
+
+ /**
+ * Sets flags that control content insertion behavior.
+ * @param flags Optional flags to configure the insertion behavior. Use 0 for default
+ * behavior. See {@code FLAG_} constants.
+ * @return this builder
+ */
+ @NonNull
+ public Builder setFlags(@Flags int flags) {
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Sets the http/https URI for the content. See
+ * {@link android.view.inputmethod.InputContentInfo#getLinkUri} for more info.
+ * @param linkUri Optional http/https URI for the content.
+ * @return this builder
+ */
+ @NonNull
+ public Builder setLinkUri(@Nullable Uri linkUri) {
+ mLinkUri = linkUri;
+ return this;
+ }
+
+ /**
+ * Sets additional metadata.
+ * @param extras Optional bundle with additional metadata.
+ * @return this builder
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * @return A new {@link Payload} instance with the data from this builder.
+ */
+ @NonNull
+ public Payload build() {
+ return new Payload(this);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 5c08fb142dca..e3129b639900 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -33,6 +33,7 @@ import static android.view.RemoteAnimationTargetProto.TASK_ID;
import static android.view.RemoteAnimationTargetProto.WINDOW_CONFIGURATION;
import android.annotation.IntDef;
+import android.app.PictureInPictureParams;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Point;
@@ -185,11 +186,20 @@ public class RemoteAnimationTarget implements Parcelable {
@UnsupportedAppUsage
public boolean isNotInRecents;
+ /**
+ * {@link PictureInPictureParams} to allow launcher to determine if an app should
+ * automatically enter PiP on swiping up to home.
+ *
+ * TODO: add this to proto dump
+ */
+ public PictureInPictureParams pictureInPictureParams;
+
public RemoteAnimationTarget(int taskId, int mode, SurfaceControl leash, boolean isTranslucent,
Rect clipRect, Rect contentInsets, int prefixOrderIndex, Point position,
Rect localBounds, Rect screenSpaceBounds,
WindowConfiguration windowConfig, boolean isNotInRecents,
- SurfaceControl startLeash, Rect startBounds) {
+ SurfaceControl startLeash, Rect startBounds,
+ PictureInPictureParams pictureInPictureParams) {
this.mode = mode;
this.taskId = taskId;
this.leash = leash;
@@ -205,6 +215,7 @@ public class RemoteAnimationTarget implements Parcelable {
this.isNotInRecents = isNotInRecents;
this.startLeash = startLeash;
this.startBounds = startBounds == null ? null : new Rect(startBounds);
+ this.pictureInPictureParams = pictureInPictureParams;
}
public RemoteAnimationTarget(Parcel in) {
@@ -223,6 +234,7 @@ public class RemoteAnimationTarget implements Parcelable {
isNotInRecents = in.readBoolean();
startLeash = in.readParcelable(null);
startBounds = in.readParcelable(null);
+ pictureInPictureParams = in.readParcelable(null);
}
@Override
@@ -247,6 +259,7 @@ public class RemoteAnimationTarget implements Parcelable {
dest.writeBoolean(isNotInRecents);
dest.writeParcelable(startLeash, 0 /* flags */);
dest.writeParcelable(startBounds, 0 /* flags */);
+ dest.writeParcelable(pictureInPictureParams, 0 /* flags */);
}
public void dump(PrintWriter pw, String prefix) {
@@ -263,6 +276,7 @@ public class RemoteAnimationTarget implements Parcelable {
pw.println();
pw.print(prefix); pw.print("windowConfiguration="); pw.println(windowConfiguration);
pw.print(prefix); pw.print("leash="); pw.println(leash);
+ pw.print(prefix); pw.print("pictureInPictureParams="); pw.println(pictureInPictureParams);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 14928cfe8f2e..ce087d1eba80 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.ColorSpace;
import android.graphics.HardwareRenderer;
@@ -66,6 +67,8 @@ public class Surface implements Parcelable {
private static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
private static native long nativeGetFromSurfaceControl(long surfaceObject,
long surfaceControlNativeObject);
+ private static native long nativeGetFromBlastBufferQueue(long surfaceObject,
+ long blastBufferQueueNativeObject);
private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
throws OutOfResourcesException;
@@ -536,6 +539,18 @@ public class Surface implements Parcelable {
}
}
+ private void updateNativeObject(long newNativeObject) {
+ synchronized (mLock) {
+ if (newNativeObject == mNativeObject) {
+ return;
+ }
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ setNativeObjectLocked(newNativeObject);
+ }
+ }
+
/**
* Copy another surface to this one. This surface now holds a reference
* to the same data as the original surface, and is -not- the owner.
@@ -559,16 +574,27 @@ public class Surface implements Parcelable {
"null SurfaceControl native object. Are you using a released SurfaceControl?");
}
long newNativeObject = nativeGetFromSurfaceControl(mNativeObject, surfaceControlPtr);
+ updateNativeObject(newNativeObject);
+ }
- synchronized (mLock) {
- if (newNativeObject == mNativeObject) {
- return;
- }
- if (mNativeObject != 0) {
- nativeRelease(mNativeObject);
- }
- setNativeObjectLocked(newNativeObject);
+ /**
+ * Update the surface if the BLASTBufferQueue IGraphicBufferProducer is different from this
+ * surface's IGraphicBufferProducer.
+ *
+ * @param queue {@link BLASTBufferQueue} to copy from.
+ * @hide
+ */
+ public void copyFrom(BLASTBufferQueue queue) {
+ if (queue == null) {
+ throw new IllegalArgumentException("queue must not be null");
+ }
+
+ long blastBufferQueuePtr = queue.mNativeObject;
+ if (blastBufferQueuePtr == 0) {
+ throw new NullPointerException("Null BLASTBufferQueue native object");
}
+ long newNativeObject = nativeGetFromBlastBufferQueue(mNativeObject, blastBufferQueuePtr);
+ updateNativeObject(newNativeObject);
}
/**
@@ -737,7 +763,7 @@ public class Surface implements Parcelable {
* Set the scaling mode to be used for this surfaces buffers
* @hide
*/
- void setScalingMode(@ScalingMode int scalingMode) {
+ public void setScalingMode(@ScalingMode int scalingMode) {
synchronized (mLock) {
checkNotReleasedLocked();
int err = nativeSetScalingMode(mNativeObject, scalingMode);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 907f6d8a652a..b85885c345f9 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -182,16 +182,11 @@ public final class SurfaceControl implements Parcelable {
IBinder displayToken, int mode);
private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
long barrierObject, long frame);
- private static native void nativeDeferTransactionUntilSurface(long transactionObj,
- long nativeObject,
- long surfaceObject, long frame);
private static native void nativeReparentChildren(long transactionObj, long nativeObject,
long newParentObject);
private static native void nativeReparent(long transactionObj, long nativeObject,
long newParentNativeObject);
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
- private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
- int scalingMode);
private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
@@ -225,6 +220,8 @@ public final class SurfaceControl implements Parcelable {
int transformHint);
private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
IBinder focusedToken, int displayId);
+ private static native void nativeSetFrameTimelineVsync(long transactionObj,
+ long frameTimelineVsyncId);
@Nullable
@GuardedBy("mLock")
@@ -268,7 +265,7 @@ public final class SurfaceControl implements Parcelable {
private WeakReference<View> mLocalOwnerView;
- static Transaction sGlobalTransaction;
+ static GlobalTransactionWrapper sGlobalTransaction;
static long sTransactionNestCount = 0;
/**
@@ -316,11 +313,19 @@ public final class SurfaceControl implements Parcelable {
public static final int HIDDEN = 0x00000004;
/**
- * Surface creation flag: The surface contains secure content, special
- * measures will be taken to disallow the surface's content to be copied
- * from another process. In particular, screenshots and VNC servers will
- * be disabled, but other measures can take place, for instance the
- * surface might not be hardware accelerated.
+ * Surface creation flag: Skip this layer and its children when taking a screenshot. This
+ * also includes mirroring and screen recording, so the layers with flag SKIP_SCREENSHOT
+ * will not be included on non primary displays.
+ * @hide
+ */
+ public static final int SKIP_SCREENSHOT = 0x00000040;
+
+ /**
+ * Surface creation flag: Special measures will be taken to disallow the surface's content to
+ * be copied. In particular, screenshots and secondary, non-secure displays will render black
+ * content instead of the surface content.
+ *
+ * @see #createDisplay(String, boolean)
* @hide
*/
public static final int SECURE = 0x00000080;
@@ -485,15 +490,6 @@ public final class SurfaceControl implements Parcelable {
public static final int POWER_MODE_ON_SUSPEND = 4;
/**
- * A value for windowType used to indicate that the window should be omitted from screenshots
- * and display mirroring. A temporary workaround until we express such things with
- * the hierarchy.
- * TODO: b/64227542
- * @hide
- */
- public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
-
- /**
* internal representation of how to interpret pixel value, used only to convert to ColorSpace.
*/
private static final int INTERNAL_DATASPACE_SRGB = 142671872;
@@ -1458,7 +1454,7 @@ public final class SurfaceControl implements Parcelable {
public static void openTransaction() {
synchronized (SurfaceControl.class) {
if (sGlobalTransaction == null) {
- sGlobalTransaction = new Transaction();
+ sGlobalTransaction = new GlobalTransactionWrapper();
}
synchronized(SurfaceControl.class) {
sTransactionNestCount++;
@@ -1492,7 +1488,7 @@ public final class SurfaceControl implements Parcelable {
} else if (--sTransactionNestCount > 0) {
return;
}
- sGlobalTransaction.apply();
+ sGlobalTransaction.applyGlobalTransaction(false);
}
}
@@ -1526,16 +1522,6 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
- public void setOverrideScalingMode(int scalingMode) {
- checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage
public void setLayer(int zorder) {
checkNotReleased();
@@ -2560,7 +2546,10 @@ public final class SurfaceControl implements Parcelable {
nativeApplyTransaction(mNativeObject, sync);
}
- private void applyResizedSurfaces() {
+ /**
+ * @hide
+ */
+ protected void applyResizedSurfaces() {
for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) {
final Point size = mResizedSurfaces.valueAt(i);
final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i);
@@ -2572,7 +2561,10 @@ public final class SurfaceControl implements Parcelable {
mResizedSurfaces.clear();
}
- private void notifyReparentedSurfaces() {
+ /**
+ * @hide
+ */
+ protected void notifyReparentedSurfaces() {
final int reparentCount = mReparentedSurfaces.size();
for (int i = reparentCount - 1; i >= 0; i--) {
final SurfaceControl child = mReparentedSurfaces.keyAt(i);
@@ -2949,22 +2941,6 @@ public final class SurfaceControl implements Parcelable {
/**
* @hide
*/
- @Deprecated
- @UnsupportedAppUsage
- public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
- long frameNumber) {
- if (frameNumber < 0) {
- return this;
- }
- checkPreconditions(sc);
- nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
- barrierSurface.mNativeObject, frameNumber);
- return this;
- }
-
- /**
- * @hide
- */
public Transaction reparentChildren(SurfaceControl sc, SurfaceControl newParent) {
checkPreconditions(sc);
nativeReparentChildren(mNativeObject, sc.mNativeObject, newParent.mNativeObject);
@@ -3004,16 +2980,6 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * @hide
- */
- public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
- checkPreconditions(sc);
- nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
- overrideScalingMode);
- return this;
- }
-
- /**
* Fills the surface with the specified color.
* @param color A float array with three values to represent r, g, b in range [0..1]. An
* invalid color will remove the color fill.
@@ -3290,6 +3256,22 @@ public final class SurfaceControl implements Parcelable {
return this;
}
+ /**
+ * Adds or removes the flag SKIP_SCREENSHOT of the surface. Setting the flag is equivalent
+ * to creating the Surface with the {@link #SKIP_SCREENSHOT} flag.
+ *
+ * @hide
+ */
+ public Transaction setSkipScreenshot(SurfaceControl sc, boolean skipScrenshot) {
+ checkPreconditions(sc);
+ if (skipScrenshot) {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, SKIP_SCREENSHOT, SKIP_SCREENSHOT);
+ } else {
+ nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SKIP_SCREENSHOT);
+ }
+ return this;
+ }
+
/**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
@@ -3329,6 +3311,19 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * Sets the frame timeline vsync id received from choreographer
+ * {@link Choreographer#getVsyncId()} that corresponds to the transaction submitted on that
+ * surface control.
+ *
+ * @hide
+ */
+ @NonNull
+ public Transaction setFrameTimelineVsync(long frameTimelineVsyncId) {
+ nativeSetFrameTimelineVsync(mNativeObject, frameTimelineVsyncId);
+ return this;
+ }
+
+ /**
* Writes the transaction to parcel, clearing the transaction as if it had been applied so
* it can be used to store future transactions. It's the responsibility of the parcel
* reader to apply the original transaction.
@@ -3395,6 +3390,26 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * As part of eliminating usage of the global Transaction we expose
+ * a SurfaceControl.getGlobalTransaction function. However calling
+ * apply on this global transaction (rather than using closeTransaction)
+ * would be very dangerous. So for the global transaction we use this
+ * subclass of Transaction where the normal apply throws an exception.
+ */
+ private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
+ void applyGlobalTransaction(boolean sync) {
+ applyResizedSurfaces();
+ notifyReparentedSurfaces();
+ nativeApplyTransaction(mNativeObject, sync);
+ }
+
+ @Override
+ public void apply(boolean sync) {
+ throw new RuntimeException("Global transaction must be applied from closeTransaction");
+ }
+ }
+
+ /**
* Acquire a frame rate flexibility token, which allows surface flinger to freely switch display
* frame rates. This is used by CTS tests to put the device in a consistent state. See
* ISurfaceComposer::acquireFrameRateFlexibilityToken(). The caller must have the
@@ -3414,4 +3429,17 @@ public final class SurfaceControl implements Parcelable {
public static void releaseFrameRateFlexibilityToken(long token) {
nativeReleaseFrameRateFlexibilityToken(token);
}
+
+ /**
+ * This is a refactoring utility function to enable lower levels of code to be refactored
+ * from using the global transaction (and instead use a passed in Transaction) without
+ * having to refactor the higher levels at the same time.
+ * The returned global transaction can't be applied, it must be applied from closeTransaction
+ * Unless you are working on removing Global Transaction usage in the WindowManager, this
+ * probably isn't a good function to use.
+ * @hide
+ */
+ public static Transaction getGlobalTransaction() {
+ return sGlobalTransaction;
+ }
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 71c74809f7a2..ec42f55871fc 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -23,8 +23,10 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
+import android.graphics.BLASTBufferQueue;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -41,6 +43,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
@@ -232,6 +235,19 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
SurfaceControlViewHost.SurfacePackage mSurfacePackage;
+ /**
+ * Returns {@code true} if buffers should be submitted via blast
+ */
+ private static boolean useBlastAdapter(Context context) {
+ ContentResolver contentResolver = context.getContentResolver();
+ return Settings.Global.getInt(contentResolver,
+ Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_SV, 0 /* default */) == 1;
+ }
+
+ private final boolean mUseBlastAdapter;
+ private SurfaceControl mBlastSurfaceControl;
+ private BLASTBufferQueue mBlastBufferQueue;
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -252,6 +268,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
public SurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes, boolean disableBackgroundLayer) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mUseBlastAdapter = useBlastAdapter(context);
mRenderNode.addPositionUpdateListener(mPositionListener);
setWillNotDraw(true);
@@ -420,8 +437,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
final boolean useBLAST = viewRoot.useBLAST();
viewRoot.registerRtFrameCallback(frame -> {
try {
- final SurfaceControl.Transaction t = useBLAST ?
- viewRoot.getBLASTSyncTransaction() : new SurfaceControl.Transaction();
synchronized (mSurfaceControlLock) {
if (!parent.isValid()) {
if (DEBUG) {
@@ -443,16 +458,22 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
Log.d(TAG, System.identityHashCode(this)
+ " updateSurfaceAlpha RT: set alpha=" + alpha);
}
- t.setAlpha(mSurfaceControl, alpha);
- if (!useBLAST) {
+ if (useBLAST) {
+ synchronized (viewRoot.getBlastTransactionLock()) {
+ viewRoot.getBLASTSyncTransaction()
+ .setAlpha(mSurfaceControl, alpha);
+ }
+ } else {
+ Transaction t = new SurfaceControl.Transaction();
+ t.setAlpha(mSurfaceControl, alpha);
t.deferTransactionUntil(mSurfaceControl,
viewRoot.getRenderSurfaceControl(), frame);
+ t.apply();
}
}
// It's possible that mSurfaceControl is released in the UI thread before
// the transaction completes. If that happens, an exception is thrown, which
// must be caught immediately.
- t.apply();
} catch (Exception e) {
Log.e(TAG, System.identityHashCode(this)
+ "updateSurfaceAlpha RT: Exception during surface transaction", e);
@@ -527,7 +548,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mRequestedVisible = false;
updateSurface();
- releaseSurfaces();
+ tryReleaseSurfaces();
// We don't release this as part of releaseSurfaces as
// that is also called on transient visibility changes. We can't
@@ -793,23 +814,26 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
final boolean useBLAST = viewRoot.useBLAST();
viewRoot.registerRtFrameCallback(frame -> {
try {
- final SurfaceControl.Transaction t = useBLAST
- ? viewRoot.getBLASTSyncTransaction()
- : new SurfaceControl.Transaction();
synchronized (mSurfaceControlLock) {
if (!parent.isValid() || mSurfaceControl == null) {
return;
}
- updateRelativeZ(t);
- if (!useBLAST) {
+
+ if (useBLAST) {
+ synchronized (viewRoot.getBlastTransactionLock()) {
+ updateRelativeZ(viewRoot.getBLASTSyncTransaction());
+ }
+ } else {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ updateRelativeZ(t);
t.deferTransactionUntil(mSurfaceControl,
viewRoot.getRenderSurfaceControl(), frame);
+ t.apply();
}
}
// It's possible that mSurfaceControl is released in the UI thread before
// the transaction completes. If that happens, an exception is thrown, which
// must be caught immediately.
- t.apply();
} catch (Exception e) {
Log.e(TAG, System.identityHashCode(this)
+ "setZOrderOnTop RT: Exception during surface transaction", e);
@@ -888,7 +912,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
return t;
}
- private void releaseSurfaces() {
+ private void tryReleaseSurfaces() {
mSurfaceAlpha = 1f;
synchronized (mSurfaceControlLock) {
@@ -899,18 +923,30 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
return;
}
- if (mSurfaceControl != null) {
- mTmpTransaction.remove(mSurfaceControl);
- mSurfaceControl = null;
- }
- if (mBackgroundControl != null) {
- mTmpTransaction.remove(mBackgroundControl);
- mBackgroundControl = null;
- }
+ releaseSurfaces(mTmpTransaction);
mTmpTransaction.apply();
}
}
+ private void releaseSurfaces(Transaction transaction) {
+ if (mSurfaceControl != null) {
+ transaction.remove(mSurfaceControl);
+ mSurfaceControl = null;
+ }
+ if (mBackgroundControl != null) {
+ transaction.remove(mBackgroundControl);
+ mBackgroundControl = null;
+ }
+ if (mBlastSurfaceControl != null) {
+ transaction.remove(mBlastSurfaceControl);
+ mBlastSurfaceControl = null;
+ }
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.destroy();
+ mBlastBufferQueue = null;
+ }
+ }
+
/** @hide */
protected void updateSurface() {
if (!mHaveFrame) {
@@ -927,7 +963,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
notifySurfaceDestroyed();
- releaseSurfaces();
+ tryReleaseSurfaces();
return;
}
@@ -992,45 +1028,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
if (creating) {
- mDeferredDestroySurfaceControl = mSurfaceControl;
-
updateOpaqueFlag();
- // SurfaceView hierarchy
- // ViewRootImpl surface
- // - bounds layer (crops all child surfaces to parent surface insets)
- // - SurfaceView surface (drawn relative to ViewRootImpl surface)
- // - Background color layer (drawn behind all SurfaceView surfaces)
- //
- // The bounds layer is used to crop the surface view so it does not draw into
- // the parent surface inset region. Since there can be multiple surface views
- // below or above the parent surface, one option is to create multiple bounds
- // layer for each z order. The other option, the one implement is to create
- // a single bounds layer and set z order for each child surface relative to the
- // parent surface.
- // When creating the surface view, we parent it to the bounds layer and then
- // set the relative z order. When the parent surface changes, we have to
- // make sure to update the relative z via ViewRootImpl.SurfaceChangedCallback.
- final String name = "SurfaceView - " + viewRoot.getTitle().toString();
-
- mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
- .setName(name)
- .setLocalOwnerView(this)
- .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
- .setFormat(mFormat)
- .setParent(viewRoot.getBoundsLayer())
- .setFlags(mSurfaceFlags)
- .setCallsite("SurfaceView.updateSurface")
- .build();
- mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
- .setName("Background for -" + name)
- .setLocalOwnerView(this)
- .setOpaque(true)
- .setColorLayer()
- .setParent(mSurfaceControl)
- .setCallsite("SurfaceView.updateSurface")
- .build();
-
+ mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot);
} else if (mSurfaceControl == null) {
return;
}
@@ -1103,8 +1102,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
mTmpTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
if (sizeChanged && !creating) {
- mTmpTransaction.setBufferSize(mSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
+ setBufferSize(mTmpTransaction);
}
mTmpTransaction.apply();
@@ -1146,19 +1144,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
notifySurfaceDestroyed();
}
- if (creating) {
- mSurface.copyFrom(mSurfaceControl);
- }
-
- if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
- < Build.VERSION_CODES.O) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- mSurface.createFrom(mSurfaceControl);
- }
+ copySurface(creating /* surfaceControlCreated */, sizeChanged);
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
@@ -1202,7 +1188,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
} finally {
mIsCreating = false;
if (mSurfaceControl != null && !mSurfaceCreated) {
- releaseSurfaces();
+ tryReleaseSurfaces();
}
}
} catch (Exception ex) {
@@ -1215,6 +1201,119 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
}
+ /**
+ * Copy the Surface from the SurfaceControl or the blast adapter.
+ *
+ * @param surfaceControlCreated true if we created the SurfaceControl and need to update our
+ * Surface if needed.
+ * @param bufferSizeChanged true if the BufferSize has changed and we need to recreate the
+ * Surface for compatibility reasons.
+ */
+ private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
+ if (surfaceControlCreated) {
+ if (mUseBlastAdapter) {
+ mSurface.copyFrom(mBlastBufferQueue);
+ } else {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+ }
+
+ if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ if (mUseBlastAdapter) {
+ mSurface.transferFrom(mBlastBufferQueue.createSurface());
+ } else {
+ mSurface.createFrom(mSurfaceControl);
+ }
+ }
+ }
+
+ private void setBufferSize(Transaction transaction) {
+ if (mUseBlastAdapter) {
+ mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ } else {
+ transaction.setBufferSize(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
+ }
+
+ /**
+ * Creates the surface control hierarchy as follows
+ * ViewRootImpl surface
+ * bounds layer (crops all child surfaces to parent surface insets)
+ * * SurfaceView surface (drawn relative to ViewRootImpl surface)
+ * * Blast surface (if enabled)
+ * * Background color layer (drawn behind all SurfaceView surfaces)
+ *
+ * The bounds layer is used to crop the surface view so it does not draw into the parent
+ * surface inset region. Since there can be multiple surface views below or above the parent
+ * surface, one option is to create multiple bounds layer for each z order. The other option,
+ * the one implement is to create a single bounds layer and set z order for each child surface
+ * relative to the parent surface.
+ * When creating the surface view, we parent it to the bounds layer and then set the relative z
+ * order. When the parent surface changes, we have to make sure to update the relative z via
+ * ViewRootImpl.SurfaceChangedCallback.
+ *
+ * @return previous SurfaceControl where the content was rendered. In the surface is switched
+ * out, the old surface can be persevered until the new one has drawn by keeping the reference
+ * of the old SurfaceControl alive.
+ */
+ private SurfaceControl createSurfaceControls(ViewRootImpl viewRoot) {
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+
+ SurfaceControl.Builder builder = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name)
+ .setLocalOwnerView(this)
+ .setParent(viewRoot.getBoundsLayer())
+ .setCallsite("SurfaceView.updateSurface");
+
+ final SurfaceControl previousSurfaceControl;
+ if (mUseBlastAdapter) {
+ mSurfaceControl = builder
+ .setContainerLayer()
+ .build();
+ previousSurfaceControl = mBlastSurfaceControl;
+ mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName(name + "(BLAST)")
+ .setLocalOwnerView(this)
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFormat(mFormat)
+ .setParent(mSurfaceControl)
+ .setFlags(mSurfaceFlags)
+ .setHidden(false)
+ .setBLASTLayer()
+ .setCallsite("SurfaceView.updateSurface")
+ .build();
+ mBlastBufferQueue = new BLASTBufferQueue(name,
+ mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, true /* TODO */);
+ } else {
+ previousSurfaceControl = mSurfaceControl;
+ mSurfaceControl = builder
+ .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setFlags(mSurfaceFlags)
+ .setFormat(mFormat)
+ .build();
+ mBlastSurfaceControl = null;
+ mBlastBufferQueue = null;
+ }
+ mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setName("Background for " + name)
+ .setLocalOwnerView(this)
+ .setOpaque(true)
+ .setColorLayer()
+ .setParent(mSurfaceControl)
+ .setCallsite("SurfaceView.updateSurface")
+ .build();
+
+ return previousSurfaceControl;
+ }
+
private void onDrawFinished() {
if (DEBUG) {
Log.i(TAG, System.identityHashCode(this) + " "
@@ -1272,7 +1371,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
Rect position, long frameNumber) {
final ViewRootImpl viewRoot = getViewRootImpl();
- if (frameNumber > 0 && viewRoot != null && !viewRoot.isDrawingToBLASTTransaction()) {
+ if (frameNumber > 0 && viewRoot != null && !viewRoot.useBLAST()) {
t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(),
frameNumber);
}
@@ -1297,19 +1396,23 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
return mRTLastReportedPosition;
}
- private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ private void setParentSpaceRectangle(Transaction t, Rect position, long frameNumber) {
final ViewRootImpl viewRoot = getViewRootImpl();
- final boolean useBLAST = viewRoot.isDrawingToBLASTTransaction();
- final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() :
- mRtTransaction;
-
applySurfaceTransforms(mSurfaceControl, t, position, frameNumber);
+ applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface, frameNumber);
+ }
- applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface,
- frameNumber);
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ final ViewRootImpl viewRoot = getViewRootImpl();
+ final boolean useBLAST = viewRoot.useBLAST();
- if (!useBLAST) {
- t.apply();
+ if (useBLAST) {
+ synchronized (viewRoot.getBlastTransactionLock()) {
+ setParentSpaceRectangle(viewRoot.getBLASTSyncTransaction(), position, frameNumber);
+ }
+ } else {
+ setParentSpaceRectangle(mRtTransaction, position, frameNumber);
+ mRtTransaction.apply();
}
}
@@ -1360,7 +1463,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
@Override
public void positionLost(long frameNumber) {
final ViewRootImpl viewRoot = getViewRootImpl();
- boolean useBLAST = viewRoot != null && viewRoot.isDrawingToBLASTTransaction();
+ boolean useBLAST = viewRoot != null && viewRoot.useBLAST();
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
@@ -1371,38 +1474,37 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
return;
}
- final SurfaceControl.Transaction t = useBLAST ?
- (viewRoot != null ? viewRoot.getBLASTSyncTransaction() : mRtTransaction) :
- mRtTransaction;
-
/**
* positionLost can be called while UI thread is un-paused so we
* need to hold the lock here.
*/
synchronized (mSurfaceControlLock) {
- if (frameNumber > 0 && viewRoot != null && !useBLAST) {
- if (viewRoot.mSurface.isValid()) {
+ if (useBLAST) {
+ synchronized (viewRoot.getBlastTransactionLock()) {
+ viewRoot.getBLASTSyncTransaction().hide(mSurfaceControl);
+ if (mRtReleaseSurfaces) {
+ mRtReleaseSurfaces = false;
+ releaseSurfaces(viewRoot.getBLASTSyncTransaction());
+ }
+ }
+ } else {
+ if (frameNumber > 0 && viewRoot != null && viewRoot.mSurface.isValid()) {
mRtTransaction.deferTransactionUntil(mSurfaceControl,
viewRoot.getRenderSurfaceControl(), frameNumber);
}
- }
- t.hide(mSurfaceControl);
-
- if (mRtReleaseSurfaces) {
- mRtReleaseSurfaces = false;
- mRtTransaction.remove(mSurfaceControl);
- mRtTransaction.remove(mBackgroundControl);
- mSurfaceControl = null;
- mBackgroundControl = null;
+ mRtTransaction.hide(mSurfaceControl);
+ if (mRtReleaseSurfaces) {
+ mRtReleaseSurfaces = false;
+ releaseSurfaces(mRtTransaction);
+ }
+ // If we aren't using BLAST, we apply the transaction locally, otherwise we let
+ // the ViewRoot apply it for us.
+ // If the ViewRoot is null, we behave as if we aren't using BLAST so we need to
+ // apply the transaction.
+ mRtTransaction.apply();
}
mRtHandlingPositionUpdates = false;
}
-
- // If we aren't using BLAST, we apply the transaction locally, otherise we let the ViewRoot apply it for us.
- // If the ViewRoot is null, we behave as if we aren't using BLAST so we need to apply the transaction.
- if (!useBLAST || viewRoot == null) {
- mRtTransaction.apply();
- }
}
};
@@ -1722,7 +1824,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* @param p The SurfacePackage to embed.
*/
public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) {
- final SurfaceControl sc = p != null ? p.getSurfaceControl() : null;
final SurfaceControl lastSc = mSurfacePackage != null ?
mSurfacePackage.getSurfaceControl() : null;
if (mSurfaceControl != null && lastSc != null) {
@@ -1739,7 +1840,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
SurfaceControlViewHost.SurfacePackage p) {
initEmbeddedHierarchyForAccessibility(p);
final SurfaceControl sc = p.getSurfaceControl();
- t.reparent(sc, mSurfaceControl).show(sc);
+ final SurfaceControl parent;
+ if (mUseBlastAdapter) {
+ parent = mBlastSurfaceControl;
+ } else {
+ parent = mSurfaceControl;
+ }
+
+ t.reparent(sc, parent).show(sc);
}
/** @hide */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d8861fba28c4..48871c692a51 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3995,89 +3995,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the status bar is displayed in transient mode.
- */
- public static final int STATUS_BAR_TRANSIENT = 0x04000000;
-
- /**
- * @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the navigation bar is displayed in transient mode.
- */
- @UnsupportedAppUsage
- public static final int NAVIGATION_BAR_TRANSIENT = 0x08000000;
-
- /**
- * @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the hidden status bar would like to be shown.
- */
- public static final int STATUS_BAR_UNHIDE = 0x10000000;
-
- /**
- * @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the hidden navigation bar would like to be shown.
- */
- public static final int NAVIGATION_BAR_UNHIDE = 0x20000000;
-
- /**
- * @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the status bar is displayed in translucent mode.
- */
- public static final int STATUS_BAR_TRANSLUCENT = 0x40000000;
-
- /**
- * @hide
- *
- * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
- * out of the public fields to keep the undefined bits out of the developer's way.
- *
- * Flag to specify that the navigation bar is displayed in translucent mode.
- */
- public static final int NAVIGATION_BAR_TRANSLUCENT = 0x80000000;
-
- /**
- * @hide
- *
- * Makes navigation bar transparent (but not the status bar).
- */
- public static final int NAVIGATION_BAR_TRANSPARENT = 0x00008000;
-
- /**
- * @hide
- *
- * Makes status bar transparent (but not the navigation bar).
- */
- public static final int STATUS_BAR_TRANSPARENT = 0x00000008;
-
- /**
- * @hide
- *
- * Makes both status bar and navigation bar transparent.
- */
- public static final int SYSTEM_UI_TRANSPARENT = NAVIGATION_BAR_TRANSPARENT
- | STATUS_BAR_TRANSPARENT;
-
- /**
- * @hide
*/
public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x00003FF7;
@@ -4302,31 +4219,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
name = "STATUS_BAR_DISABLE_RECENT"),
@ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
equals = STATUS_BAR_DISABLE_SEARCH,
- name = "STATUS_BAR_DISABLE_SEARCH"),
- @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT,
- equals = STATUS_BAR_TRANSIENT,
- name = "STATUS_BAR_TRANSIENT"),
- @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT,
- equals = NAVIGATION_BAR_TRANSIENT,
- name = "NAVIGATION_BAR_TRANSIENT"),
- @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE,
- equals = STATUS_BAR_UNHIDE,
- name = "STATUS_BAR_UNHIDE"),
- @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE,
- equals = NAVIGATION_BAR_UNHIDE,
- name = "NAVIGATION_BAR_UNHIDE"),
- @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT,
- equals = STATUS_BAR_TRANSLUCENT,
- name = "STATUS_BAR_TRANSLUCENT"),
- @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT,
- equals = NAVIGATION_BAR_TRANSLUCENT,
- name = "NAVIGATION_BAR_TRANSLUCENT"),
- @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT,
- equals = NAVIGATION_BAR_TRANSPARENT,
- name = "NAVIGATION_BAR_TRANSPARENT"),
- @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT,
- equals = STATUS_BAR_TRANSPARENT,
- name = "STATUS_BAR_TRANSPARENT")
+ name = "STATUS_BAR_DISABLE_SEARCH")
}, formatToHexString = true)
@SystemUiVisibility
int mSystemUiVisibility;
@@ -4355,14 +4248,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
STATUS_BAR_DISABLE_CLOCK,
STATUS_BAR_DISABLE_RECENT,
STATUS_BAR_DISABLE_SEARCH,
- STATUS_BAR_TRANSIENT,
- NAVIGATION_BAR_TRANSIENT,
- STATUS_BAR_UNHIDE,
- NAVIGATION_BAR_UNHIDE,
- STATUS_BAR_TRANSLUCENT,
- NAVIGATION_BAR_TRANSLUCENT,
- NAVIGATION_BAR_TRANSPARENT,
- STATUS_BAR_TRANSPARENT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SystemUiVisibility {}
@@ -5358,6 +5243,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@InputSourceClass
int mUnbufferedInputSource = InputDevice.SOURCE_CLASS_NONE;
+ @Nullable
+ private OnReceiveContentCallback<? extends View> mOnReceiveContentCallback;
+
/**
* Simple constructor to use when creating a view from code.
*
@@ -9113,6 +9001,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Returns the callback used for handling insertion of content into this view. See
+ * {@link #setOnReceiveContentCallback} for more info.
+ *
+ * @return The callback for handling insertion of content. Returns null if no callback has been
+ * {@link #setOnReceiveContentCallback set}.
+ */
+ @Nullable
+ public OnReceiveContentCallback<? extends View> getOnReceiveContentCallback() {
+ return mOnReceiveContentCallback;
+ }
+
+ /**
+ * Sets the callback to handle insertion of content into this view.
+ *
+ * <p>Depending on the view, this callback may be invoked for scenarios such as content
+ * insertion from the IME, Autofill, etc.
+ *
+ * <p>The callback will only be invoked if the MIME type of the content is
+ * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback.
+ * If the content type is not supported by the callback, the default platform handling will be
+ * executed instead.
+ *
+ * @param callback The callback to use. This can be null to reset to the default behavior.
+ */
+ public void setOnReceiveContentCallback(
+ @Nullable OnReceiveContentCallback<? extends View> callback) {
+ mOnReceiveContentCallback = callback;
+ }
+
+ /**
* Automatically fills the content of this view with the {@code value}.
*
* <p>Views support the Autofill Framework mainly by:
@@ -15250,6 +15168,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+ * that the system has successfully initialized an {@link InputConnection} and it is ready for
+ * use.
+ *
+ * <p>The default implementation does nothing, since a view doesn't support input methods by
+ * default (see {@link #onCreateInputConnection}).
+ *
+ * @param inputConnection The {@link InputConnection} from {@link #onCreateInputConnection},
+ * after it's been fully initialized by the system.
+ * @param editorInfo The {@link EditorInfo} that was used to create the {@link InputConnection}.
+ * @param handler The dedicated {@link Handler} on which IPC method calls from input methods
+ * will be dispatched. This is the handler returned by {@link InputConnection#getHandler()}. If
+ * that method returns null, this parameter will be null also.
+ *
+ * @hide
+ */
+ public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, @Nullable Handler handler) {}
+
+ /**
+ * Called by the {@link android.view.inputmethod.InputMethodManager} to notify the application
+ * that the {@link InputConnection} has been closed.
+ *
+ * <p>The default implementation does nothing, since a view doesn't support input methods by
+ * default (see {@link #onCreateInputConnection}).
+ *
+ * <p><strong>Note:</strong> This callback is not invoked if the view is already detached when
+ * the {@link InputConnection} is closed or the connection is not valid and managed by
+ * {@link com.android.server.inputmethod.InputMethodManagerService}.
+ * TODO(b/170645312): Before un-hiding this API, handle the detached view scenario.
+ *
+ * @hide
+ */
+ public void onInputConnectionClosedInternal() {}
+
+ /**
* Called by the {@link android.view.inputmethod.InputMethodManager}
* when a view who is not the current
* input connection target is trying to make a call on the manager. The
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c505a86cb25a..6f5d3afb56ca 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -33,6 +33,23 @@ import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewRootImplProto.ADDED;
+import static android.view.ViewRootImplProto.APP_VISIBLE;
+import static android.view.ViewRootImplProto.CUR_SCROLL_Y;
+import static android.view.ViewRootImplProto.DISPLAY_ID;
+import static android.view.ViewRootImplProto.HEIGHT;
+import static android.view.ViewRootImplProto.IS_ANIMATING;
+import static android.view.ViewRootImplProto.IS_DRAWING;
+import static android.view.ViewRootImplProto.LAST_WINDOW_INSETS;
+import static android.view.ViewRootImplProto.PENDING_DISPLAY_CUTOUT;
+import static android.view.ViewRootImplProto.REMOVED;
+import static android.view.ViewRootImplProto.SCROLL_Y;
+import static android.view.ViewRootImplProto.SOFT_INPUT_MODE;
+import static android.view.ViewRootImplProto.VIEW;
+import static android.view.ViewRootImplProto.VISIBLE_RECT;
+import static android.view.ViewRootImplProto.WIDTH;
+import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
+import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
@@ -61,6 +78,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_FOCUS_CONTROLLER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INSETS_CONTROLLER;
import android.Manifest;
import android.animation.LayoutTransition;
@@ -128,6 +148,8 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.TypedValue;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.InputDevice.InputSourceClass;
import android.view.InsetsState.InternalInsetsType;
import android.view.Surface.OutOfResourcesException;
@@ -165,6 +187,7 @@ import android.window.ClientWindowFrames;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.SomeArgs;
import com.android.internal.policy.DecorView;
@@ -183,6 +206,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
@@ -340,8 +364,6 @@ public final class ViewRootImpl implements ViewParent,
final int mTargetSdkVersion;
- int mSeq;
-
@UnsupportedAppUsage
View mView;
@@ -649,32 +671,30 @@ public final class ViewRootImpl implements ViewParent,
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
static final class SystemUiVisibilityInfo {
- int seq;
int globalVisibility;
int localValue;
int localChanges;
}
- // If set, ViewRootImpl will call BLASTBufferQueue::setNextTransaction with
- // mRtBLASTSyncTransaction, prior to invoking draw. This provides a way
- // to redirect the buffers in to transactions.
+ // If set, ViewRootImpl will request a callback from HWRender when it's ready to render the next
+ // frame. This will allow VRI to call BLASTBufferQueue::setNextTransaction with
+ // mRtBLASTSyncTransaction, so the next frame submitted will be added to the
+ // mRtBLASTSyncTransaction instead of getting applied.
private boolean mNextDrawUseBLASTSyncTransaction;
- // Set when calling setNextTransaction, we can't just reuse mNextDrawUseBLASTSyncTransaction
- // because, imagine this scenario:
- // 1. First draw is using BLAST, mNextDrawUseBLAST = true
- // 2. We call perform draw and are waiting on the callback
- // 3. After the first perform draw but before the first callback and the
- // second perform draw, a second draw sets mNextDrawUseBLAST = true (it already was)
- // 4. At this point the callback fires and we set mNextDrawUseBLAST = false;
- // 5. We get to performDraw and fail to sync as we intended because mNextDrawUseBLAST
- // is now false.
- // This is why we use a two-step latch with the two booleans, one consumed from
- // performDraw and one consumed from finishBLASTSync()
- private boolean mNextReportConsumeBLAST;
- // Be very careful with the threading here. This is used from the render thread while
- // the UI thread is paused and then applied and cleared from the UI thread right after
- // draw returns.
- private SurfaceControl.Transaction mRtBLASTSyncTransaction = new SurfaceControl.Transaction();
+
+ // This is used to signal if the mRtBLASTSyncTransaction should be applied/merged. When true,
+ // it indicates mRtBLASTSyncTransaction was sent to BLASTBufferQueue::setNextTransaction.
+ // Therefore, in onFrameComplete, if mRtNextFrameReportConsumeWithBlast is true, that means
+ // mRtBLASTSyncTransaction now contains the next buffer frame to be applied.
+ private boolean mRtNextFrameReportedConsumeWithBlast;
+
+ // Be very careful with the threading here. This is used from a thread pool generated by the
+ // render thread. Therefore, it needs to be locked when updating from the thread pool since
+ // multiple threads can be accessing it. It does not need to be locked when applied or merged
+ // since that can only happen from the frame complete callback after the other callbacks have
+ // been invoked.
+ private final SurfaceControl.Transaction mRtBLASTSyncTransaction =
+ new SurfaceControl.Transaction();
// Keeps track of whether the WM requested us to use BLAST Sync when calling relayout.
// We use this to make sure we don't send the WM transactions from an internal BLAST sync
@@ -998,7 +1018,7 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
- res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
+ res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrames.frame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
@@ -1793,7 +1813,7 @@ public final class ViewRootImpl implements ViewParent,
.setParent(getRenderSurfaceControl())
.setCallsite("ViewRootImpl.getBoundsLayer")
.build();
- setBoundsLayerCrop();
+ setBoundsLayerCrop(mTransaction);
mTransaction.show(mBoundsLayer).apply();
}
return mBoundsLayer;
@@ -1809,11 +1829,11 @@ public final class ViewRootImpl implements ViewParent,
Surface ret = null;
if (mBlastBufferQueue == null) {
- mBlastBufferQueue = new BLASTBufferQueue(
+ mBlastBufferQueue = new BLASTBufferQueue(mTag,
mBlastSurfaceControl, width, height, mEnableTripleBuffering);
// We only return the Surface the first time, as otherwise
// it hasn't changed and there is no need to update.
- ret = mBlastBufferQueue.getSurface();
+ ret = mBlastBufferQueue.createSurface();
} else {
mBlastBufferQueue.update(mBlastSurfaceControl, width, height);
}
@@ -1821,25 +1841,41 @@ public final class ViewRootImpl implements ViewParent,
return ret;
}
- private void setBoundsLayerCrop() {
+ private void setBoundsLayerCrop(Transaction t) {
// mWinFrame is already adjusted for surface insets. So offset it and use it as
// the cropping bounds.
mTempBoundsRect.set(mWinFrame);
mTempBoundsRect.offsetTo(mWindowAttributes.surfaceInsets.left,
mWindowAttributes.surfaceInsets.top);
- mTransaction.setWindowCrop(mBoundsLayer, mTempBoundsRect);
+ t.setWindowCrop(mBoundsLayer, mTempBoundsRect);
}
/**
* Called after window layout to update the bounds surface. If the surface insets have changed
* or the surface has resized, update the bounds surface.
*/
- private void updateBoundsLayer() {
+ private boolean updateBoundsLayer(SurfaceControl.Transaction t) {
if (mBoundsLayer != null) {
- setBoundsLayerCrop();
- mTransaction.deferTransactionUntil(mBoundsLayer,
- getRenderSurfaceControl(), mSurface.getNextFrameNumber())
- .apply();
+ setBoundsLayerCrop(t);
+ t.deferTransactionUntil(mBoundsLayer, getRenderSurfaceControl(),
+ mSurface.getNextFrameNumber());
+ return true;
+ }
+ return false;
+ }
+
+ private void prepareSurfaces(boolean sizeChanged) {
+ final SurfaceControl.Transaction t = mTransaction;
+ final SurfaceControl sc = getRenderSurfaceControl();
+ if (!sc.isValid()) return;
+
+ boolean applyTransaction = updateBoundsLayer(t);
+ if (sizeChanged) {
+ applyTransaction = true;
+ t.setBufferSize(sc, mSurfaceSize.x, mSurfaceSize.y);
+ }
+ if (applyTransaction) {
+ t.apply();
}
}
@@ -1988,11 +2024,7 @@ public final class ViewRootImpl implements ViewParent,
mCompatibleVisibilityInfo.globalVisibility =
(mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
| (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
- if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) {
- mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
- mHandler.sendMessage(mHandler.obtainMessage(
- MSG_DISPATCH_SYSTEM_UI_VISIBILITY, mCompatibleVisibilityInfo));
- }
+ dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
if (mAttachInfo.mKeepScreenOn != oldScreenOn
|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
@@ -2045,9 +2077,30 @@ public final class ViewRootImpl implements ViewParent,
info.globalVisibility |= systemUiFlag;
info.localChanges &= ~systemUiFlag;
}
- if (mDispatchedSystemUiVisibility != info.globalVisibility) {
+ dispatchDispatchSystemUiVisibilityChanged(info);
+ }
+
+ /**
+ * If the system is forcing showing any system bar, the legacy low profile flag should be
+ * cleared for compatibility.
+ *
+ * @param showTypes {@link InsetsType types} shown by the system.
+ * @param fromIme {@code true} if the invocation is from IME.
+ */
+ private void clearLowProfileModeIfNeeded(@InsetsType int showTypes, boolean fromIme) {
+ final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+ if ((showTypes & Type.systemBars()) != 0 && !fromIme
+ && (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
+ info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE;
+ info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE;
+ dispatchDispatchSystemUiVisibilityChanged(info);
+ }
+ }
+
+ private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+ if (mDispatchedSystemUiVisibility != args.globalVisibility) {
mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, info));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
}
}
@@ -2652,12 +2705,18 @@ public final class ViewRootImpl implements ViewParent,
surfaceSizeChanged = true;
mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
}
- final boolean alwaysConsumeSystemBarsChanged =
+ final boolean alwaysConsumeSystemBarsChanged =
mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
updateColorModeIfNeeded(lp.getColorMode());
surfaceCreated = !hadSurface && mSurface.isValid();
surfaceDestroyed = hadSurface && !mSurface.isValid();
- surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId())
+ // When using Blast, the surface generation id may not change when there's a new
+ // SurfaceControl. In that case, we also check relayout flag
+ // RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
+ // SurfaceControl.
+ surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
+ || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
+ == RELAYOUT_RES_SURFACE_CHANGED)
&& mSurface.isValid();
if (cutoutChanged) {
@@ -2913,7 +2972,16 @@ public final class ViewRootImpl implements ViewParent,
}
if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
- updateBoundsLayer();
+ // If the surface has been replaced, there's a chance the bounds layer is not parented
+ // to the new layer. When updating bounds layer, also reparent to the main VRI
+ // SurfaceControl to ensure it's correctly placed in the hierarchy.
+ //
+ // This needs to be done on the client side since WMS won't reparent the children to the
+ // new surface if it thinks the app is closing. WMS gets the signal that the app is
+ // stopping, but on the client side it doesn't get stopped since it's restarted quick
+ // enough. WMS doesn't want to keep around old children since they will leak when the
+ // client creates new children.
+ prepareSurfaces(surfaceSizeChanged);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -3785,26 +3853,29 @@ public final class ViewRootImpl implements ViewParent,
commitCallbacks.get(i).run();
}
}
- });});
+ });
+ });
}
}
try {
if (mNextDrawUseBLASTSyncTransaction) {
- // TODO(b/149747443)
- // We aren't prepared to handle overlapping use of mRtBLASTSyncTransaction
- // so if we are BLAST syncing we make sure the previous draw has
- // totally finished.
- if (mAttachInfo.mThreadedRenderer != null) {
- mAttachInfo.mThreadedRenderer.pause();
- }
-
- mNextReportConsumeBLAST = true;
+ // Frame callbacks will always occur after submitting draw requests and before
+ // the draw actually occurs. This will ensure that we set the next transaction
+ // for the frame that's about to get drawn and not on a previous frame that.
+ //
+ // This is thread safe since mRtNextFrameReportConsumeWithBlast will only be
+ // modified in onFrameDraw and then again in onFrameComplete. This is to ensure the
+ // next frame completed should be reported with the blast sync transaction.
+ registerRtFrameCallback(frame -> {
+ mRtNextFrameReportedConsumeWithBlast = true;
+ if (mBlastBufferQueue != null) {
+ // We don't need to synchronize mRtBLASTSyncTransaction here since it's not
+ // being modified and only sent to BlastBufferQueue.
+ mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+ }
+ });
mNextDrawUseBLASTSyncTransaction = false;
-
- if (mBlastBufferQueue != null) {
- mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
- }
}
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
@@ -4952,6 +5023,7 @@ public final class ViewRootImpl implements ViewParent,
String.format("Calling showInsets(%d,%b) on window that no longer"
+ " has views.", msg.arg1, msg.arg2 == 1));
}
+ clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1);
mInsetsController.show(msg.arg1, msg.arg2 == 1);
break;
}
@@ -7378,7 +7450,7 @@ public final class ViewRootImpl implements ViewParent,
frameNumber = mSurface.getNextFrameNumber();
}
- int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
+ int relayoutResult = mWindowSession.relayout(mWindow, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
@@ -7508,6 +7580,38 @@ public final class ViewRootImpl implements ViewParent,
mView.debug();
}
+ /**
+ * Export the state of {@link ViewRootImpl} and other relevant classes into a protocol buffer
+ * output stream.
+ *
+ * @param proto Stream to write the state to
+ * @param fieldId FieldId of ViewRootImpl as defined in the parent message
+ */
+ @GuardedBy("this")
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(VIEW, Objects.toString(mView));
+ proto.write(DISPLAY_ID, mDisplay.getDisplayId());
+ proto.write(APP_VISIBLE, mAppVisible);
+ proto.write(HEIGHT, mHeight);
+ proto.write(WIDTH, mWidth);
+ proto.write(IS_ANIMATING, mIsAnimating);
+ mVisRect.dumpDebug(proto, VISIBLE_RECT);
+ proto.write(IS_DRAWING, mIsDrawing);
+ proto.write(ADDED, mAdded);
+ mWinFrame.dumpDebug(proto, WIN_FRAME);
+ mPendingDisplayCutout.get().dumpDebug(proto, PENDING_DISPLAY_CUTOUT);
+ proto.write(LAST_WINDOW_INSETS, Objects.toString(mLastWindowInsets));
+ proto.write(SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(mSoftInputMode));
+ proto.write(SCROLL_Y, mScrollY);
+ proto.write(CUR_SCROLL_Y, mCurScrollY);
+ proto.write(REMOVED, mRemoved);
+ mWindowAttributes.dumpDebug(proto, WINDOW_ATTRIBUTES);
+ proto.end(token);
+ mInsetsController.dumpDebug(proto, INSETS_CONTROLLER);
+ mImeFocusController.dumpDebug(proto, IME_FOCUS_CONTROLLER);
+ }
+
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
String innerPrefix = prefix + " ";
writer.println(prefix + "ViewRoot:");
@@ -8449,17 +8553,6 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessage(msg);
}
- // TODO(118118435): Remove this after migration
- public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
- int localValue, int localChanges) {
- SystemUiVisibilityInfo args = new SystemUiVisibilityInfo();
- args.seq = seq;
- args.globalVisibility = globalVisibility;
- args.localValue = localValue;
- args.localChanges = localChanges;
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
- }
-
public void dispatchCheckFocus() {
if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
@@ -9083,6 +9176,9 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void showInsets(@InsetsType int types, boolean fromIme) {
+ if (fromIme) {
+ ImeTracing.getInstance().triggerDump();
+ }
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.showInsets(types, fromIme);
@@ -9091,6 +9187,9 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void hideInsets(@InsetsType int types, boolean fromIme) {
+ if (fromIme) {
+ ImeTracing.getInstance().triggerDump();
+ }
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.hideInsets(types, fromIme);
@@ -9218,16 +9317,6 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
- int localValue, int localChanges) {
- final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility,
- localValue, localChanges);
- }
- }
-
- @Override
public void dispatchWindowShown() {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
@@ -9766,9 +9855,12 @@ public final class ViewRootImpl implements ViewParent,
private void finishBLASTSync(boolean apply) {
mSendNextFrameToWm = false;
- if (mNextReportConsumeBLAST) {
- mNextReportConsumeBLAST = false;
+ if (mRtNextFrameReportedConsumeWithBlast) {
+ mRtNextFrameReportedConsumeWithBlast = false;
+ // We don't need to synchronize mRtBLASTSyncTransaction here we're guaranteed that this
+ // is called after all onFrameDraw and after callbacks to PositionUpdateListener.
+ // Therefore, no one should be modifying this object until the next vsync.
if (apply) {
mRtBLASTSyncTransaction.apply();
} else {
@@ -9781,6 +9873,10 @@ public final class ViewRootImpl implements ViewParent,
return mRtBLASTSyncTransaction;
}
+ Object getBlastTransactionLock() {
+ return mRtBLASTSyncTransaction;
+ }
+
/**
* @hide
*/
@@ -9808,12 +9904,4 @@ public final class ViewRootImpl implements ViewParent,
boolean useBLAST() {
return mUseBLASTAdapter && !mForceDisableBLAST;
}
-
- /**
- * Returns true if we are about to or currently processing a draw directed
- * in to a BLAST transaction.
- */
- boolean isDrawingToBLASTTransaction() {
- return mNextReportConsumeBLAST;
- }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 0d62da6bc8e3..e96e98b437a1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3994,4 +3994,15 @@ public interface WindowManager extends ViewManager {
}
}
}
+
+ /**
+ * Holds the WM lock for the specified amount of milliseconds.
+ * Intended for use by the tests that need to imitate lock contention.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.INJECT_EVENTS)
+ default void holdLock(int durationMs) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 7ef7ba5304e8..b9ecff91be77 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -283,4 +283,13 @@ public final class WindowManagerImpl implements WindowManager {
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public void holdLock(int durationMs) {
+ try {
+ WindowManagerGlobal.getWindowManagerService().holdLock(durationMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b70cb015a275..dbd8184e57bb 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -129,7 +129,7 @@ public class WindowlessWindowManager implements IWindowSession {
* IWindowSession implementation.
*/
@Override
- public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
@@ -165,18 +165,18 @@ public class WindowlessWindowManager implements IWindowSession {
* IWindowSession implementation. Currently this class doesn't need to support for multi-user.
*/
@Override
- public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
- return addToDisplay(window, seq, attrs, viewVisibility, displayId,
+ return addToDisplay(window, attrs, viewVisibility, displayId,
outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls);
}
@Override
- public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq,
+ public int addToDisplayWithoutInputChannel(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
android.graphics.Rect outContentInsets, android.graphics.Rect outStableInsets,
android.view.InsetsState insetsState) {
@@ -223,7 +223,7 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public int relayout(IWindow window, int seq, WindowManager.LayoutParams inAttrs,
+ public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index d80d2300dbce..f6d6fde6435f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -199,7 +199,7 @@ import java.util.List;
* <b>Window state changed</b> - represents the event of a change to a section of
* the user interface that is visually distinct. Should be sent from either the
* root view of a window or from a view that is marked as a pane
- * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes
+ * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Note that changes
* to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br>
* <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
* <em>Properties:</em></br>
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 299ae2f0c55e..d803f8bfa6cc 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -441,8 +441,8 @@ public final class AccessibilityInteractionClient
prefetchFlags &= ~AccessibilityNodeInfo.FLAG_PREFETCH_MASK;
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final String[] packageNames;
+ final long identityToken = Binder.clearCallingIdentity();
try {
packageNames = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
@@ -501,8 +501,8 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final String[] packageNames;
+ final long identityToken = Binder.clearCallingIdentity();
try {
packageNames = connection.findAccessibilityNodeInfosByViewId(
accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
@@ -555,8 +555,8 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final String[] packageNames;
+ final long identityToken = Binder.clearCallingIdentity();
try {
packageNames = connection.findAccessibilityNodeInfosByText(
accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
@@ -608,8 +608,8 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final String[] packageNames;
+ final long identityToken = Binder.clearCallingIdentity();
try {
packageNames = connection.findFocus(accessibilityWindowId,
accessibilityNodeId, focusType, interactionId, this,
@@ -657,8 +657,8 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final String[] packageNames;
+ final long identityToken = Binder.clearCallingIdentity();
try {
packageNames = connection.focusSearch(accessibilityWindowId,
accessibilityNodeId, direction, interactionId, this,
@@ -705,8 +705,8 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final long identityToken = Binder.clearCallingIdentity();
final boolean success;
+ final long identityToken = Binder.clearCallingIdentity();
try {
success = connection.performAccessibilityAction(
accessibilityWindowId, accessibilityNodeId, action, arguments,
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 16f35ad8c24c..aed9b89747d0 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -606,7 +606,7 @@ public final class AccessibilityManager {
// it is possible that this manager is in the same process as the service but
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
try {
service.sendAccessibilityEvent(dispatchedEvent, userId);
} finally {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 44c754c21261..9ba886aba81a 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -2465,8 +2465,7 @@ public final class AutofillManager {
* {@link #STATE_UNKNOWN_COMPAT_MODE} (beucase the session was finished when the URL bar
* changed on compat mode), {@link #STATE_UNKNOWN_FAILED} (because the session was finished
* when the service failed to fullfil the request, or {@link #STATE_DISABLED_BY_SERVICE}
- * (because the autofill service or {@link #STATE_DISABLED_BY_SERVICE} (because the autofill
- * service disabled further autofill requests for the activity).
+ * (because the autofill service disabled further autofill requests for the activity).
* @param autofillableIds list of ids that could trigger autofill, use to not handle a new
* session when they're entered.
*/
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 05353651258e..16fd383fa7ff 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -643,54 +643,58 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
// change should also get get rid of the "internalNotifyXXXX" methods above
void notifyChildSessionStarted(int parentSessionId, int childSessionId,
@NonNull ContentCaptureContext clientContext) {
- sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_STARTED)
.setParentSessionId(parentSessionId).setClientContext(clientContext),
- FORCE_FLUSH);
+ FORCE_FLUSH));
}
void notifyChildSessionFinished(int parentSessionId, int childSessionId) {
- sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
- .setParentSessionId(parentSessionId), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(childSessionId, TYPE_SESSION_FINISHED)
+ .setParentSessionId(parentSessionId), FORCE_FLUSH));
}
void notifyViewAppeared(int sessionId, @NonNull ViewStructureImpl node) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
- .setViewNode(node.mNode));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_APPEARED)
+ .setViewNode(node.mNode)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewDisappeared(int sessionId, @NonNull AutofillId id) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id));
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_DISAPPEARED).setAutofillId(id)));
}
void notifyViewTextChanged(int sessionId, @NonNull AutofillId id, @Nullable CharSequence text) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED).setAutofillId(id)
- .setText(text));
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
+ .setAutofillId(id).setText(text)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewInsetsChanged(int sessionId, @NonNull Insets viewInsets) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
- .setInsets(viewInsets));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_VIEW_INSETS_CHANGED)
+ .setInsets(viewInsets)));
}
/** Public because is also used by ViewRootImpl */
public void notifyViewTreeEvent(int sessionId, boolean started) {
final int type = started ? TYPE_VIEW_TREE_APPEARING : TYPE_VIEW_TREE_APPEARED;
- sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, type), FORCE_FLUSH));
}
void notifySessionResumed(int sessionId) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_SESSION_RESUMED), FORCE_FLUSH));
}
void notifySessionPaused(int sessionId) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH);
+ mHandler.post(() -> sendEvent(
+ new ContentCaptureEvent(sessionId, TYPE_SESSION_PAUSED), FORCE_FLUSH));
}
void notifyContextUpdated(int sessionId, @Nullable ContentCaptureContext context) {
- sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
- .setClientContext(context));
+ mHandler.post(() -> sendEvent(new ContentCaptureEvent(sessionId, TYPE_CONTEXT_UPDATED)
+ .setClientContext(context)));
}
@Override
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index eef27262c699..73636f81369f 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -16,7 +16,10 @@
package android.view.inputmethod;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;
+
import android.annotation.CallSuper;
+import android.content.ClipData;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -34,6 +37,7 @@ import android.util.Log;
import android.util.LogPrinter;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.OnReceiveContentCallback;
import android.view.View;
class ComposingText implements NoCopySpan {
@@ -870,9 +874,41 @@ public class BaseInputConnection implements InputConnection {
}
/**
- * The default implementation does nothing.
+ * Default implementation which invokes the target view's {@link OnReceiveContentCallback} if
+ * it is {@link View#setOnReceiveContentCallback set} and supports the MIME type of the given
+ * content; otherwise, simply returns false.
*/
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
- return false;
+ @SuppressWarnings("unchecked") final OnReceiveContentCallback<View> receiver =
+ (OnReceiveContentCallback<View>) mTargetView.getOnReceiveContentCallback();
+ if (receiver == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Can't insert content from IME; no callback");
+ }
+ return false;
+ }
+ if (!receiver.supports(mTargetView, inputContentInfo.getDescription())) {
+ if (DEBUG) {
+ Log.d(TAG, "Can't insert content from IME; callback doesn't support MIME type: "
+ + inputContentInfo.getDescription());
+ }
+ return false;
+ }
+ if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+ try {
+ inputContentInfo.requestPermission();
+ } catch (Exception e) {
+ Log.w(TAG, "Can't insert content from IME; requestPermission() failed", e);
+ return false;
+ }
+ }
+ final ClipData clip = new ClipData(inputContentInfo.getDescription(),
+ new ClipData.Item(inputContentInfo.getContentUri()));
+ final OnReceiveContentCallback.Payload payload =
+ new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_INPUT_METHOD)
+ .setLinkUri(inputContentInfo.getLinkUri())
+ .setExtras(opts)
+ .build();
+ return receiver.onReceiveContent(mTargetView, payload);
}
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 104bc4347c29..7dbf69369996 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -17,6 +17,12 @@
package android.view.inputmethod;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.view.inputmethod.EditorInfoProto.FIELD_ID;
+import static android.view.inputmethod.EditorInfoProto.IME_OPTIONS;
+import static android.view.inputmethod.EditorInfoProto.INPUT_TYPE;
+import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME;
+import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS;
+import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -32,6 +38,7 @@ import android.text.InputType;
import android.text.ParcelableSpan;
import android.text.TextUtils;
import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
import android.view.View;
import android.view.autofill.AutofillId;
@@ -795,6 +802,26 @@ public class EditorInfo implements InputType, Parcelable {
}
/**
+ * Export the state of {@link EditorInfo} into a protocol buffer output stream.
+ *
+ * @param proto Stream to write the state to
+ * @param fieldId FieldId of ViewRootImpl as defined in the parent message
+ * @hide
+ */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(INPUT_TYPE, inputType);
+ proto.write(IME_OPTIONS, imeOptions);
+ proto.write(PRIVATE_IME_OPTIONS, privateImeOptions);
+ proto.write(PACKAGE_NAME, packageName);
+ proto.write(FIELD_ID, this.fieldId);
+ if (targetInputMethodUser != null) {
+ proto.write(TARGET_INPUT_METHOD_USER_ID, targetInputMethodUser.getIdentifier());
+ }
+ proto.end(token);
+ }
+
+ /**
* Write debug output of this object.
*/
public void dump(Printer pw, String prefix) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a3c95a916669..5785999e2672 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,17 @@ package android.view.inputmethod;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.util.imetracing.ImeTracing.PROTO_ARG;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.DISPLAY_ID;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.EDITOR_INFO;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INPUT_METHOD_MANAGER;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.VIEW_ROOT_IMPL;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientsProto.CLIENT;
+import static android.view.inputmethod.InputMethodManagerProto.ACTIVE;
+import static android.view.inputmethod.InputMethodManagerProto.CUR_ID;
+import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE;
+import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING;
import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION;
@@ -62,6 +73,8 @@ import android.util.Pools.SimplePool;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.SparseArray;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.ImeFocusController;
import android.view.ImeInsetsSourceConsumer;
@@ -564,6 +577,7 @@ public final class InputMethodManager {
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags) {
final View servedView;
+ ImeTracing.getInstance().triggerDump();
synchronized (mH) {
mCurrentTextBoxAttribute = null;
mCompletions = null;
@@ -610,7 +624,8 @@ public final class InputMethodManager {
@Override
public void startInputAsyncOnWindowFocusGain(View focusedView,
@SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
- final int startInputFlags = getStartInputFlags(focusedView, 0);
+ int startInputFlags = getStartInputFlags(focusedView, 0);
+ startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;
final ImeFocusController controller = getFocusController();
if (controller == null) {
@@ -991,6 +1006,24 @@ public final class InputMethodManager {
return;
}
closeConnection();
+
+ // Notify the app that the InputConnection was closed.
+ final View servedView = mServedView.get();
+ if (servedView != null) {
+ final Handler handler = servedView.getHandler();
+ // The handler is null if the view is already detached. When that's the case, for
+ // now, we simply don't dispatch this callback.
+ if (handler != null) {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionClosed: view=" + servedView);
+ }
+ if (handler.getLooper().isCurrentThread()) {
+ servedView.onInputConnectionClosedInternal();
+ } else {
+ handler.post(servedView::onInputConnectionClosedInternal);
+ }
+ }
+ }
}
@Override
@@ -1083,6 +1116,11 @@ public final class InputMethodManager {
mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0,
matrixValues).sendToTarget();
}
+
+ @Override
+ public void setImeTraceEnabled(boolean enabled) {
+ ImeTracing.getInstance().setEnabled(enabled);
+ }
};
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
@@ -1651,6 +1689,7 @@ public final class InputMethodManager {
* {@link #RESULT_HIDDEN}.
*/
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
+ ImeTracing.getInstance().triggerDump();
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
@@ -1756,6 +1795,7 @@ public final class InputMethodManager {
*/
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver) {
+ ImeTracing.getInstance().triggerDump();
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
@@ -1918,6 +1958,8 @@ public final class InputMethodManager {
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+ final Handler icHandler;
+ InputBindResult res = null;
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
@@ -1954,7 +1996,6 @@ public final class InputMethodManager {
mCursorCandEnd = -1;
mCursorRect.setEmpty();
mCursorAnchorInfo = null;
- final Handler icHandler;
missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
!= 0) {
@@ -1968,6 +2009,7 @@ public final class InputMethodManager {
} else {
servedContext = null;
missingMethodFlags = 0;
+ icHandler = null;
}
mServedInputConnectionWrapper = servedContext;
@@ -1975,7 +2017,7 @@ public final class InputMethodManager {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
- final InputBindResult res = mService.startInputOrWindowGainedFocus(
+ res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
@@ -2014,6 +2056,15 @@ public final class InputMethodManager {
}
}
+ // Notify the app that the InputConnection is initialized and ready for use.
+ if (ic != null && res != null && res.method != null) {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
+ + ", ic=" + ic + ", tba=" + tba + ", handler=" + icHandler);
+ }
+ view.onInputConnectionOpenedInternal(ic, tba, icHandler);
+ }
+
return true;
}
@@ -3107,6 +3158,10 @@ public final class InputMethodManager {
}
void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ if (processDump(fd, args)) {
+ return;
+ }
+
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
@@ -3201,4 +3256,74 @@ public final class InputMethodManager {
return sb.toString();
}
+
+ /**
+ * Checks the args to see if a proto-based ime dump was requested and writes the client side
+ * ime dump to the given {@link FileDescriptor}.
+ *
+ * @return {@code true} if a proto-based ime dump was requested.
+ */
+ private boolean processDump(final FileDescriptor fd, final String[] args) {
+ if (args == null) {
+ return false;
+ }
+
+ for (String arg : args) {
+ if (arg.equals(PROTO_ARG)) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ dumpProto(proto);
+ proto.flush();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Write the proto dump for all displays associated with this client.
+ *
+ * @param proto The proto stream to which the dumps are written.
+ * @hide
+ */
+ public static void dumpProto(ProtoOutputStream proto) {
+ for (int i = sInstanceMap.size() - 1; i >= 0; i--) {
+ InputMethodManager imm = sInstanceMap.valueAt(i);
+ imm.dumpDebug(proto);
+ }
+ }
+
+ /**
+ * Write the proto dump of various client side components to the provided
+ * {@link ProtoOutputStream}.
+ *
+ * @param proto The proto stream to which the dumps are written.
+ * @hide
+ */
+ @GuardedBy("mH")
+ public void dumpDebug(ProtoOutputStream proto) {
+ if (mCurMethod == null) {
+ return;
+ }
+
+ final long clientDumpToken = proto.start(CLIENT);
+ proto.write(DISPLAY_ID, mDisplayId);
+ final long token = proto.start(INPUT_METHOD_MANAGER);
+ synchronized (mH) {
+ proto.write(CUR_ID, mCurId);
+ proto.write(FULLSCREEN_MODE, mFullscreenMode);
+ proto.write(ACTIVE, mActive);
+ proto.write(SERVED_CONNECTING, mServedConnecting);
+ proto.end(token);
+ if (mCurRootView != null) {
+ mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL);
+ }
+ if (mCurrentTextBoxAttribute != null) {
+ mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO);
+ }
+ if (mImeInsetsConsumer != null) {
+ mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER);
+ }
+ }
+ proto.end(clientDumpToken);
+ }
}
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index 8224e0eb48da..12fe6263643b 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -18,6 +18,7 @@ package android.view.textservice;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Parcel;
@@ -53,6 +54,7 @@ public final class SpellCheckerSubtype implements Parcelable {
/**
* @hide
*/
+ @TestApi
public static final int SUBTYPE_ID_NONE = 0;
private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java
index e69b6e7277a8..ca529f626cfe 100644
--- a/core/java/android/view/textservice/SuggestionsInfo.java
+++ b/core/java/android/view/textservice/SuggestionsInfo.java
@@ -45,6 +45,14 @@ public final class SuggestionsInfo implements Parcelable {
* the result suggestions include highly recommended ones.
*/
public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004;
+
+ /**
+ * Flag of the attributes of the suggestions that can be obtained by
+ * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested
+ * sentence contains a grammar error.
+ */
+ public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 0x0008;
+
private final int mSuggestionsAttributes;
private final String[] mSuggestions;
private final boolean mSuggestionsAvailable;
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index acb35d63df9d..cd70a313db67 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -18,6 +18,7 @@ package android.view.textservice;
import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -259,6 +260,7 @@ public final class TextServicesManager {
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public boolean isSpellCheckerEnabled() {
try {
return mService.isSpellCheckerEnabled(mUserId);
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
index b04105ab1e75..28496c6f8a07 100644
--- a/core/java/android/webkit/PacProcessor.java
+++ b/core/java/android/webkit/PacProcessor.java
@@ -44,24 +44,17 @@ public interface PacProcessor {
}
/**
- * Returns PacProcessor instance associated with the {@link Network}.
- * The host resolution is done on this {@link Network}.
+ * Create a new PacProcessor instance.
*
- * <p> There can only be one {@link PacProcessor} instance at a time for each {@link Network}.
- * This method will create a new instance if one did not already exist, or
- * if the previous instance was released with {@link #releasePacProcessor}.
- *
- * <p> The {@link PacProcessor} instance needs to be released manually with
- * {@link #releasePacProcessor} when the associated {@link Network} goes away.
+ * <p> The created instance needs to be released manually once it is no longer needed
+ * by calling {@link #releasePacProcessor} to prevent memory leaks.
*
- * @param network a {@link Network} which this {@link PacProcessor}
- * will use for host/address resolution.
- * If {@code null} this method is equivalent to {@link #getInstance}.
- * @return {@link PacProcessor} instance for the specified network.
+ * <p> The created instance is not tied to any particular {@link Network}.
+ * To associate {@link PacProcessor} with a {@link Network} use {@link #setNetwork} method.
*/
@NonNull
- static PacProcessor getInstanceForNetwork(@Nullable Network network) {
- return WebViewFactory.getProvider().getPacProcessorForNetwork(network);
+ static PacProcessor createInstance() {
+ throw new UnsupportedOperationException("Not implemented");
}
/**
@@ -94,6 +87,18 @@ public interface PacProcessor {
}
/**
+ * Associate {@link PacProcessor} instance with the {@link Network}.
+ * Once this method returns host resolution is done on the set {@link Network}.
+
+ * @param network a {@link Network} which this {@link PacProcessor}
+ * will use for host/address resolution. If {@code null} reset
+ * {@link PacProcessor} instance so it is not associated with any {@link Network}.
+ */
+ default void setNetwork(@Nullable Network network) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ /**
* Returns a {@link Network} associated with this {@link PacProcessor}.
*
* @return an associated {@link Network} or {@code null} if a network is unspecified.
diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java
index 2e5ee041e54c..5bcfa8bb6439 100644
--- a/core/java/android/webkit/UserPackage.java
+++ b/core/java/android/webkit/UserPackage.java
@@ -34,7 +34,7 @@ public class UserPackage {
private final UserInfo mUserInfo;
private final PackageInfo mPackageInfo;
- public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.R;
+ public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.S;
public UserPackage(UserInfo user, PackageInfo packageInfo) {
this.mUserInfo = user;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 052bca57d77c..8eb1371505bf 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -704,18 +704,20 @@ public class WebView extends AbsoluteLayout
return mProvider.restoreState(inState);
}
- /**
- * Loads the given URL with the specified additional HTTP headers.
+ /**
+ * Loads the given URL with additional HTTP headers, specified as a map from
+ * name to value. Note that if this map contains any of the headers that are
+ * set by default by this WebView, such as those controlling caching, accept
+ * types or the User-Agent, their values may be overridden by this WebView's
+ * defaults.
+ * <p>
+ * Some older WebView implementations require {@code additionalHttpHeaders}
+ * to be mutable.
* <p>
* Also see compatibility note on {@link #evaluateJavascript}.
*
* @param url the URL of the resource to load
- * @param additionalHttpHeaders the additional headers to be used in the
- * HTTP request for this URL, specified as a map from name to
- * value. Note that if this map contains any of the headers
- * that are set by default by this WebView, such as those
- * controlling caching, accept types or the User-Agent, their
- * values may be overridden by this WebView's defaults.
+ * @param additionalHttpHeaders map with additional headers
*/
public void loadUrl(@NonNull String url, @NonNull Map<String, String> additionalHttpHeaders) {
checkThread();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8790bbdcd8f7..5fc93443f4b8 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -47,7 +47,7 @@ public final class WebViewFactory {
// visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote.
/** @hide */
private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webview.chromium.WebViewChromiumFactoryProviderForR";
+ "com.android.webview.chromium.WebViewChromiumFactoryProviderForS";
private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index ce999cd5e235..3d6450632e06 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -17,7 +17,6 @@
package android.webkit;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.content.Intent;
@@ -186,8 +185,7 @@ public interface WebViewFactoryProvider {
}
/**
- * Returns PacProcessor instance associated with the {@link Network}.
- * The host resolution is done on this {@link Network}.
+ * Create a new PacProcessor instance.
*
* @param network a {@link Network} which needs to be associated
* with the returned {@link PacProcessor}.
@@ -195,7 +193,7 @@ public interface WebViewFactoryProvider {
* @return the {@link PacProcessor} instance associated with {@link Network}.
*/
@NonNull
- default PacProcessor getPacProcessorForNetwork(@Nullable Network network) {
+ default PacProcessor createPacProcessor() {
throw new UnsupportedOperationException("Not implemented");
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 60f8bb7ebe6c..eaa738d59577 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP;
+
import android.R;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
@@ -96,6 +98,7 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.OnReceiveContentCallback;
import android.view.SubMenu;
import android.view.View;
import android.view.View.DragShadowBuilder;
@@ -183,6 +186,9 @@ public class Editor {
private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
+ private static final int FLAG_MISSPELLED_OR_GRAMMAR_ERROR =
+ SuggestionSpan.FLAG_MISSPELLED | SuggestionSpan.FLAG_GRAMMAR_ERROR;
+
@IntDef({MagnifierHandleTrigger.SELECTION_START,
MagnifierHandleTrigger.SELECTION_END,
MagnifierHandleTrigger.INSERTION})
@@ -1549,7 +1555,7 @@ public class Editor {
for (int i = 0; i < suggestionSpans.length; i++) {
int flags = suggestionSpans[i].getFlags();
if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
- && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) {
+ && (flags & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) == 0) {
flags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
suggestionSpans[i].setFlags(flags);
}
@@ -2851,9 +2857,11 @@ public class Editor {
try {
final int originalLength = mTextView.getText().length();
Selection.setSelection((Spannable) mTextView.getText(), offset);
- ClipData clip = event.getClipData();
- mTextView.getRichContentReceiver().onReceive(mTextView, clip,
- RichContentReceiver.SOURCE_DRAG_AND_DROP, 0);
+ final ClipData clip = event.getClipData();
+ final OnReceiveContentCallback.Payload payload =
+ new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_DRAG_AND_DROP)
+ .build();
+ mTextView.onReceiveContent(payload);
if (dragDropIntoItself) {
deleteSourceAfterLocalDrop(dragLocalState, offset, originalLength);
}
@@ -3059,8 +3067,9 @@ public class Editor {
// Remove potential misspelled flags
int suggestionSpanFlags = suggestionSpan.getFlags();
- if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
+ if ((suggestionSpanFlags & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) != 0) {
suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
+ suggestionSpanFlags &= ~SuggestionSpan.FLAG_GRAMMAR_ERROR;
suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
suggestionSpan.setFlags(suggestionSpanFlags);
}
@@ -3596,19 +3605,29 @@ public class Editor {
final int flag1 = span1.getFlags();
final int flag2 = span2.getFlags();
if (flag1 != flag2) {
- // The order here should match what is used in updateDrawState
- final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
- final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0;
- final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0;
- final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0;
- if (easy1 && !misspelled1) return -1;
- if (easy2 && !misspelled2) return 1;
- if (misspelled1) return -1;
- if (misspelled2) return 1;
+ // Compare so that the order will be: easy -> misspelled -> grammarError
+ int easy = compareFlag(SuggestionSpan.FLAG_EASY_CORRECT, flag1, flag2);
+ if (easy != 0) return easy;
+ int misspelled = compareFlag(SuggestionSpan.FLAG_MISSPELLED, flag1, flag2);
+ if (misspelled != 0) return misspelled;
+ int grammarError = compareFlag(SuggestionSpan.FLAG_GRAMMAR_ERROR, flag1, flag2);
+ if (grammarError != 0) return grammarError;
}
return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue();
}
+
+ /*
+ * Returns -1 if flags1 has flagToCompare but flags2 does not.
+ * Returns 1 if flags2 has flagToCompare but flags1 does not.
+ * Otherwise, returns 0.
+ */
+ private int compareFlag(int flagToCompare, int flags1, int flags2) {
+ boolean hasFlag1 = (flags1 & flagToCompare) != 0;
+ boolean hasFlag2 = (flags2 & flagToCompare) != 0;
+ if (hasFlag1 == hasFlag2) return 0;
+ return hasFlag1 ? -1 : 1;
+ }
}
/**
@@ -3627,8 +3646,9 @@ public class Editor {
mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start));
}
- // The suggestions are sorted according to their types (easy correction first, then
- // misspelled) and to the length of the text that they cover (shorter first).
+ // The suggestions are sorted according to their types (easy correction first,
+ // misspelled second, then grammar error) and to the length of the text that they cover
+ // (shorter first).
Arrays.sort(suggestionSpans, mSuggestionSpanComparator);
mSpansLengths.clear();
@@ -3656,7 +3676,7 @@ public class Editor {
final int spanEnd = spannable.getSpanEnd(suggestionSpan);
if (misspelledSpanInfo != null
- && (suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
+ && (suggestionSpan.getFlags() & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) != 0) {
misspelledSpanInfo.mSuggestionSpan = suggestionSpan;
misspelledSpanInfo.mSpanStart = spanStart;
misspelledSpanInfo.mSpanEnd = spanEnd;
diff --git a/core/java/android/widget/RichContentReceiver.java b/core/java/android/widget/RichContentReceiver.java
deleted file mode 100644
index 80a05622aa67..000000000000
--- a/core/java/android/widget/RichContentReceiver.java
+++ /dev/null
@@ -1,234 +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.widget;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.view.View;
-import android.view.inputmethod.InputConnection;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Set;
-
-/**
- * Callback for apps to implement handling for insertion of rich content. "Rich content" here refers
- * to both text and non-text content: plain text, styled text, HTML, images, videos, audio files,
- * etc.
- *
- * <p>This callback can be attached to different types of UI components. For editable
- * {@link TextView} components, implementations should typically wrap
- * {@link TextView#DEFAULT_RICH_CONTENT_RECEIVER}.
- *
- * <p>Example implementation:<br>
- * <pre class="prettyprint">
- * public class MyRichContentReceiver implements RichContentReceiver&lt;TextView&gt; {
- *
- * private static final Set&lt;String&gt; SUPPORTED_MIME_TYPES = Collections.unmodifiableSet(
- * Set.of("text/*", "image/gif", "image/png", "image/jpg"));
- *
- * &#64;NonNull
- * &#64;Override
- * public Set&lt;String&gt; getSupportedMimeTypes() {
- * return SUPPORTED_MIME_TYPES;
- * }
- *
- * &#64;Override
- * public boolean onReceive(@NonNull TextView textView, @NonNull ClipData clip,
- * int source, int flags) {
- * if (clip.getDescription().hasMimeType("image/*")) {
- * return receiveImage(textView, clip);
- * }
- * return TextView.DEFAULT_RICH_CONTENT_RECEIVER.onReceive(textView, clip, source);
- * }
- *
- * private boolean receiveImage(@NonNull TextView textView, @NonNull ClipData clip) {
- * // ... app-specific logic to handle the content URI in the clip ...
- * }
- * }
- * </pre>
- *
- * @param <T> The type of {@link View} with which this receiver can be associated.
- */
-@SuppressLint("CallbackMethodName")
-public interface RichContentReceiver<T extends View> {
- /**
- * Specifies the UI through which content is being inserted.
- *
- * @hide
- */
- @IntDef(prefix = {"SOURCE_"}, value = {SOURCE_CLIPBOARD, SOURCE_INPUT_METHOD,
- SOURCE_DRAG_AND_DROP, SOURCE_AUTOFILL, SOURCE_PROCESS_TEXT})
- @Retention(RetentionPolicy.SOURCE)
- @interface Source {}
-
- /**
- * Specifies that the operation was triggered by a paste from the clipboard (e.g. "Paste" or
- * "Paste as plain text" action in the insertion/selection menu).
- */
- int SOURCE_CLIPBOARD = 0;
-
- /**
- * Specifies that the operation was triggered from the soft keyboard (also known as input method
- * editor or IME). See https://developer.android.com/guide/topics/text/image-keyboard for more
- * info.
- */
- int SOURCE_INPUT_METHOD = 1;
-
- /**
- * Specifies that the operation was triggered by the drag/drop framework. See
- * https://developer.android.com/guide/topics/ui/drag-drop for more info.
- */
- int SOURCE_DRAG_AND_DROP = 2;
-
- /**
- * Specifies that the operation was triggered by the autofill framework. See
- * https://developer.android.com/guide/topics/text/autofill for more info.
- */
- int SOURCE_AUTOFILL = 3;
-
- /**
- * Specifies that the operation was triggered by a result from a
- * {@link android.content.Intent#ACTION_PROCESS_TEXT PROCESS_TEXT} action in the selection menu.
- */
- int SOURCE_PROCESS_TEXT = 4;
-
- /**
- * Flags to configure the insertion behavior.
- *
- * @hide
- */
- @IntDef(flag = true, prefix = {"FLAG_"}, value = {FLAG_CONVERT_TO_PLAIN_TEXT})
- @Retention(RetentionPolicy.SOURCE)
- @interface Flags {}
-
- /**
- * Flag for {@link #onReceive} requesting that the content should be converted to plain text
- * prior to inserting.
- */
- int FLAG_CONVERT_TO_PLAIN_TEXT = 1 << 0;
-
- /**
- * Insert the given clip.
- *
- * <p>For editable {@link TextView} components, this function will be invoked for the
- * following scenarios:
- * <ol>
- * <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the
- * insertion/selection menu)
- * <li>Content insertion from the keyboard ({@link InputConnection#commitContent})
- * <li>Drag and drop ({@link View#onDragEvent})
- * <li>Autofill, when the type for the field is
- * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT}
- * </ol>
- *
- * <p>For text, if the view has a selection, the selection should be overwritten by the
- * clip; if there's no selection, this method should insert the content at the current
- * cursor position.
- *
- * <p>For rich content (e.g. an image), this function may insert the content inline, or it may
- * add the content as an attachment (could potentially go into a completely separate view).
- *
- * <p>This function may be invoked with a clip whose MIME type is not in the list of supported
- * types returned by {@link #getSupportedMimeTypes()}. This provides the opportunity to
- * implement custom fallback logic if desired.
- *
- * @param view The view where the content insertion was requested.
- * @param clip The clip to insert.
- * @param source The trigger of the operation.
- * @param flags Optional flags to configure the insertion behavior. Use 0 for default
- * behavior. See {@code FLAG_} constants on this interface for other options.
- * @return Returns true if the clip was inserted.
- */
- boolean onReceive(@NonNull T view, @NonNull ClipData clip, @Source int source, int flags);
-
- /**
- * Returns the MIME types that can be handled by this callback.
- *
- * <p>Different platform features (e.g. pasting from the clipboard, inserting stickers from the
- * keyboard, etc) may use this function to conditionally alter their behavior. For example, the
- * keyboard may choose to hide its UI for inserting GIFs if the input field that has focus has
- * a {@link RichContentReceiver} set and the MIME types returned from this function don't
- * include "image/gif".
- *
- * @return An immutable set with the MIME types supported by this callback. The returned
- * MIME types may contain wildcards such as "text/*", "image/*", etc.
- */
- @NonNull
- Set<String> getSupportedMimeTypes();
-
- /**
- * Returns true if the MIME type of the given clip is {@link #getSupportedMimeTypes supported}
- * by this receiver.
- *
- * @hide
- */
- default boolean supports(@NonNull ClipDescription description) {
- for (String supportedMimeType : getSupportedMimeTypes()) {
- if (description.hasMimeType(supportedMimeType)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns true if this receiver {@link #getSupportedMimeTypes supports} non-text content, such
- * as images.
- *
- * @hide
- */
- default boolean supportsNonTextContent() {
- for (String supportedMimeType : getSupportedMimeTypes()) {
- if (!supportedMimeType.startsWith("text/")) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the symbolic name of the given source.
- *
- * @hide
- */
- static String sourceToString(@Source int source) {
- switch (source) {
- case SOURCE_CLIPBOARD: return "SOURCE_CLIPBOARD";
- case SOURCE_INPUT_METHOD: return "SOURCE_INPUT_METHOD";
- case SOURCE_DRAG_AND_DROP: return "SOURCE_DRAG_AND_DROP";
- case SOURCE_AUTOFILL: return "SOURCE_AUTOFILL";
- case SOURCE_PROCESS_TEXT: return "SOURCE_PROCESS_TEXT";
- }
- return String.valueOf(source);
- }
-
- /**
- * Returns the symbolic names of the set flags or {@code "0"} if no flags are set.
- *
- * @hide
- */
- static String flagsToString(@Flags int flags) {
- if ((flags & FLAG_CONVERT_TO_PLAIN_TEXT) != 0) {
- return "FLAG_CONVERT_TO_PLAIN_TEXT";
- }
- return String.valueOf(flags);
- }
-}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index d4aad752daa0..0611bea7978d 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -338,11 +338,13 @@ public class SpellChecker implements SpellCheckerSessionListener {
((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
final boolean looksLikeTypo =
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
+ final boolean looksLikeGrammarError =
+ ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR) > 0);
final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k];
//TODO: we need to change that rule for results from a sentence-level spell
// checker that will probably be in dictionary.
- if (!isInDictionary && looksLikeTypo) {
+ if (!isInDictionary && (looksLikeTypo || looksLikeGrammarError)) {
createMisspelledSuggestionSpan(
editable, suggestionsInfo, spellCheckSpan, offset, length);
} else {
@@ -482,8 +484,16 @@ public class SpellChecker implements SpellCheckerSessionListener {
suggestions = ArrayUtils.emptyArray(String.class);
}
- SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
- SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
+ final int suggestionsAttrs = suggestionsInfo.getSuggestionsAttributes();
+ int flags = SuggestionSpan.FLAG_EASY_CORRECT;
+ if ((suggestionsAttrs & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) != 0) {
+ flags |= SuggestionSpan.FLAG_MISSPELLED;
+ }
+ if ((suggestionsAttrs & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR) != 0) {
+ flags |= SuggestionSpan.FLAG_GRAMMAR_ERROR;
+ }
+ SuggestionSpan suggestionSpan =
+ new SuggestionSpan(mTextView.getContext(), suggestions, flags);
// TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface
// to share the logic of word level spell checker and sentence level spell checker
if (mIsSentenceSpellCheckSupported) {
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index b5beac99d4e2..49c409368448 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -22,7 +22,7 @@
"name": "CtsAutoFillServiceTestCases",
"options": [
{
- "include-filter": "android.autofillservice.cts.LoginActivityTest"
+ "include-filter": "android.autofillservice.cts.dropdown.LoginActivityTest"
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
@@ -36,7 +36,7 @@
"name": "CtsAutoFillServiceTestCases",
"options": [
{
- "include-filter": "android.autofillservice.cts.CheckoutActivityTest"
+ "include-filter": "android.autofillservice.cts.dropdown.CheckoutActivityTest"
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6f14dfb89e6b..52a3f4145e7e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,12 +17,15 @@
package android.widget;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_CLIPBOARD;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_PROCESS_TEXT;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
-import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT;
import android.R;
import android.annotation.CallSuper;
@@ -39,6 +42,7 @@ import android.annotation.RequiresPermission;
import android.annotation.Size;
import android.annotation.StringRes;
import android.annotation.StyleRes;
+import android.annotation.TestApi;
import android.annotation.XmlRes;
import android.app.Activity;
import android.app.PendingIntent;
@@ -149,6 +153,7 @@ import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.OnReceiveContentCallback;
import android.view.PointerIcon;
import android.view.View;
import android.view.ViewConfiguration;
@@ -426,7 +431,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* @hide
*/
- static final int PROCESS_TEXT_REQUEST_CODE = 100;
+ @TestApi
+ public static final int PROCESS_TEXT_REQUEST_CODE = 100;
/**
* Return code of {@link #doKeyDown}.
@@ -735,6 +741,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mLocalesChanged = false;
private int mTextSizeUnit = -1;
+ // True if force bold text feature is enabled. This feature makes all text bolder.
+ private boolean mForceBoldTextEnabled;
+ private Typeface mOriginalTypeface;
+
// True if setKeyListener() has been explicitly called
private boolean mListenerChanged = false;
// True if internationalized input should be used for numbers and date and time.
@@ -882,12 +892,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* The default content insertion callback used by {@link TextView}. See
- * {@link #setRichContentReceiver} for more info.
+ * {@link #setOnReceiveContentCallback} for more info.
*/
- public static final @NonNull RichContentReceiver<TextView> DEFAULT_RICH_CONTENT_RECEIVER =
- TextViewRichContentReceiver.INSTANCE;
-
- private RichContentReceiver<TextView> mRichContentReceiver = DEFAULT_RICH_CONTENT_RECEIVER;
+ private static final TextViewOnReceiveContentCallback DEFAULT_ON_RECEIVE_CONTENT_CALLBACK =
+ new TextViewOnReceiveContentCallback();
private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
private static final int DEVICE_PROVISIONED_NO = 1;
@@ -1645,6 +1653,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
attributes.mTypefaceIndex = MONOSPACE;
}
+ mForceBoldTextEnabled = getContext().getResources().getConfiguration().forceBoldText
+ == Configuration.FORCE_BOLD_TEXT_YES;
applyTextAppearance(attributes);
if (isPassword) {
@@ -2138,15 +2148,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* @hide
*/
+ @TestApi
@Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
if (result != null) {
if (isTextEditable()) {
ClipData clip = ClipData.newPlainText("", result);
- mRichContentReceiver.onReceive(this, clip, SOURCE_PROCESS_TEXT, 0);
+ OnReceiveContentCallback.Payload payload =
+ new OnReceiveContentCallback.Payload.Builder(
+ clip, SOURCE_PROCESS_TEXT)
+ .build();
+ onReceiveContent(payload);
if (mEditor != null) {
mEditor.refreshTextActionMode();
}
@@ -4267,6 +4282,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
invalidate();
}
}
+ if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_YES) {
+ mForceBoldTextEnabled = true;
+ setTypeface(getTypeface());
+ } else if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_NO
+ || newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_UNDEFINED) {
+ mForceBoldTextEnabled = false;
+ setTypeface(getTypeface());
+ }
}
/**
@@ -4418,6 +4441,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textStyle
*/
public void setTypeface(@Nullable Typeface tf) {
+ mOriginalTypeface = tf;
+ if (mForceBoldTextEnabled) {
+ int newWeight = tf != null ? tf.getWeight() + 300 : 400;
+ newWeight = Math.min(newWeight, 1000);
+ int typefaceStyle = tf != null ? tf.getStyle() : 0;
+ boolean italic = (typefaceStyle & Typeface.ITALIC) != 0;
+ tf = Typeface.create(tf, newWeight, italic);
+ }
if (mTextPaint.getTypeface() != tf) {
mTextPaint.setTypeface(tf);
@@ -4441,7 +4472,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@InspectableProperty
public Typeface getTypeface() {
- return mTextPaint.getTypeface();
+ return mOriginalTypeface;
}
/**
@@ -8722,9 +8753,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
- int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion > Build.VERSION_CODES.R) {
- outAttrs.contentMimeTypes = mRichContentReceiver.getSupportedMimeTypes()
+ // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types.
+ OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback();
+ if (receiver != null) {
+ outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this)
.toArray(new String[0]);
}
return ic;
@@ -11827,7 +11859,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this);
return;
}
- ClipData clip;
+ final ClipData clip;
if (value.isRichContent()) {
clip = value.getRichContentValue();
} else if (value.isText()) {
@@ -11837,22 +11869,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
+ " cannot be autofilled into " + this);
return;
}
- mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_AUTOFILL, 0);
+ final OnReceiveContentCallback.Payload payload =
+ new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_AUTOFILL).build();
+ onReceiveContent(payload);
}
@Override
public @AutofillType int getAutofillType() {
- if (!isTextEditable()) {
- return AUTOFILL_TYPE_NONE;
- }
- final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion <= Build.VERSION_CODES.R) {
- return AUTOFILL_TYPE_TEXT;
- }
- // TODO(b/147301047): Update autofill framework code to check the target SDK of the autofill
- // provider and force the type AUTOFILL_TYPE_TEXT for providers that target older SDKs.
- return mRichContentReceiver.supportsNonTextContent() ? AUTOFILL_TYPE_RICH_CONTENT
- : AUTOFILL_TYPE_TEXT;
+ return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
}
/**
@@ -12913,8 +12937,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (clip == null) {
return;
}
- int flags = withFormatting ? 0 : RichContentReceiver.FLAG_CONVERT_TO_PLAIN_TEXT;
- mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_CLIPBOARD, flags);
+ final OnReceiveContentCallback.Payload payload =
+ new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_CLIPBOARD)
+ .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT)
+ .build();
+ onReceiveContent(payload);
sLastCutCopyOrTextChangedTime = 0;
}
@@ -13697,43 +13724,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Returns the callback that handles insertion of content into this view (e.g. pasting from
- * the clipboard). See {@link #setRichContentReceiver} for more info.
+ * Returns the callback used for handling insertion of content into this view. See
+ * {@link #setOnReceiveContentCallback} for more info.
*
- * @return The callback that this view is using to handle insertion of content. Returns
- * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if no custom callback has been
- * {@link #setRichContentReceiver set}.
+ * @return The callback for handling insertion of content. Returns null if no callback has been
+ * {@link #setOnReceiveContentCallback set}.
*/
- @NonNull
- public RichContentReceiver<TextView> getRichContentReceiver() {
- return mRichContentReceiver;
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public OnReceiveContentCallback<TextView> getOnReceiveContentCallback() {
+ return (OnReceiveContentCallback<TextView>) super.getOnReceiveContentCallback();
}
/**
* Sets the callback to handle insertion of content into this view.
*
- * <p>"Content" and "rich content" here refers to both text and non-text: plain text, styled
- * text, HTML, images, videos, audio files, etc.
- *
- * <p>The callback configured here should typically wrap {@link #DEFAULT_RICH_CONTENT_RECEIVER}
- * to provide consistent behavior for text content.
- *
* <p>This callback will be invoked for the following scenarios:
* <ol>
* <li>Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the
* insertion/selection menu)
- * <li>Content insertion from the keyboard ({@link InputConnection#commitContent})
- * <li>Drag and drop ({@link View#onDragEvent})
- * <li>Autofill, when the type for the field is
- * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT}
+ * <li>Content insertion from the keyboard (from {@link InputConnection#commitContent})
+ * <li>Drag and drop (drop events from {@link #onDragEvent(DragEvent)})
+ * <li>Autofill (from {@link #autofill(AutofillValue)})
+ * <li>{@link Intent#ACTION_PROCESS_TEXT} replacement
* </ol>
*
- * @param receiver The callback to use. This can be {@link #DEFAULT_RICH_CONTENT_RECEIVER} to
- * reset to the default behavior.
+ * <p>The callback will only be invoked if the MIME type of the content is
+ * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback.
+ * If the content type is not supported by the callback, the default platform handling will be
+ * executed instead.
+ *
+ * @param callback The callback to use. This can be null to reset to the default behavior.
*/
- public void setRichContentReceiver(@NonNull RichContentReceiver<TextView> receiver) {
- mRichContentReceiver = Objects.requireNonNull(receiver,
- "RichContentReceiver should not be null.");
+ @Override
+ public void setOnReceiveContentCallback(
+ @Nullable OnReceiveContentCallback<? extends View> callback) {
+ super.setOnReceiveContentCallback(callback);
+ }
+
+ /**
+ * Handles the request to insert content using the configured callback or the default callback.
+ *
+ * @hide
+ */
+ void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) {
+ OnReceiveContentCallback<TextView> receiver = getOnReceiveContentCallback();
+ ClipDescription description = payload.getClip().getDescription();
+ if (receiver != null && receiver.supports(this, description)) {
+ receiver.onReceiveContent(this, payload);
+ } else {
+ DEFAULT_ON_RECEIVE_CONTENT_CALLBACK.onReceiveContent(this, payload);
+ }
}
private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) {
diff --git a/core/java/android/widget/TextViewRichContentReceiver.java b/core/java/android/widget/TextViewOnReceiveContentCallback.java
index 4f2d95466997..35618cb3d2a5 100644
--- a/core/java/android/widget/TextViewRichContentReceiver.java
+++ b/core/java/android/widget/TextViewOnReceiveContentCallback.java
@@ -16,7 +16,12 @@
package android.widget;
+import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL;
+import static android.view.OnReceiveContentCallback.Payload.SOURCE_DRAG_AND_DROP;
+
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.Context;
import android.text.Editable;
@@ -24,54 +29,45 @@ import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.Log;
+import android.view.OnReceiveContentCallback;
+import android.view.OnReceiveContentCallback.Payload.Flags;
+import android.view.OnReceiveContentCallback.Payload.Source;
import java.util.Collections;
import java.util.Set;
/**
- * Default implementation of {@link RichContentReceiver} for editable {@link TextView} components.
- * This class handles insertion of text (plain text, styled text, HTML, etc) but not images or other
- * rich content. Typically this class will be used as a delegate by custom implementations of
- * {@link RichContentReceiver}, to provide consistent behavior for insertion of text while
- * implementing custom behavior for insertion of other content (images, etc). See
- * {@link TextView#DEFAULT_RICH_CONTENT_RECEIVER}.
- *
- * @hide
+ * Default implementation of {@link android.view.OnReceiveContentCallback} for editable
+ * {@link TextView} components. This class handles insertion of text (plain text, styled text, HTML,
+ * etc) but not images or other content. This class can be used as a base class for an
+ * implementation of {@link android.view.OnReceiveContentCallback} for a {@link TextView}, to
+ * provide consistent behavior for insertion of text.
*/
-final class TextViewRichContentReceiver implements RichContentReceiver<TextView> {
- static final TextViewRichContentReceiver INSTANCE = new TextViewRichContentReceiver();
-
- private static final String LOG_TAG = "RichContentReceiver";
+public class TextViewOnReceiveContentCallback implements OnReceiveContentCallback<TextView> {
+ private static final String LOG_TAG = "OnReceiveContent";
private static final Set<String> MIME_TYPES_ALL_TEXT = Collections.singleton("text/*");
+ @SuppressLint("CallbackMethodName")
+ @NonNull
@Override
- public Set<String> getSupportedMimeTypes() {
+ public Set<String> getSupportedMimeTypes(@NonNull TextView view) {
return MIME_TYPES_ALL_TEXT;
}
@Override
- public boolean onReceive(@NonNull TextView textView, @NonNull ClipData clip,
- @Source int source, @Flags int flags) {
+ public boolean onReceiveContent(@NonNull TextView view, @NonNull Payload payload) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
- StringBuilder sb = new StringBuilder("onReceive: clip=");
- if (clip.getDescription() == null) {
- sb.append("null");
- } else {
- clip.getDescription().toShortStringTypesOnly(sb);
- }
- sb.append(", source=").append(RichContentReceiver.sourceToString(source));
- sb.append(", flags=").append(RichContentReceiver.flagsToString(flags));
- Log.d(LOG_TAG, sb.toString());
+ Log.d(LOG_TAG, "onReceive:" + payload);
}
+ ClipData clip = payload.getClip();
+ @Source int source = payload.getSource();
+ @Flags int flags = payload.getFlags();
if (source == SOURCE_AUTOFILL) {
- return onReceiveForAutofill(textView, clip, flags);
+ return onReceiveForAutofill(view, clip, flags);
}
if (source == SOURCE_DRAG_AND_DROP) {
- return onReceiveForDragAndDrop(textView, clip, flags);
- }
- if (source == SOURCE_INPUT_METHOD && !supports(clip.getDescription())) {
- return false;
+ return onReceiveForDragAndDrop(view, clip, flags);
}
// The code here follows the original paste logic from TextView:
@@ -79,8 +75,8 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView>
// In particular, multiple items within the given ClipData will trigger separate calls to
// replace/insert. This is to preserve the original behavior with respect to TextWatcher
// notifications fired from SpannableStringBuilder when replace/insert is called.
- final Editable editable = (Editable) textView.getText();
- final Context context = textView.getContext();
+ final Editable editable = (Editable) view.getText();
+ final Context context = view.getContext();
boolean didFirst = false;
for (int i = 0; i < clip.getItemCount(); i++) {
CharSequence itemText;
@@ -100,7 +96,7 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView>
}
}
}
- return didFirst;
+ return true;
}
private static void replaceSelection(@NonNull Editable editable,
@@ -128,7 +124,7 @@ final class TextViewRichContentReceiver implements RichContentReceiver<TextView>
@NonNull ClipData clip, @Flags int flags) {
final CharSequence text = coerceToText(clip, textView.getContext(), flags);
if (text.length() == 0) {
- return false;
+ return true;
}
replaceSelection((Editable) textView.getText(), text);
return true;
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index fb5d55dd2141..b484dfacbf6c 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -48,6 +48,8 @@ import com.android.internal.util.ArrayUtils;
public class ToastPresenter {
private static final String TAG = "ToastPresenter";
private static final String WINDOW_TITLE = "Toast";
+
+ // exclusively used to guarantee window timeouts
private static final long SHORT_DURATION_TIMEOUT = 4000;
private static final long LONG_DURATION_TIMEOUT = 7000;
@@ -145,7 +147,7 @@ public class ToastPresenter {
*/
private void adjustLayoutParams(WindowManager.LayoutParams params, IBinder windowToken,
int duration, int gravity, int xOffset, int yOffset, float horizontalMargin,
- float verticalMargin) {
+ float verticalMargin, boolean removeWindowAnimations) {
Configuration config = mResources.getConfiguration();
int absGravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection());
params.gravity = absGravity;
@@ -163,6 +165,10 @@ public class ToastPresenter {
params.hideTimeoutMilliseconds =
(duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
params.token = windowToken;
+
+ if (removeWindowAnimations && params.windowAnimations == R.style.Animation_Toast) {
+ params.windowAnimations = 0;
+ }
}
/**
@@ -193,16 +199,28 @@ public class ToastPresenter {
/**
* Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
+ * Uses window animations to animate the toast.
*/
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
@Nullable ITransientNotificationCallback callback) {
+ show(view, token, windowToken, duration, gravity, xOffset, yOffset, horizontalMargin,
+ verticalMargin, callback, false /* removeWindowAnimations */);
+ }
+
+ /**
+ * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
+ * Can optionally remove window animations from the toast window.
+ */
+ public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
+ int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
+ @Nullable ITransientNotificationCallback callback, boolean removeWindowAnimations) {
checkState(mView == null, "Only one toast at a time is allowed, call hide() first.");
mView = view;
mToken = token;
adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
- horizontalMargin, verticalMargin);
+ horizontalMargin, verticalMargin, removeWindowAnimations);
if (mView.getParent() != null) {
mWindowManager.removeView(mView);
}
@@ -247,7 +265,8 @@ public class ToastPresenter {
try {
callback.onToastHidden();
} catch (RemoteException e) {
- Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
+ Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()",
+ e);
}
}
mView = null;
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index 12b16ff6645c..3a84c1f98ce6 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -17,7 +17,9 @@
package android.window;
import android.app.ActivityManager;
+import android.content.pm.ParceledListSlice;
import android.window.ITaskOrganizer;
+import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -26,8 +28,11 @@ interface ITaskOrganizerController {
/**
* Register a TaskOrganizer to manage all the tasks with supported windowing modes.
+ *
+ * @return a list of the tasks that should be managed by the organizer, not including tasks
+ * created via {@link #createRootTask}.
*/
- void registerTaskOrganizer(ITaskOrganizer organizer);
+ ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer);
/**
* Unregisters a previously registered task organizer.
diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl
new file mode 100644
index 000000000000..a8a29b26a148
--- /dev/null
+++ b/core/java/android/window/ITransitionPlayer.aidl
@@ -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.window;
+
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+/**
+ * Implemented by WMShell to initiate and play transition animations.
+ * The flow (with {@link IWindowOrganizerController}) looks like this:
+ * <p><ol>
+ * <li>Core starts an activity and calls {@link #requestStartTransition}
+ * <li>This TransitionPlayer impl does whatever, then calls
+ * {@link IWindowOrganizerController#startTransition} to tell Core to formally start (until
+ * this happens, Core will collect changes on the transition, but won't consider it ready to
+ * animate).
+ * <li>Once all collected changes on the transition have finished drawing, Core will then call
+ * {@link #onTransitionReady} here to delegate the actual animation.
+ * <li>Once this TransitionPlayer impl finishes animating, it notifies Core via
+ * {@link IWindowOrganizerController#finishTransition}. At this point, ITransitionPlayer's
+ * responsibilities end.
+ * </ul>
+ *
+ * {@hide}
+ */
+oneway interface ITransitionPlayer {
+
+ /**
+ * Called when all participants of a transition are ready to animate. This is in response to
+ * {@link IWindowOrganizerController#startTransition}.
+ *
+ * @param transitionToken An identifying token for the transition that is now ready to animate.
+ * @param info A collection of all the changes encapsulated by this transition.
+ * @param t A surface transaction containing the surface state prior to animating.
+ */
+ void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
+ in SurfaceControl.Transaction t);
+
+ /**
+ * Called when something in WMCore requires a transition to play -- for example when an Activity
+ * is started in a new Task.
+ *
+ * @param type The {@link WindowManager#TransitionType} of the transition to start.
+ * @param transitionToken An identifying token for the transition that needs to be started.
+ * Pass this to {@link IWindowOrganizerController#startTransition}.
+ */
+ void requestStartTransition(int type, in IBinder transitionToken);
+}
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 7e9c783c83c6..0cd9b366bf5c 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -18,8 +18,10 @@ package android.window;
import android.view.SurfaceControl;
+import android.os.IBinder;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -45,6 +47,30 @@ interface IWindowOrganizerController {
int applySyncTransaction(in WindowContainerTransaction t,
in IWindowContainerTransactionCallback callback);
+ /**
+ * Starts a transition.
+ * @param type The transition type.
+ * @param transitionToken A token associated with the transition to start. If null, a new
+ * transition will be created of the provided type.
+ * @param t Operations that are part of the transition.
+ * @return a token representing the transition. This will just be transitionToken if it was
+ * non-null.
+ */
+ IBinder startTransition(int type, in @nullable IBinder transitionToken,
+ in @nullable WindowContainerTransaction t);
+
+ /**
+ * Finishes a transition. This must be called for all created transitions.
+ * @param transitionToken Which transition to finish
+ * @param t Changes to make before finishing but in the same SF Transaction. Can be null.
+ * @param callback Called when t is finished applying.
+ * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
+ * negative if no sync transaction was attached (null t or callback)
+ */
+ int finishTransition(in IBinder transitionToken,
+ in @nullable WindowContainerTransaction t,
+ in IWindowContainerTransactionCallback callback);
+
/** @return An interface enabling the management of task organizers. */
ITaskOrganizerController getTaskOrganizerController();
@@ -61,4 +87,10 @@ interface IWindowOrganizerController {
* @return true if the screenshot was successful, false otherwise.
*/
boolean takeScreenshot(in WindowContainerToken token, out SurfaceControl outSurfaceControl);
+
+ /**
+ * Registers a transition player with Core. There is only one of these at a time and calling
+ * this will replace the existing one if set.
+ */
+ void registerTransitionPlayer(in ITransitionPlayer player);
}
diff --git a/core/java/android/window/TaskAppearedInfo.aidl b/core/java/android/window/TaskAppearedInfo.aidl
new file mode 100644
index 000000000000..13eba25f37a3
--- /dev/null
+++ b/core/java/android/window/TaskAppearedInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.window;
+
+/**
+ * Data object for the task info provided when a task is presented to an organizer.
+ * @hide
+ */
+parcelable TaskAppearedInfo;
+
diff --git a/core/java/android/window/TaskAppearedInfo.java b/core/java/android/window/TaskAppearedInfo.java
new file mode 100644
index 000000000000..2ff331eb22e5
--- /dev/null
+++ b/core/java/android/window/TaskAppearedInfo.java
@@ -0,0 +1,86 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+
+/**
+ * Data object for the task info provided when a task is presented to an organizer.
+ * @hide
+ */
+@TestApi
+public final class TaskAppearedInfo implements Parcelable {
+
+ @NonNull
+ private final RunningTaskInfo mTaskInfo;
+
+ @NonNull
+ private final SurfaceControl mLeash;
+
+ @NonNull
+ public static final Creator<TaskAppearedInfo> CREATOR = new Creator<TaskAppearedInfo>() {
+ @Override
+ public TaskAppearedInfo createFromParcel(Parcel source) {
+ final RunningTaskInfo taskInfo = source.readTypedObject(RunningTaskInfo.CREATOR);
+ final SurfaceControl leash = source.readTypedObject(SurfaceControl.CREATOR);
+ return new TaskAppearedInfo(taskInfo, leash);
+ }
+
+ @Override
+ public TaskAppearedInfo[] newArray(int size) {
+ return new TaskAppearedInfo[size];
+ }
+
+ };
+
+ public TaskAppearedInfo(@NonNull RunningTaskInfo taskInfo, @NonNull SurfaceControl leash) {
+ mTaskInfo = taskInfo;
+ mLeash = leash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mTaskInfo, flags);
+ dest.writeTypedObject(mLeash, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @return the task info.
+ */
+ @NonNull
+ public RunningTaskInfo getTaskInfo() {
+ return mTaskInfo;
+ }
+
+ /**
+ * @return the leash for the task.
+ */
+ @NonNull
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index d8f2bb248fd1..909bb47bf1a5 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -17,6 +17,7 @@
package android.window;
import android.annotation.BinderThread;
+import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -39,22 +40,28 @@ public class TaskOrganizer extends WindowOrganizer {
private ITaskOrganizerController mTaskOrganizerController;
public TaskOrganizer() {
- mTaskOrganizerController = getController();
+ this(null);
}
/** @hide */
@VisibleForTesting
public TaskOrganizer(ITaskOrganizerController taskOrganizerController) {
- mTaskOrganizerController = taskOrganizerController;
+ mTaskOrganizerController = taskOrganizerController != null
+ ? taskOrganizerController : getController();
}
/**
* Register a TaskOrganizer to manage tasks as they enter a supported windowing mode.
+ *
+ * @return a list of the tasks that should be managed by the organizer, not including tasks
+ * created via {@link #createRootTask}.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public final void registerOrganizer() {
+ @CallSuper
+ @NonNull
+ public List<TaskAppearedInfo> registerOrganizer() {
try {
- mTaskOrganizerController.registerTaskOrganizer(mInterface);
+ return mTaskOrganizerController.registerTaskOrganizer(mInterface).getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -62,7 +69,8 @@ public class TaskOrganizer extends WindowOrganizer {
/** Unregisters a previously registered task organizer. */
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
- public final void unregisterOrganizer() {
+ @CallSuper
+ public void unregisterOrganizer() {
try {
mTaskOrganizerController.unregisterTaskOrganizer(mInterface);
} catch (RemoteException e) {
diff --git a/core/java/android/window/TransitionInfo.aidl b/core/java/android/window/TransitionInfo.aidl
new file mode 100644
index 000000000000..6c33e9737f6a
--- /dev/null
+++ b/core/java/android/window/TransitionInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.window;
+
+parcelable TransitionInfo;
+parcelable TransitionInfo.Change;
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
new file mode 100644
index 000000000000..34d1d4e8699d
--- /dev/null
+++ b/core/java/android/window/TransitionInfo.java
@@ -0,0 +1,290 @@
+/*
+ * 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.window;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used to communicate information about what is changing during a transition to a TransitionPlayer.
+ * @hide
+ */
+public final class TransitionInfo implements Parcelable {
+
+ /** No transition mode. This is a placeholder, don't use this as an actual mode. */
+ public static final int TRANSIT_NONE = 0;
+
+ /** The container didn't exist before but will exist and be visible after. */
+ public static final int TRANSIT_OPEN = 1;
+
+ /** The container existed and was visible before but won't exist after. */
+ public static final int TRANSIT_CLOSE = 2;
+
+ /** The container existed before but was invisible and will be visible after. */
+ public static final int TRANSIT_SHOW = 3;
+
+ /** The container is going from visible to invisible but it will still exist after. */
+ public static final int TRANSIT_HIDE = 4;
+
+ /** The container exists and is visible before and after but it changes. */
+ public static final int TRANSIT_CHANGE = 5;
+
+ /** @hide */
+ @IntDef(prefix = { "TRANSIT_" }, value = {
+ TRANSIT_NONE,
+ TRANSIT_OPEN,
+ TRANSIT_CLOSE,
+ TRANSIT_SHOW,
+ TRANSIT_HIDE,
+ TRANSIT_CHANGE
+ })
+ public @interface TransitionMode {}
+
+ private final @WindowManager.TransitionType int mType;
+ private final ArrayList<Change> mChanges = new ArrayList<>();
+
+ /** @hide */
+ public TransitionInfo(@WindowManager.TransitionType int type) {
+ mType = type;
+ }
+
+ private TransitionInfo(Parcel in) {
+ mType = in.readInt();
+ in.readList(mChanges, null /* classLoader */);
+ }
+
+ @Override
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeList(mChanges);
+ }
+
+ @NonNull
+ public static final Creator<TransitionInfo> CREATOR =
+ new Creator<TransitionInfo>() {
+ @Override
+ public TransitionInfo createFromParcel(Parcel in) {
+ return new TransitionInfo(in);
+ }
+
+ @Override
+ public TransitionInfo[] newArray(int size) {
+ return new TransitionInfo[size];
+ }
+ };
+
+ @Override
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ @NonNull
+ public List<Change> getChanges() {
+ return mChanges;
+ }
+
+ /**
+ * @return the Change that a window is undergoing or {@code null} if not directly
+ * represented.
+ */
+ @Nullable
+ public Change getChange(@NonNull WindowContainerToken token) {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ if (mChanges.get(i).mContainer == token) {
+ return mChanges.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a {@link Change} to this transition.
+ */
+ public void addChange(@NonNull Change change) {
+ mChanges.add(change);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{t=" + mType + " c=[");
+ for (int i = 0; i < mChanges.size(); ++i) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(mChanges.get(i));
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+
+ /** Converts a transition mode/action to its string representation. */
+ @NonNull
+ public static String modeToString(@TransitionMode int mode) {
+ switch(mode) {
+ case TRANSIT_NONE: return "NONE";
+ case TRANSIT_OPEN: return "OPEN";
+ case TRANSIT_CLOSE: return "CLOSE";
+ case TRANSIT_SHOW: return "SHOW";
+ case TRANSIT_HIDE: return "HIDE";
+ case TRANSIT_CHANGE: return "CHANGE";
+ default: return "<unknown:" + mode + ">";
+ }
+ }
+
+ /** Represents the change a WindowContainer undergoes during a transition */
+ public static final class Change implements Parcelable {
+ private final WindowContainerToken mContainer;
+ private WindowContainerToken mParent;
+ private final SurfaceControl mLeash;
+ private int mMode = TRANSIT_NONE;
+ private final Rect mStartBounds = new Rect();
+ private final Rect mEndBounds = new Rect();
+
+ public Change(@NonNull WindowContainerToken container, @NonNull SurfaceControl leash) {
+ mContainer = container;
+ mLeash = leash;
+ }
+
+ private Change(Parcel in) {
+ mContainer = WindowContainerToken.CREATOR.createFromParcel(in);
+ mParent = in.readParcelable(WindowContainerToken.class.getClassLoader());
+ mLeash = new SurfaceControl();
+ mLeash.readFromParcel(in);
+ mMode = in.readInt();
+ mStartBounds.readFromParcel(in);
+ mEndBounds.readFromParcel(in);
+ }
+
+ /** Sets the parent of this change's container. The parent must be a participant or null. */
+ public void setParent(@Nullable WindowContainerToken parent) {
+ mParent = parent;
+ }
+
+ /** Sets the transition mode for this change */
+ public void setMode(@TransitionMode int mode) {
+ mMode = mode;
+ }
+
+ /** Sets the bounds this container occupied before the change */
+ public void setStartBounds(@Nullable Rect rect) {
+ mStartBounds.set(rect);
+ }
+
+ /** Sets the bounds this container will occupy after the change */
+ public void setEndBounds(@Nullable Rect rect) {
+ mEndBounds.set(rect);
+ }
+
+ /** @return the container that is changing */
+ @NonNull
+ public WindowContainerToken getContainer() {
+ return mContainer;
+ }
+
+ /**
+ * @return the parent of the changing container. This is the parent within the participants,
+ * not necessarily the actual parent.
+ */
+ @Nullable
+ public WindowContainerToken getParent() {
+ return mParent;
+ }
+
+ /** @return which action this change represents. */
+ public @TransitionMode int getMode() {
+ return mMode;
+ }
+
+ /**
+ * @return the bounds of the container before the change. It may be empty if the container
+ * is coming into existence.
+ */
+ @NonNull
+ public Rect getStartBounds() {
+ return mStartBounds;
+ }
+
+ /**
+ * @return the bounds of the container after the change. It may be empty if the container
+ * is disappearing.
+ */
+ @NonNull
+ public Rect getEndBounds() {
+ return mEndBounds;
+ }
+
+ /** @return the leash or surface to animate for this container */
+ @NonNull
+ public SurfaceControl getLeash() {
+ return mLeash;
+ }
+
+ @Override
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mContainer.writeToParcel(dest, flags);
+ dest.writeParcelable(mParent, 0);
+ mLeash.writeToParcel(dest, flags);
+ dest.writeInt(mMode);
+ mStartBounds.writeToParcel(dest, flags);
+ mEndBounds.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public static final Creator<Change> CREATOR =
+ new Creator<Change>() {
+ @Override
+ public Change createFromParcel(Parcel in) {
+ return new Change(in);
+ }
+
+ @Override
+ public Change[] newArray(int size) {
+ return new Change[size];
+ }
+ };
+
+ @Override
+ /** @hide */
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + mContainer + "(" + mParent + ") leash=" + mLeash
+ + " m=" + modeToString(mMode) + " sb=" + mStartBounds
+ + " eb=" + mEndBounds + "}";
+ }
+ }
+}
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index c92ccae66ff8..96e8b44d13cc 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -78,6 +78,11 @@ public final class WindowContainerToken implements Parcelable {
}
@Override
+ public String toString() {
+ return "WCT{" + mRealToken + "}";
+ }
+
+ @Override
public boolean equals(Object obj) {
if (!(obj instanceof WindowContainerToken)) {
return false;
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 8be37e9e492d..eba4fd21166d 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -238,6 +238,22 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sets whether a container should ignore the orientation request from apps and windows below
+ * it. It currently only applies to {@link com.android.server.wm.DisplayArea}. When
+ * {@code false}, it may rotate based on the orientation request; When {@code true}, it can
+ * never specify orientation, but shows the fixed-orientation apps below it in the letterbox.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setIgnoreOrientationRequest(
+ @NonNull WindowContainerToken container, boolean ignoreOrientationRequest) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mIgnoreOrientationRequest = ignoreOrientationRequest;
+ chg.mChangeMask |= Change.CHANGE_IGNORE_ORIENTATION_REQUEST;
+ return this;
+ }
+
+ /**
* Reparents a container into another one. The effect of a {@code null} parent can vary. For
* example, reparenting a stack to {@code null} will reparent it to its display.
*
@@ -341,10 +357,12 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int CHANGE_PIP_CALLBACK = 1 << 2;
public static final int CHANGE_HIDDEN = 1 << 3;
public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;
+ public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
+ private boolean mIgnoreOrientationRequest = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
@@ -362,6 +380,7 @@ public final class WindowContainerTransaction implements Parcelable {
mConfiguration.readFromParcel(in);
mFocusable = in.readBoolean();
mHidden = in.readBoolean();
+ mIgnoreOrientationRequest = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -404,6 +423,9 @@ public final class WindowContainerTransaction implements Parcelable {
if ((other.mChangeMask & CHANGE_HIDDEN) != 0) {
mHidden = other.mHidden;
}
+ if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
+ mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -445,6 +467,15 @@ public final class WindowContainerTransaction implements Parcelable {
return mHidden;
}
+ /** Gets the requested state of whether to ignore orientation request. */
+ public boolean getIgnoreOrientationRequest() {
+ if ((mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) == 0) {
+ throw new RuntimeException("IgnoreOrientationRequest not set. "
+ + "Check CHANGE_IGNORE_ORIENTATION_REQUEST first");
+ }
+ return mIgnoreOrientationRequest;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -509,6 +540,9 @@ public final class WindowContainerTransaction implements Parcelable {
if (mBoundsChangeTransaction != null) {
sb.append("hasBoundsTransaction,");
}
+ if ((mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
+ sb.append("ignoreOrientationRequest:" + mIgnoreOrientationRequest + ",");
+ }
sb.append("}");
return sb.toString();
}
@@ -518,6 +552,7 @@ public final class WindowContainerTransaction implements Parcelable {
mConfiguration.writeToParcel(dest, flags);
dest.writeBoolean(mFocusable);
dest.writeBoolean(mHidden);
+ dest.writeBoolean(mIgnoreOrientationRequest);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 97a97d9984f9..5ac19fa685d7 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -19,8 +19,10 @@ package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.ActivityTaskManager;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Singleton;
import android.view.SurfaceControl;
@@ -66,6 +68,48 @@ public class WindowOrganizer {
}
/**
+ * Start a transition.
+ * @param type The type of the transition. This is ignored if a transitionToken is provided.
+ * @param transitionToken An existing transition to start. If null, a new transition is created.
+ * @param t The set of window operations that are part of this transition.
+ * @return A token identifying the transition. This will be the same as transitionToken if it
+ * was provided.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ @NonNull
+ public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ @Nullable WindowContainerTransaction t) {
+ try {
+ return getWindowOrganizerController().startTransition(type, transitionToken, t);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Finishes a running transition.
+ * @param transitionToken The transition to finish. Can't be null.
+ * @param t A set of window operations to apply before finishing.
+ * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
+ * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
+ *
+ * @hide
+ */
+ @SuppressLint("ExecutorRegistration")
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public int finishTransition(@NonNull IBinder transitionToken,
+ @Nullable WindowContainerTransaction t,
+ @Nullable WindowContainerTransactionCallback callback) {
+ try {
+ return getWindowOrganizerController().finishTransition(transitionToken, t,
+ callback != null ? callback.mInterface : null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Take a screenshot for a specified Window
* @param token The token for the WindowContainer that should get a screenshot taken.
* @return A SurfaceControl where the screenshot will be attached, or null if failed.
@@ -87,6 +131,19 @@ public class WindowOrganizer {
}
}
+ /**
+ * Register an ITransitionPlayer to handle transition animations.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+ public void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ try {
+ getWindowOrganizerController().registerTransitionPlayer(player);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
IWindowOrganizerController getWindowOrganizerController() {
return IWindowOrganizerControllerSingleton.get();
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl b/core/java/com/android/ims/internal/uce/presence/PresCmdID.aidl
index 6ece045ffcf7..6ece045ffcf7 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdId.aidl
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdID.aidl
diff --git a/core/java/com/android/internal/BrightnessSynchronizer.java b/core/java/com/android/internal/BrightnessSynchronizer.java
index f08d0ef8c052..6b8cf6361e91 100644
--- a/core/java/com/android/internal/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/BrightnessSynchronizer.java
@@ -36,7 +36,7 @@ import java.util.Queue;
* (new) system for storing the brightness. It has methods to convert between the two and also
* observes for when one of the settings is changed and syncs this with the other.
*/
-public class BrightnessSynchronizer{
+public class BrightnessSynchronizer {
private static final int MSG_UPDATE_FLOAT = 1;
private static final int MSG_UPDATE_INT = 2;
@@ -78,6 +78,26 @@ public class BrightnessSynchronizer{
mContext = context;
mBrightnessSyncObserver = new BrightnessSyncObserver(mHandler);
mBrightnessSyncObserver.startObserving();
+
+ // It is possible for the system to start up with the int and float values not
+ // synchronized. So we force an update to the int value, since float is the source
+ // of truth. Fallback to int value, if float is invalid. If both are invalid, use default
+ // float value from config.
+ final float currentFloatBrightness = getScreenBrightnessFloat(context);
+ final int currentIntBrightness = getScreenBrightnessInt(context);
+
+ if (!Float.isNaN(currentFloatBrightness)) {
+ updateBrightnessIntFromFloat(currentFloatBrightness);
+ } else if (currentIntBrightness != -1) {
+ updateBrightnessFloatFromInt(currentIntBrightness);
+ } else {
+ final float defaultBrightness = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessSettingDefaultFloat);
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FLOAT, defaultBrightness,
+ UserHandle.USER_CURRENT);
+
+ }
}
/**
@@ -132,7 +152,8 @@ public class BrightnessSynchronizer{
private static int getScreenBrightnessInt(Context context) {
return Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS, 0, UserHandle.USER_CURRENT);
+ Settings.System.SCREEN_BRIGHTNESS, PowerManager.BRIGHTNESS_INVALID,
+ UserHandle.USER_CURRENT);
}
private float mPreferredSettingValue;
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 491ddba8959d..2b4e09d66851 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -80,6 +80,8 @@ public class AccessibilityShortcutController {
"com.android.server.accessibility.MagnificationController";
public static final ComponentName MAGNIFICATION_COMPONENT_NAME =
new ComponentName("com.android.server.accessibility", "Magnification");
+ public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "ReduceBrightColors");
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -126,6 +128,11 @@ public class AccessibilityShortcutController {
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
"1" /* Value to enable */, "0" /* Value to disable */,
R.string.color_correction_feature_name));
+ featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
+ new ToggleableFrameworkFeatureInfo(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ "1" /* Value to enable */, "0" /* Value to disable */,
+ R.string.reduce_bright_colors_feature_name));
sFrameworkShortcutFeaturesMap = Collections.unmodifiableMap(featuresMap);
}
return sFrameworkShortcutFeaturesMap;
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index a7c5f6d5e03f..9d06bb92b205 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -21,6 +21,7 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTT
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
@@ -112,7 +113,7 @@ public final class AccessibilityTargetHelper {
@ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
- targets.addAll(getWhiteListingFeatureTargets(context, shortcutType));
+ targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
return targets;
}
@@ -196,12 +197,12 @@ public final class AccessibilityTargetHelper {
return targets;
}
- private static List<AccessibilityTarget> getWhiteListingFeatureTargets(Context context,
+ private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
@ShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
- final InvisibleToggleWhiteListingFeatureTarget magnification =
- new InvisibleToggleWhiteListingFeatureTarget(context,
+ final InvisibleToggleAllowListingFeatureTarget magnification =
+ new InvisibleToggleAllowListingFeatureTarget(context,
shortcutType,
isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
MAGNIFICATION_CONTROLLER_NAME,
@@ -209,8 +210,8 @@ public final class AccessibilityTargetHelper {
context.getDrawable(R.drawable.ic_accessibility_magnification),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
- final ToggleWhiteListingFeatureTarget daltonizer =
- new ToggleWhiteListingFeatureTarget(context,
+ final ToggleAllowListingFeatureTarget daltonizer =
+ new ToggleAllowListingFeatureTarget(context,
shortcutType,
isShortcutContained(context, shortcutType,
DALTONIZER_COMPONENT_NAME.flattenToString()),
@@ -219,8 +220,8 @@ public final class AccessibilityTargetHelper {
context.getDrawable(R.drawable.ic_accessibility_color_correction),
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
- final ToggleWhiteListingFeatureTarget colorInversion =
- new ToggleWhiteListingFeatureTarget(context,
+ final ToggleAllowListingFeatureTarget colorInversion =
+ new ToggleAllowListingFeatureTarget(context,
shortcutType,
isShortcutContained(context, shortcutType,
COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
@@ -229,9 +230,21 @@ public final class AccessibilityTargetHelper {
context.getDrawable(R.drawable.ic_accessibility_color_inversion),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ // TODO: Update with shortcut icon
+ final ToggleAllowListingFeatureTarget reduceBrightColors =
+ new ToggleAllowListingFeatureTarget(context,
+ shortcutType,
+ isShortcutContained(context, shortcutType,
+ REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
+ REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
+ context.getString(R.string.reduce_bright_colors_feature_name),
+ null,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
+
targets.add(magnification);
targets.add(daltonizer);
targets.add(colorInversion);
+ targets.add(reduceBrightColors);
return targets;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index acd101bf28ba..e78036d9f1e9 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleWhiteListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -26,9 +26,9 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility
* Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#INVISIBLE_TOGGLE}
* type.
*/
-class InvisibleToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
- InvisibleToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+ InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE,
isShortcutSwitched, id, label, icon, key);
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 5ab9eb84f0e4..38aac708de15 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleWhiteListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -32,9 +32,9 @@ import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
* Extension for {@link AccessibilityTarget} with {@link AccessibilityFragmentType#TOGGLE}
* type.
*/
-class ToggleWhiteListingFeatureTarget extends AccessibilityTarget {
+class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
- ToggleWhiteListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+ ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
boolean isShortcutSwitched, String id, CharSequence label, Drawable icon, String key) {
super(context, shortcutType, AccessibilityFragmentType.TOGGLE,
isShortcutSwitched, id, label, icon, key);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 5910b334dc2b..e0f8a1ad0a1a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -274,8 +274,6 @@ public class ChooserActivity extends ResolverActivity implements
private int mLastNumberOfChildren = -1;
private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
- // TODO: Update to handle landscape instead of using static value
- private static final int MAX_RANKED_TARGETS = 4;
private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>();
@@ -952,7 +950,7 @@ public class ChooserActivity extends ResolverActivity implements
updateStickyContentPreview();
if (shouldShowStickyContentPreview()
|| mChooserMultiProfilePagerAdapter
- .getCurrentRootAdapter().getContentPreviewRowCount() != 0) {
+ .getCurrentRootAdapter().getSystemRowCount() != 0) {
logActionShareWithPreview();
}
return postRebuildListInternal(rebuildCompleted);
@@ -1326,13 +1324,14 @@ public class ChooserActivity extends ResolverActivity implements
ViewGroup parent) {
ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
R.layout.chooser_grid_preview_image, parent, false);
+ ViewGroup imagePreview = contentPreviewLayout.findViewById(R.id.content_preview_image_area);
final ViewGroup actionRow =
(ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
//TODO: addActionButton(actionRow, createCopyButton());
addActionButton(actionRow, createNearbyButton(targetIntent));
- mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
+ mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
String action = targetIntent.getAction();
if (Intent.ACTION_SEND.equals(action)) {
@@ -1352,7 +1351,7 @@ public class ChooserActivity extends ResolverActivity implements
if (imageUris.size() == 0) {
Log.i(TAG, "Attempted to display image preview area with zero"
+ " available images detected in EXTRA_STREAM list");
- contentPreviewLayout.setVisibility(View.GONE);
+ imagePreview.setVisibility(View.GONE);
return contentPreviewLayout;
}
@@ -1772,7 +1771,7 @@ public class ChooserActivity extends ResolverActivity implements
case ChooserListAdapter.TARGET_CALLER:
case ChooserListAdapter.TARGET_STANDARD:
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
- value -= currentListAdapter.getSelectableServiceTargetCount();
+ value -= currentListAdapter.getSurfacedTargetInfo().size();
numCallerProvided = currentListAdapter.getCallerTargetCount();
getChooserActivityLogger().logShareTargetSelected(
SELECTION_TYPE_APP,
@@ -1856,6 +1855,7 @@ public class ChooserActivity extends ResolverActivity implements
@VisibleForTesting
protected void queryTargetServices(ChooserListAdapter adapter) {
+
mQueriedTargetServicesTimeMs = System.currentTimeMillis();
Context selectedProfileContext = createContextAsUser(
@@ -1872,15 +1872,14 @@ public class ChooserActivity extends ResolverActivity implements
continue;
}
final ActivityInfo ai = dri.getResolveInfo().activityInfo;
- if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
- && sm.hasShareTargets(ai.packageName)) {
+ if (sm.hasShareTargets(ai.packageName)) {
// Share targets will be queried from ShortcutManager
continue;
}
final Bundle md = ai.metaData;
final String serviceName = md != null ? convertServiceName(ai.packageName,
md.getString(ChooserTargetService.META_DATA_NAME)) : null;
- if (serviceName != null) {
+ if (serviceName != null && ChooserFlags.USE_SERVICE_TARGETS_FOR_DIRECT_TARGETS) {
final ComponentName serviceComponent = new ComponentName(
ai.packageName, serviceName);
@@ -2695,7 +2694,7 @@ public class ChooserActivity extends ResolverActivity implements
final int bottomInset = mSystemWindowInsets != null
? mSystemWindowInsets.bottom : 0;
int offset = bottomInset;
- int rowsToShow = gridAdapter.getContentPreviewRowCount()
+ int rowsToShow = gridAdapter.getSystemRowCount()
+ gridAdapter.getProfileRowCount()
+ gridAdapter.getServiceTargetRowCount()
+ gridAdapter.getCallerAndRankedTargetRowCount();
@@ -2884,8 +2883,7 @@ public class ChooserActivity extends ResolverActivity implements
return;
}
- if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
- || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ if (ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
if (DEBUG) {
Log.d(TAG, "querying direct share targets from ShortcutManager");
}
@@ -3295,7 +3293,7 @@ public class ChooserActivity extends ResolverActivity implements
public int getRowCount() {
return (int) (
- getContentPreviewRowCount()
+ getSystemRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
@@ -3307,22 +3305,21 @@ public class ChooserActivity extends ResolverActivity implements
}
/**
- * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item
- * content preview. Not to be confused with the sticky content preview which is above the
- * personal and work tabs.
+ * Whether the "system" row of targets is displayed.
+ * This area includes the content preview (if present) and action row.
*/
- public int getContentPreviewRowCount() {
+ public int getSystemRowCount() {
// For the tabbed case we show the sticky content preview above the tabs,
// please refer to shouldShowStickyContentPreview
if (shouldShowTabs()) {
return 0;
}
+
if (!isSendAction(getTargetIntent())) {
return 0;
}
- if (mHideContentPreview || mChooserListAdapter == null
- || mChooserListAdapter.getCount() == 0) {
+ if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
return 0;
}
@@ -3364,7 +3361,7 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public int getItemCount() {
return (int) (
- getContentPreviewRowCount()
+ getSystemRowCount()
+ getProfileRowCount()
+ getServiceTargetRowCount()
+ getCallerAndRankedTargetRowCount()
@@ -3419,7 +3416,7 @@ public class ChooserActivity extends ResolverActivity implements
public int getItemViewType(int position) {
int count;
- int countSum = (count = getContentPreviewRowCount());
+ int countSum = (count = getSystemRowCount());
if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
countSum += (count = getProfileRowCount());
@@ -3643,7 +3640,7 @@ public class ChooserActivity extends ResolverActivity implements
}
int getListPosition(int position) {
- position -= getContentPreviewRowCount() + getProfileRowCount();
+ position -= getSystemRowCount() + getProfileRowCount();
final int serviceCount = mChooserListAdapter.getServiceTargetCount();
final int serviceRows = (int) Math.ceil((float) serviceCount
diff --git a/core/java/com/android/internal/app/ChooserFlags.java b/core/java/com/android/internal/app/ChooserFlags.java
index f1f1dbf49b8b..3e26679e28a4 100644
--- a/core/java/com/android/internal/app/ChooserFlags.java
+++ b/core/java/com/android/internal/app/ChooserFlags.java
@@ -17,22 +17,29 @@
package com.android.internal.app;
import android.app.prediction.AppPredictionManager;
+import android.provider.DeviceConfig;
+
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
/**
* Common flags for {@link ChooserListAdapter} and {@link ChooserActivity}.
*/
public class ChooserFlags {
+
/**
- * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
- * binding to every ChooserTargetService implementation.
+ * Whether to use the deprecated {@link android.service.chooser.ChooserTargetService} API for
+ * direct share targets. If true, both CTS and Shortcuts will be used to find Direct Share
+ * targets. If false, only Shortcuts will be used.
*/
- // TODO(b/121287573): Replace with a system flag (setprop?)
- public static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+ public static final boolean USE_SERVICE_TARGETS_FOR_DIRECT_TARGETS =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SHARE_USE_SERVICE_TARGETS, true);
/**
- * If {@link ChooserFlags#USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
- * {@link AppPredictionManager} will be queried for direct share targets.
+ * Whether to use {@link AppPredictionManager} to query for direct share targets (as opposed to
+ * talking directly to {@link android.content.pm.ShortcutManager}.
*/
// TODO(b/123089490): Replace with system flag
static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
}
+
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 51e56b7fca43..04146bcad083 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -56,6 +56,13 @@ interface IAppOpsService {
String proxiedAttributionTag, int proxyUid, String proxyPackageName,
String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage);
+ int startProxyOperation(IBinder clientId, int code, int proxiedUid, String proxiedPackageName,
+ @nullable String proxiedAttributionTag, int proxyUid, String proxyPackageName,
+ @nullable String proxyAttributionTag, boolean startIfModeDefault,
+ boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage);
+ void finishProxyOperation(IBinder clientId, int code, int proxiedUid, String proxiedPackageName,
+ @nullable String proxiedAttributionTag, int proxyUid, String proxyPackageName,
+ @nullable String proxyAttributionTag);
// Remaining methods are only used in Java.
int checkPackage(int uid, String packageName);
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 74bfb963c6b0..f8aac42b8018 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,53 +16,45 @@
package com.android.internal.app;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.hardware.soundtrigger.IRecognitionStatusCallback;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.hardware.soundtrigger.ModelParams;
-import android.os.Bundle;
-import android.os.ParcelUuid;
+import android.media.permission.Identity;
+import com.android.internal.app.ISoundTriggerSession;
/**
* Service interface for a generic sound recognition model.
+ *
+ * This interface serves as an entry point to establish a session, associated with a client
+ * identity, which exposes the actual functionality.
+ *
* @hide
*/
interface ISoundTriggerService {
-
- SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
-
- void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
-
- void deleteSoundModel(in ParcelUuid soundModelId);
-
- int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
- in SoundTrigger.RecognitionConfig config);
-
- int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
-
- int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel);
- int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel);
-
- int startRecognitionForService(in ParcelUuid soundModelId, in Bundle params,
- in ComponentName callbackIntent,in SoundTrigger.RecognitionConfig config);
-
- int stopRecognitionForService(in ParcelUuid soundModelId);
-
- int unloadSoundModel(in ParcelUuid soundModelId);
-
- /** For both ...Intent and ...Service based usage */
- boolean isRecognitionActive(in ParcelUuid parcelUuid);
-
- int getModelState(in ParcelUuid soundModelId);
-
- @nullable SoundTrigger.ModuleProperties getModuleProperties();
-
- int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
- int value);
-
- int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
-
- @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
- in ModelParams modelParam);
+ /**
+ * Creates a new session.
+ *
+ * This version is intended to be used when the caller itself is the originator of the
+ * operations, for authorization purposes.
+ *
+ * The pid/uid fields are ignored and will be replaced by those provided by binder.
+ *
+ * It is good practice to clear the binder calling identity prior to calling this, in case the
+ * caller is ever in the same process as the callee.
+ */
+ ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity);
+
+ /**
+ * Creates a new session.
+ *
+ * This version is intended to be used when the caller is acting on behalf of a separate entity
+ * (the originator) and the sessions operations are to be accounted against that originator for
+ * authorization purposes.
+ *
+ * The caller must hold the SOUNDTRIGGER_DELEGATE_IDENTITY permission in order to be trusted to
+ * provide a reliable originator identity. It should follow the best practices for reliably and
+ * securely verifying the identity of the originator.
+ *
+ * It is good practice to clear the binder calling identity prior to calling this, in case the
+ * caller is ever in the same process as the callee.
+ */
+ ISoundTriggerSession attachAsMiddleman(in Identity middlemanIdentity,
+ in Identity originatorIdentity);
}
diff --git a/core/java/com/android/internal/app/ISoundTriggerSession.aidl b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
new file mode 100644
index 000000000000..ec7d282522c8
--- /dev/null
+++ b/core/java/com/android/internal/app/ISoundTriggerSession.aidl
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.ModelParams;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+
+/**
+ * Service interface for a generic sound recognition model.
+ * @hide
+ */
+interface ISoundTriggerSession {
+
+ SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
+
+ void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
+
+ void deleteSoundModel(in ParcelUuid soundModelId);
+
+ int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback,
+ in SoundTrigger.RecognitionConfig config);
+
+ int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
+
+ int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel);
+ int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel);
+
+ int startRecognitionForService(in ParcelUuid soundModelId, in Bundle params,
+ in ComponentName callbackIntent,in SoundTrigger.RecognitionConfig config);
+
+ int stopRecognitionForService(in ParcelUuid soundModelId);
+
+ int unloadSoundModel(in ParcelUuid soundModelId);
+
+ /** For both ...Intent and ...Service based usage */
+ boolean isRecognitionActive(in ParcelUuid parcelUuid);
+
+ int getModelState(in ParcelUuid soundModelId);
+
+ @nullable SoundTrigger.ModuleProperties getModuleProperties();
+
+ int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
+ int value);
+
+ int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
+
+ @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
+ in ModelParams modelParam);
+}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 15ba8e8c11f7..49b4cd1e6a73 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -18,6 +18,7 @@ package com.android.internal.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.media.permission.Identity;
import android.os.Bundle;
import android.os.RemoteCallback;
@@ -25,9 +26,8 @@ import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractionSessionListener;
-import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import android.hardware.soundtrigger.KeyphraseMetadata;
-import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -86,13 +86,6 @@ interface IVoiceInteractionManagerService {
* @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
-
- /**
- * Gets the properties of the DSP hardware on this device, null if not present.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- */
- SoundTrigger.ModuleProperties getDspModuleProperties();
/**
* Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
* user ID of the caller.
@@ -116,65 +109,6 @@ interface IVoiceInteractionManagerService {
*/
KeyphraseMetadata getEnrolledKeyphraseMetadata(String keyphrase, String bcp47Locale);
/**
- * Starts a recognition for the given keyphrase.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- */
- int startRecognition(int keyphraseId, in String bcp47Locale,
- in IRecognitionStatusCallback callback,
- in SoundTrigger.RecognitionConfig recognitionConfig);
- /**
- * Stops a recognition for the given keyphrase.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- */
- int stopRecognition(int keyphraseId, in IRecognitionStatusCallback callback);
- /**
- * Set a model specific ModelParams with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
- * stopping recognition. Once the model is unloaded, the value will be lost.
- * queryParameter should be checked first before calling this method.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- *
- * @param keyphraseId The unique identifier for the keyphrase.
- * @param modelParam ModelParams
- * @param value Value to set
- * @return - {@link SoundTrigger#STATUS_OK} in case of success
- * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
- * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
- * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
- * if API is not supported by HAL
- */
- int setParameter(int keyphraseId, in ModelParams modelParam, int value);
- /**
- * Get a model specific ModelParams. This parameter will keep its value
- * for the duration the model is loaded regardless of starting and stopping recognition.
- * Once the model is unloaded, the value will be lost. If the value is not set, a default
- * value is returned. See ModelParams for parameter default values.
- * queryParameter should be checked first before calling this method.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- *
- * @param keyphraseId The unique identifier for the keyphrase.
- * @param modelParam ModelParams
- * @return value of parameter
- */
- int getParameter(int keyphraseId, in ModelParams modelParam);
- /**
- * Determine if parameter control is supported for the given model handle.
- * This method should be checked prior to calling setParameter or getParameter.
- * Caller must be the active voice interaction service via
- * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
- *
- * @param keyphraseId The unique identifier for the keyphrase.
- * @param modelParam ModelParams
- * @return supported range of parameter, null if not supported
- */
- @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId,
- in ModelParams modelParam);
-
- /**
* @return the component name for the currently active voice interaction service
* @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE
*/
@@ -277,4 +211,12 @@ interface IVoiceInteractionManagerService {
*/
void setDisabled(boolean disabled);
+ /**
+ * Creates a session, allowing controlling running sound models on detection hardware.
+ * Caller must provide an identity, used for permission tracking purposes.
+ * The uid/pid elements of the identity will be ignored by the server and replaced with the ones
+ * provided by binder.
+ */
+ IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
+ in Identity originatorIdentity);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
new file mode 100644
index 000000000000..33aab4132150
--- /dev/null
+++ b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl
@@ -0,0 +1,95 @@
+/*
+ * 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.internal.app;
+
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
+import android.hardware.soundtrigger.SoundTrigger;
+
+/**
+ * This interface allows performing sound-trigger related operations with the actual sound trigger
+ * hardware.
+ *
+ * Every instance of this interface is associated with a client identity ("originator"), established
+ * upon the creation of the instance and used for permission accounting.
+ */
+interface IVoiceInteractionSoundTriggerSession {
+ /**
+ * Gets the properties of the DSP hardware on this device, null if not present.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ */
+ SoundTrigger.ModuleProperties getDspModuleProperties();
+ /**
+ * Starts a recognition for the given keyphrase.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ */
+ int startRecognition(int keyphraseId, in String bcp47Locale,
+ in IRecognitionStatusCallback callback,
+ in SoundTrigger.RecognitionConfig recognitionConfig);
+ /**
+ * Stops a recognition for the given keyphrase.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ */
+ int stopRecognition(int keyphraseId, in IRecognitionStatusCallback callback);
+ /**
+ * Set a model specific ModelParams with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * queryParameter should be checked first before calling this method.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ *
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ int setParameter(int keyphraseId, in ModelParams modelParam, int value);
+ /**
+ * Get a model specific ModelParams. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * queryParameter should be checked first before calling this method.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ *
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return value of parameter
+ */
+ int getParameter(int keyphraseId, in ModelParams modelParam);
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling setParameter or getParameter.
+ * Caller must be the active voice interaction service via
+ * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}.
+ *
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return supported range of parameter, null if not supported
+ */
+ @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId,
+ in ModelParams modelParam);
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index c3b570331671..6edf7a3204d4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -19,6 +19,7 @@ package com.android.internal.app;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.Nullable;
import android.annotation.StringRes;
@@ -69,7 +70,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
@@ -101,7 +104,6 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
-
/**
* This activity is displayed when the system attempts to start an Intent for
* which there is more than one matching activity, allowing the user to decide
@@ -822,6 +824,8 @@ public class ResolverActivity extends Activity implements
@Override
protected void onStart() {
super.onStart();
+
+ this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
if (shouldShowTabs()) {
mWorkProfileStateReceiver = createWorkProfileStateReceiver();
registerWorkProfileStateReceiver();
@@ -849,6 +853,12 @@ public class ResolverActivity extends Activity implements
@Override
protected void onStop() {
super.onStop();
+
+ final Window window = this.getWindow();
+ final WindowManager.LayoutParams attrs = window.getAttributes();
+ attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+ window.setAttributes(attrs);
+
if (mRegistered) {
mPersonalPackageMonitor.unregister();
if (mWorkPackageMonitor != null) {
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index 900e18d468bb..ce75f45d0897 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -38,7 +38,6 @@ import android.text.SpannableStringBuilder;
import android.util.Log;
import com.android.internal.app.ChooserActivity;
-import com.android.internal.app.ChooserFlags;
import com.android.internal.app.ResolverActivity;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
import com.android.internal.app.SimpleIconFactory;
@@ -147,7 +146,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
final Icon icon = target.getIcon();
if (icon != null) {
directShareIcon = icon.loadDrawable(mContext);
- } else if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS && shortcutInfo != null) {
+ } else if (shortcutInfo != null) {
LauncherApps launcherApps = (LauncherApps) mContext.getSystemService(
Context.LAUNCHER_APPS_SERVICE);
directShareIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, 0);
@@ -166,7 +165,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
// Now fetch app icon and raster with no badging even in work profile
Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
- .getIconBitmap(UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+ .getIconBitmap(android.os.Process.myUserHandle());
// Raster target drawable with appIcon as a badge
SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 1ce246ad4274..d23ea3c46695 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -429,6 +429,14 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String BACK_GESTURE_ML_MODEL_THRESHOLD = "back_gesture_ml_model_threshold";
+ /**
+ * (boolean) Sharesheet - Whether to use the deprecated
+ * {@link android.service.chooser.ChooserTargetService} API for
+ * direct share targets. If true, both CTS and Shortcuts will be used to find Direct
+ * Share targets. If false, only Shortcuts will be used.
+ */
+ public static final String SHARE_USE_SERVICE_TARGETS = "share_use_service_targets";
+
private SystemUiDeviceConfigFlags() {
}
diff --git a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
index f1398bf67283..70505bc5895b 100644
--- a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
@@ -25,6 +25,7 @@ import android.util.Slog;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* Base class representing a remote service that can queue multiple pending requests while not
@@ -39,7 +40,7 @@ public abstract class AbstractMultiplePendingRequestsRemoteService<S
private final int mInitialCapacity;
- protected ArrayList<BasePendingRequest<S, I>> mPendingRequests;
+ protected @NonNull List<BasePendingRequest<S, I>> mPendingRequests;
public AbstractMultiplePendingRequestsRemoteService(@NonNull Context context,
@NonNull String serviceInterface, @NonNull ComponentName componentName, int userId,
@@ -48,35 +49,36 @@ public abstract class AbstractMultiplePendingRequestsRemoteService<S
super(context, serviceInterface, componentName, userId, callback, handler, bindingFlags,
verbose);
mInitialCapacity = initialCapacity;
+ mPendingRequests = new ArrayList<>(mInitialCapacity);
}
@Override // from AbstractRemoteService
void handlePendingRequests() {
- if (mPendingRequests != null) {
+ synchronized (mPendingRequests) {
final int size = mPendingRequests.size();
if (mVerbose) Slog.v(mTag, "Sending " + size + " pending requests");
for (int i = 0; i < size; i++) {
mPendingRequests.get(i).run();
}
- mPendingRequests = null;
+ mPendingRequests.clear();
}
}
@Override // from AbstractRemoteService
protected void handleOnDestroy() {
- if (mPendingRequests != null) {
+ synchronized (mPendingRequests) {
final int size = mPendingRequests.size();
if (mVerbose) Slog.v(mTag, "Canceling " + size + " pending requests");
for (int i = 0; i < size; i++) {
mPendingRequests.get(i).cancel();
}
- mPendingRequests = null;
+ mPendingRequests.clear();
}
}
@Override // from AbstractRemoteService
final void handleBindFailure() {
- if (mPendingRequests != null) {
+ synchronized (mPendingRequests) {
final int size = mPendingRequests.size();
if (mVerbose) Slog.v(mTag, "Sending failure to " + size + " pending requests");
for (int i = 0; i < size; i++) {
@@ -84,7 +86,7 @@ public abstract class AbstractMultiplePendingRequestsRemoteService<S
request.onFailed();
request.finish();
}
- mPendingRequests = null;
+ mPendingRequests.clear();
}
}
@@ -94,18 +96,21 @@ public abstract class AbstractMultiplePendingRequestsRemoteService<S
pw.append(prefix).append("initialCapacity=").append(String.valueOf(mInitialCapacity))
.println();
- final int size = mPendingRequests == null ? 0 : mPendingRequests.size();
+ int size;
+ synchronized (mPendingRequests) {
+ size = mPendingRequests.size();
+ }
pw.append(prefix).append("pendingRequests=").append(String.valueOf(size)).println();
}
@Override // from AbstractRemoteService
void handlePendingRequestWhileUnBound(@NonNull BasePendingRequest<S, I> pendingRequest) {
- if (mPendingRequests == null) {
- mPendingRequests = new ArrayList<>(mInitialCapacity);
- }
- mPendingRequests.add(pendingRequest);
- if (mVerbose) {
- Slog.v(mTag, "queued " + mPendingRequests.size() + " requests; last=" + pendingRequest);
+ synchronized (mPendingRequests) {
+ mPendingRequests.add(pendingRequest);
+ if (mVerbose) {
+ Slog.v(mTag,
+ "queued " + mPendingRequests.size() + " requests; last=" + pendingRequest);
+ }
}
}
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 37f68233db53..3bcba75ec163 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -224,6 +224,8 @@ public final class InputMethodDebug {
return "HIDE_DOCKED_STACK_ATTACHED";
case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION:
return "HIDE_RECENTS_ANIMATION";
+ case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR:
+ return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR";
default:
return "Unknown=" + reason;
}
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 4b968b45f122..f46626be48a8 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -47,7 +47,8 @@ import java.lang.annotation.Retention;
SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME,
SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED,
SoftInputShowHideReason.HIDE_RECENTS_ANIMATION,
- SoftInputShowHideReason.HIDE_BUBBLES})
+ SoftInputShowHideReason.HIDE_BUBBLES,
+ SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR})
public @interface SoftInputShowHideReason {
/** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
int SHOW_SOFT_INPUT = 0;
@@ -147,4 +148,17 @@ public @interface SoftInputShowHideReason {
* switching, or collapsing Bubbles.
*/
int HIDE_BUBBLES = 19;
+
+ /**
+ * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
+ * valid focused editor.
+ *
+ * Note: From Android R, the window focus change callback is processed by InputDispatcher,
+ * some focus behavior changes (e.g. There are an activity with a dialog window, after
+ * screen turned-off and turned-on, before Android R the window focus sequence would be
+ * the activity first and then the dialog focused, however, in R the focus sequence would be
+ * only the dialog focused as it's the latest window with input focus) makes we need to hide
+ * soft-input when the same window focused again to align with the same behavior prior to R.
+ */
+ int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
}
diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java
index 5a8d2c227256..ac83987ef12c 100644
--- a/core/java/com/android/internal/inputmethod/StartInputFlags.java
+++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java
@@ -47,4 +47,10 @@ public @interface StartInputFlags {
* documented hence we probably need to revisit this though.
*/
int INITIAL_CONNECTION = 4;
+
+ /**
+ * The start input happens when the window gained focus to call
+ * {@code android.view.inputmethod.InputMethodManager#startInputAsyncOnWindowFocusGain}.
+ */
+ int WINDOW_GAINED_FOCUS = 8;
}
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index dd1978ebb026..85dc2aea4b1c 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -24,11 +24,11 @@ import android.util.Log;
import android.view.FrameMetrics;
import android.view.ThreadedRenderer;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor.Session;
import com.android.internal.util.FrameworkStatsLog;
/**
+ * A class that allows the app to get the frame metrics from HardwareRendererObserver.
* @hide
*/
public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
@@ -45,28 +45,21 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
private long mBeginTime = UNKNOWN_TIMESTAMP;
private long mEndTime = UNKNOWN_TIMESTAMP;
private boolean mShouldTriggerTrace;
+ private boolean mSessionEnd;
private int mTotalFramesCount = 0;
private int mMissedFramesCount = 0;
private long mMaxFrameTimeNanos = 0;
private Session mSession;
- public FrameTracker(@NonNull Session session,
- @NonNull Handler handler, @NonNull ThreadedRenderer renderer) {
- mSession = session;
- mRendererWrapper = new ThreadedRendererWrapper(renderer);
- mMetricsWrapper = new FrameMetricsWrapper();
- mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler);
- }
-
/**
- * This constructor is only for unit tests.
+ * Constructor of FrameTracker.
* @param session a trace session.
- * @param renderer a test double for ThreadedRenderer
- * @param metrics a test double for FrameMetrics
+ * @param handler a handler for handling callbacks.
+ * @param renderer a ThreadedRendererWrapper instance.
+ * @param metrics a FrameMetricsWrapper instance.
*/
- @VisibleForTesting
- public FrameTracker(@NonNull Session session, Handler handler,
+ public FrameTracker(@NonNull Session session, @NonNull Handler handler,
@NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics) {
mSession = session;
mRendererWrapper = renderer;
@@ -77,15 +70,11 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
/**
* Begin a trace session of the CUJ.
*/
- public void begin() {
+ public synchronized void begin() {
long timestamp = System.nanoTime();
if (DEBUG) {
Log.d(TAG, "begin: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
- + ", end(ns)=" + mEndTime + ", session=" + mSession);
- }
- if (mBeginTime != UNKNOWN_TIMESTAMP && mEndTime == UNKNOWN_TIMESTAMP) {
- // We have an ongoing tracing already, skip subsequent calls.
- return;
+ + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
}
mBeginTime = timestamp;
mEndTime = UNKNOWN_TIMESTAMP;
@@ -96,32 +85,48 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
/**
* End the trace session of the CUJ.
*/
- public void end() {
+ public synchronized void end() {
long timestamp = System.nanoTime();
if (DEBUG) {
Log.d(TAG, "end: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime
- + ", end(ns)=" + mEndTime + ", session=" + mSession);
- }
- if (mBeginTime == UNKNOWN_TIMESTAMP || mEndTime != UNKNOWN_TIMESTAMP) {
- // We haven't started a trace yet.
- return;
+ + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
}
mEndTime = timestamp;
Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ }
+
+ /**
+ * Cancel the trace session of the CUJ.
+ */
+ public synchronized void cancel() {
+ if (mBeginTime == UNKNOWN_TIMESTAMP || mEndTime != UNKNOWN_TIMESTAMP) return;
+ if (DEBUG) {
+ Log.d(TAG, "cancel: time(ns)=" + System.nanoTime() + ", begin(ns)=" + mBeginTime
+ + ", end(ns)=" + mEndTime + ", session=" + mSession.getName());
+ }
+ Trace.endAsyncSection(mSession.getName(), (int) mBeginTime);
+ mRendererWrapper.removeObserver(mObserver);
+ mBeginTime = UNKNOWN_TIMESTAMP;
+ mEndTime = UNKNOWN_TIMESTAMP;
+ mShouldTriggerTrace = false;
}
@Override
- public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
+ public synchronized void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
// Since this callback might come a little bit late after the end() call.
// We should keep tracking the begin / end timestamp.
// Then compare with vsync timestamp to check if the frame is in the duration of the CUJ.
- if (mBeginTime == UNKNOWN_TIMESTAMP) return; // We haven't started tracing yet.
long vsyncTimestamp = mMetricsWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
- if (vsyncTimestamp < mBeginTime) return; // The tracing has been started.
+ // Discard the frame metrics which is not in the trace session.
+ if (vsyncTimestamp < mBeginTime) return;
- // If the end time has not been set, we are still in the tracing.
- if (mEndTime != UNKNOWN_TIMESTAMP && vsyncTimestamp > mEndTime) {
+ // We stop getting callback when the vsync is later than the end calls.
+ if (mEndTime != UNKNOWN_TIMESTAMP && vsyncTimestamp > mEndTime && !mSessionEnd) {
+ mSessionEnd = true;
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
mRendererWrapper.removeObserver(mObserver);
@@ -170,9 +175,8 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai
/**
* Trigger the prefetto daemon.
*/
- @VisibleForTesting
public void triggerPerfetto() {
- InteractionJankMonitor.trigger();
+ InteractionJankMonitor.getInstance().trigger();
}
/**
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 5a0cbf9e93e4..3624f0d34725 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,20 +16,33 @@
package com.android.internal.jank;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.HandlerThread;
-import android.view.ThreadedRenderer;
+import android.util.Log;
+import android.util.SparseArray;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
+import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* This class let users to begin and end the always on tracing mechanism.
@@ -38,156 +51,298 @@ import java.util.Map;
public class InteractionJankMonitor {
private static final String TAG = InteractionJankMonitor.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final Object LOCK = new Object();
+ private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
// Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE.
- public static final int CUJ_NOTIFICATION_SHADE_MOTION = 0;
- public static final int CUJ_NOTIFICATION_SHADE_GESTURE = 1;
+ public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0;
+ public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1;
+ public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2;
+ public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3;
+ public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4;
+ public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5;
+ public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6;
+ public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7;
+ public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8;
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
+ public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
+ public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
// Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd.
- private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
- NO_STATSD_LOGGING,
+ @VisibleForTesting
+ public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = {
+ // This should be mapping to CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE.
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH,
};
- private static ThreadedRenderer sRenderer;
- private static Map<String, FrameTracker> sRunningTracker;
- private static HandlerThread sWorker;
- private static boolean sInitialized;
+ private static volatile InteractionJankMonitor sInstance;
+
+ private ThreadedRendererWrapper mRenderer;
+ private FrameMetricsWrapper mMetrics;
+ private SparseArray<FrameTracker> mRunningTrackers;
+ private SparseArray<Runnable> mTimeoutActions;
+ private HandlerThread mWorker;
+ private boolean mInitialized;
/** @hide */
@IntDef({
- CUJ_NOTIFICATION_SHADE_MOTION,
- CUJ_NOTIFICATION_SHADE_GESTURE
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK,
+ CUJ_NOTIFICATION_SHADE_SCROLL_FLING,
+ CUJ_NOTIFICATION_SHADE_ROW_EXPAND,
+ CUJ_NOTIFICATION_SHADE_ROW_SWIPE,
+ CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE,
+ CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS,
+ CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON,
+ CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
+ CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
+ CUJ_LAUNCHER_QUICK_SWITCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
/**
- * @param view Any view in the view tree to get context and ThreadedRenderer.
+ * Get the singleton of InteractionJankMonitor.
+ * @return instance of InteractionJankMonitor
*/
- public static void init(@NonNull View view) {
- init(view, null, null, null);
+ public static InteractionJankMonitor getInstance() {
+ // Use DCL here since this method might be invoked very often.
+ if (sInstance == null) {
+ synchronized (InteractionJankMonitor.class) {
+ if (sInstance == null) {
+ sInstance = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME));
+ }
+ }
+ }
+ return sInstance;
}
/**
- * Should be only invoked internally or from unit tests.
+ * This constructor should be only public to tests.
+ * @param worker the worker thread for the callbacks
*/
@VisibleForTesting
- public static void init(@NonNull View view, @NonNull ThreadedRenderer renderer,
- @NonNull Map<String, FrameTracker> map, @NonNull HandlerThread worker) {
+ public InteractionJankMonitor(@NonNull HandlerThread worker) {
+ mRunningTrackers = new SparseArray<>();
+ mTimeoutActions = new SparseArray<>();
+ mWorker = worker;
+ }
+
+ /**
+ * Init InteractionJankMonitor for later instrumentation.
+ * @param view Any view in the view tree to get context and ThreadedRenderer.
+ * @return boolean true if the instance has been initialized successfully.
+ */
+ public boolean init(@NonNull View view) {
//TODO (163505250): This should be no-op if not in droid food rom.
- synchronized (LOCK) {
- if (!sInitialized) {
- if (!view.isAttachedToWindow()) {
- throw new IllegalStateException("View is not attached!");
+ if (!mInitialized) {
+ synchronized (this) {
+ if (!mInitialized) {
+ if (!view.isAttachedToWindow()) {
+ Log.d(TAG, "Expect an attached view!", new Throwable());
+ return false;
+ }
+ mRenderer = new ThreadedRendererWrapper(view.getThreadedRenderer());
+ mMetrics = new FrameMetricsWrapper();
+ mWorker.start();
+ mInitialized = true;
}
- sRenderer = renderer == null ? view.getThreadedRenderer() : renderer;
- sRunningTracker = map == null ? new HashMap<>() : map;
- sWorker = worker == null ? new HandlerThread("Aot-Worker") : worker;
- sWorker.start();
- sInitialized = true;
}
}
+ return true;
}
/**
- * Must invoke init() before invoking this method.
+ * Create a {@link FrameTracker} instance.
+ * @param session the session associates with this tracker
+ * @return instance of the FrameTracker
*/
- public static void begin(@NonNull @CujType int cujType) {
- begin(cujType, null);
+ @VisibleForTesting
+ public FrameTracker createFrameTracker(Session session) {
+ synchronized (this) {
+ if (!mInitialized) return null;
+ return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics);
+ }
}
/**
- * Should be only invoked internally or from unit tests.
+ * Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @return boolean true if the tracker is started successfully, false otherwise.
*/
- @VisibleForTesting
- public static void begin(@NonNull @CujType int cujType, FrameTracker tracker) {
+ public boolean begin(@CujType int cujType) {
//TODO (163505250): This should be no-op if not in droid food rom.
- //TODO (163510843): Remove synchronized, add @UiThread if only invoked from ui threads.
- synchronized (LOCK) {
- checkInitStateLocked();
- Session session = new Session(cujType);
- FrameTracker currentTracker = getTracker(session.getName());
- if (currentTracker != null) return;
- if (tracker == null) {
- tracker = new FrameTracker(session, sWorker.getThreadHandler(), sRenderer);
+ synchronized (this) {
+ return begin(cujType, 0L /* timeout */);
+ }
+ }
+
+ /**
+ * Begin a trace session, must invoke {@link #init(View)} before invoking this method.
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param timeout the elapsed time in ms until firing the timeout action.
+ * @return boolean true if the tracker is started successfully, false otherwise.
+ */
+ public boolean begin(@CujType int cujType, long timeout) {
+ //TODO (163505250): This should be no-op if not in droid food rom.
+ synchronized (this) {
+ if (!mInitialized) {
+ Log.d(TAG, "Not initialized!", new Throwable());
+ return false;
}
- sRunningTracker.put(session.getName(), tracker);
+ Session session = new Session(cujType);
+ FrameTracker tracker = getTracker(session);
+ // Skip subsequent calls if we already have an ongoing tracing.
+ if (tracker != null) return false;
+
+ // begin a new trace session.
+ tracker = createFrameTracker(session);
+ mRunningTrackers.put(cujType, tracker);
tracker.begin();
+
+ // Cancel the trace if we don't get an end() call in specified duration.
+ timeout = timeout > 0L ? timeout : DEFAULT_TIMEOUT_MS;
+ Runnable timeoutAction = () -> cancel(cujType);
+ mTimeoutActions.put(cujType, timeoutAction);
+ mWorker.getThreadHandler().postDelayed(timeoutAction, timeout);
+ return true;
}
}
/**
- * Must invoke init() before invoking this method.
+ * End a trace session, must invoke {@link #init(View)} before invoking this method.
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @return boolean true if the tracker is ended successfully, false otherwise.
*/
- public static void end(@NonNull @CujType int cujType) {
+ public boolean end(@CujType int cujType) {
//TODO (163505250): This should be no-op if not in droid food rom.
- //TODO (163510843): Remove synchronized, add @UiThread if only invoked from ui threads.
- synchronized (LOCK) {
- checkInitStateLocked();
+ synchronized (this) {
+ if (!mInitialized) {
+ Log.d(TAG, "Not initialized!", new Throwable());
+ return false;
+ }
+ // remove the timeout action first.
+ Runnable timeout = mTimeoutActions.get(cujType);
+ if (timeout != null) {
+ mWorker.getThreadHandler().removeCallbacks(timeout);
+ mTimeoutActions.remove(cujType);
+ }
+
Session session = new Session(cujType);
- FrameTracker tracker = getTracker(session.getName());
- if (tracker != null) {
- tracker.end();
- sRunningTracker.remove(session.getName());
+ FrameTracker tracker = getTracker(session);
+ // Skip this call since we haven't started a trace yet.
+ if (tracker == null) return false;
+ tracker.end();
+ mRunningTrackers.remove(session.getCuj());
+ return true;
+ }
+ }
+
+ /**
+ * Cancel the trace session, must invoke {@link #init(View)} before invoking this method.
+ * @return boolean true if the tracker is cancelled successfully, false otherwise.
+ */
+ public boolean cancel(@CujType int cujType) {
+ //TODO (163505250): This should be no-op if not in droid food rom.
+ synchronized (this) {
+ if (!mInitialized) {
+ Log.d(TAG, "Not initialized!", new Throwable());
+ return false;
}
+ // remove the timeout action first.
+ Runnable timeout = mTimeoutActions.get(cujType);
+ if (timeout != null) {
+ mWorker.getThreadHandler().removeCallbacks(timeout);
+ mTimeoutActions.remove(cujType);
+ }
+
+ Session session = new Session(cujType);
+ FrameTracker tracker = getTracker(session);
+ // Skip this call since we haven't started a trace yet.
+ if (tracker == null) return false;
+ tracker.cancel();
+ mRunningTrackers.remove(session.getCuj());
+ return true;
}
}
- private static void checkInitStateLocked() {
- if (!sInitialized) {
- throw new IllegalStateException("InteractionJankMonitor not initialized!");
+ private void destroy() {
+ synchronized (this) {
+ int trackers = mRunningTrackers.size();
+ for (int i = 0; i < trackers; i++) {
+ mRunningTrackers.valueAt(i).cancel();
+ }
+ mRunningTrackers = null;
+ mTimeoutActions.clear();
+ mTimeoutActions = null;
+ mWorker.quit();
+ mWorker = null;
}
}
/**
- * Should be only invoked from unit tests.
+ * Abandon current instance.
*/
@VisibleForTesting
- public static void reset() {
- sInitialized = false;
- sRenderer = null;
- sRunningTracker = null;
- if (sWorker != null) {
- sWorker.quit();
- sWorker = null;
+ public static void abandon() {
+ if (sInstance == null) return;
+ synchronized (InteractionJankMonitor.class) {
+ if (sInstance == null) return;
+ sInstance.destroy();
+ sInstance = null;
}
}
- private static FrameTracker getTracker(String sessionName) {
- synchronized (LOCK) {
- return sRunningTracker.get(sessionName);
+ private FrameTracker getTracker(Session session) {
+ synchronized (this) {
+ if (!mInitialized) return null;
+ return mRunningTrackers.get(session.getCuj());
}
}
/**
* Trigger the perfetto daemon to collect and upload data.
*/
- public static void trigger() {
- sWorker.getThreadHandler().post(
- () -> PerfettoTrigger.trigger(PerfettoTrigger.TRIGGER_TYPE_JANK));
+ @VisibleForTesting
+ public void trigger() {
+ synchronized (this) {
+ if (!mInitialized) return;
+ mWorker.getThreadHandler().post(
+ () -> PerfettoTrigger.trigger(PerfettoTrigger.TRIGGER_TYPE_JANK));
+ }
}
/**
* A class to represent a session.
*/
public static class Session {
- private @CujType int mId;
+ private @CujType int mCujType;
- public Session(@CujType int session) {
- mId = session;
+ public Session(@CujType int cujType) {
+ mCujType = cujType;
}
- public int getId() {
- return mId;
+ public int getCuj() {
+ return mCujType;
}
public int getStatsdInteractionType() {
- return CUJ_TO_STATSD_INTERACTION_TYPE[mId];
+ return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType];
}
/** Describes whether the measurement from this session should be written to statsd. */
@@ -196,7 +351,7 @@ public class InteractionJankMonitor {
}
public String getName() {
- return "CujType<" + mId + ">";
+ return "Cuj<" + getCuj() + ">";
}
}
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index c9156c13aae3..c0f44a5eb39b 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -33,7 +33,11 @@ public class UiEventLoggerImpl implements UiEventLogger {
public void log(UiEventEnum event, int uid, String packageName) {
final int eventID = event.getId();
if (eventID > 0) {
- FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
+ FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED,
+ /* event_id = 1 */ eventID,
+ /* uid = 2 */ uid,
+ /* package_name = 3 */ packageName,
+ /* instance_id = 4 */ 0);
}
}
@@ -42,8 +46,11 @@ public class UiEventLoggerImpl implements UiEventLogger {
InstanceId instance) {
final int eventID = event.getId();
if ((eventID > 0) && (instance != null)) {
- FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, eventID, uid, packageName,
- instance.getId());
+ FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED,
+ /* event_id = 1 */ eventID,
+ /* uid = 2 */ uid,
+ /* package_name = 3 */ packageName,
+ /* instance_id = 4 */ instance.getId());
} else {
log(event, uid, packageName);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e58990eff2c8..4c5f9886a7c6 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -65,7 +65,6 @@ import android.provider.Settings;
import android.telephony.CellSignalStrength;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
-import android.telephony.ModemActivityInfo.TransmitPower;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
@@ -145,7 +144,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final boolean DEBUG = false;
public static final boolean DEBUG_ENERGY = false;
private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
- private static final boolean DEBUG_BINDER_STATS = true;
+ private static final boolean DEBUG_BINDER_STATS = false;
private static final boolean DEBUG_MEMORY = false;
private static final boolean DEBUG_HISTORY = false;
private static final boolean USE_OLD_HISTORY = false; // for debugging.
@@ -156,7 +155,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- static final int VERSION = 187 + (USE_OLD_HISTORY ? 1000 : 0);
+ static final int VERSION = 189 + (USE_OLD_HISTORY ? 1000 : 0);
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
@@ -221,7 +220,8 @@ public class BatteryStatsImpl extends BatteryStats {
@VisibleForTesting
protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
@VisibleForTesting
- protected SystemServerCpuThreadReader mSystemServerCpuThreadReader;
+ protected SystemServerCpuThreadReader mSystemServerCpuThreadReader =
+ SystemServerCpuThreadReader.create();
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
@@ -1014,14 +1014,19 @@ public class BatteryStatsImpl extends BatteryStats {
private long[] mCpuFreqs;
/**
+ * Times spent by the system server process grouped by cluster and CPU speed.
+ */
+ private LongSamplingCounterArray mSystemServerCpuTimesUs;
+
+ /**
* Times spent by the system server threads grouped by cluster and CPU speed.
*/
- private LongSamplingCounter[][] mSystemServerThreadCpuTimesUs;
+ private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
/**
* Times spent by the system server threads handling incoming binder requests.
*/
- private LongSamplingCounter[][] mBinderThreadCpuTimesUs;
+ private LongSamplingCounterArray mBinderThreadCpuTimesUs;
@VisibleForTesting
protected PowerProfile mPowerProfile;
@@ -6613,22 +6618,29 @@ public class BatteryStatsImpl extends BatteryStats {
* the power consumption to the calling app.
*/
public void noteBinderCallStats(int workSourceUid, long incrementalCallCount,
- Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) {
- noteBinderCallStats(workSourceUid, incrementalCallCount, callStats, binderThreadNativeTids,
+ Collection<BinderCallsStats.CallStat> callStats) {
+ noteBinderCallStats(workSourceUid, incrementalCallCount, callStats,
mClocks.elapsedRealtime(), mClocks.uptimeMillis());
}
public void noteBinderCallStats(int workSourceUid, long incrementalCallCount,
- Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids,
+ Collection<BinderCallsStats.CallStat> callStats,
long elapsedRealtimeMs, long uptimeMs) {
synchronized (this) {
getUidStatsLocked(workSourceUid, elapsedRealtimeMs, uptimeMs)
.noteBinderCallStatsLocked(incrementalCallCount, callStats);
- mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids);
}
}
/**
+ * Takes note of native IDs of threads taking incoming binder calls. The CPU time
+ * of these threads is attributed to the apps making those binder calls.
+ */
+ public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
+ mSystemServerCpuThreadReader.setBinderThreadNativeTids(binderThreadNativeTids);
+ }
+
+ /**
* Estimates the proportion of system server CPU activity handling incoming binder calls
* that can be attributed to each app
*/
@@ -7778,7 +7790,7 @@ public class BatteryStatsImpl extends BatteryStats {
public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() {
if (mModemControllerActivity == null) {
mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
- ModemActivityInfo.TX_POWER_LEVELS);
+ ModemActivityInfo.getNumTxPowerLevels());
}
return mModemControllerActivity;
}
@@ -9244,7 +9256,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (in.readInt() != 0) {
mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase,
- ModemActivityInfo.TX_POWER_LEVELS, in);
+ ModemActivityInfo.getNumTxPowerLevels(), in);
} else {
mModemControllerActivity = null;
}
@@ -10507,7 +10519,7 @@ public class BatteryStatsImpl extends BatteryStats {
mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
NUM_BT_TX_LEVELS);
mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
- ModemActivityInfo.TX_POWER_LEVELS);
+ ModemActivityInfo.getNumTxPowerLevels());
mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase);
mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
mOnBatteryTimeBase);
@@ -10603,8 +10615,6 @@ public class BatteryStatsImpl extends BatteryStats {
firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
}
- mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create();
-
if (mEstimatedBatteryCapacity == -1) {
// Initialize the estimated battery capacity to a known preset one.
mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
@@ -11284,6 +11294,7 @@ public class BatteryStatsImpl extends BatteryStats {
mTmpRailStats.reset();
+ resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
@@ -11698,26 +11709,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- private ModemActivityInfo mLastModemActivityInfo =
- new ModemActivityInfo(0, 0, 0, new int[0], 0);
-
- private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) {
- if (activityInfo == null) {
- return null;
- }
- int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
- for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
- txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis()
- - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis();
- }
- ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(),
- activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(),
- activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(),
- txTimeMs,
- activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis());
- mLastModemActivityInfo = activityInfo;
- return deltaInfo;
- }
+ private ModemActivityInfo mLastModemActivityInfo = null;
/**
* Distribute Cell radio energy info and network traffic to apps.
@@ -11734,7 +11726,9 @@ public class BatteryStatsImpl extends BatteryStats {
if (DEBUG_ENERGY) {
Slog.d(TAG, "Updating mobile radio stats with " + activityInfo);
}
- ModemActivityInfo deltaInfo = getDeltaModemActivityInfo(activityInfo);
+ ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo
+ : mLastModemActivityInfo.getDelta(activityInfo);
+ mLastModemActivityInfo = activityInfo;
// Add modem tx power to history.
addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
@@ -11766,10 +11760,9 @@ public class BatteryStatsImpl extends BatteryStats {
mModemActivity.getSleepTimeCounter().addCountLocked(
deltaInfo.getSleepTimeMillis());
mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis());
- for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
+ for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) {
mModemActivity.getTxTimeCounters()[lvl]
- .addCountLocked(deltaInfo.getTransmitPowerInfo()
- .get(lvl).getTimeInMillis());
+ .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl));
}
// POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
@@ -11783,11 +11776,11 @@ public class BatteryStatsImpl extends BatteryStats {
mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
+ deltaInfo.getReceiveTimeMillis() *
mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
- List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo();
- for (int i = 0; i < Math.min(txPowerInfo.size(),
+ for (int i = 0; i < Math.min(ModemActivityInfo.getNumTxPowerLevels(),
CellSignalStrength.getNumSignalStrengthLevels()); i++) {
- energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile
- .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+ energyUsed += deltaInfo.getTransmitDurationMillisAtPowerLevel(i)
+ * mPowerProfile.getAveragePower(
+ PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
}
// We store the power drain as mAms.
@@ -11882,10 +11875,10 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (totalTxPackets > 0 && entry.txPackets > 0) {
- for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
- long txMs =
- entry.txPackets * deltaInfo.getTransmitPowerInfo()
- .get(lvl).getTimeInMillis();
+ for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels();
+ lvl++) {
+ long txMs = entry.txPackets
+ * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl);
txMs /= totalTxPackets;
activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
}
@@ -11917,18 +11910,14 @@ public class BatteryStatsImpl extends BatteryStats {
if (activityInfo == null) {
return;
}
- List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo();
- if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) {
- return;
- }
int levelMaxTimeSpent = 0;
- for (int i = 1; i < txPowerInfo.size(); i++) {
- if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent)
- .getTimeInMillis()) {
+ for (int i = 1; i < ModemActivityInfo.getNumTxPowerLevels(); i++) {
+ if (activityInfo.getTransmitDurationMillisAtPowerLevel(i)
+ > activityInfo.getTransmitDurationMillisAtPowerLevel(levelMaxTimeSpent)) {
levelMaxTimeSpent = i;
}
}
- if (levelMaxTimeSpent == ModemActivityInfo.TX_POWER_LEVELS - 1) {
+ if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
@@ -12414,38 +12403,33 @@ public class BatteryStatsImpl extends BatteryStats {
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
mSystemServerCpuThreadReader.readDelta();
+ if (systemServiceCpuThreadTimes == null) {
+ return;
+ }
- int index = 0;
- int numCpuClusters = mPowerProfile.getNumCpuClusters();
- if (mSystemServerThreadCpuTimesUs == null) {
- mSystemServerThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
- mBinderThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
- }
- for (int cluster = 0; cluster < numCpuClusters; cluster++) {
- int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
- if (mSystemServerThreadCpuTimesUs[cluster] == null) {
- mSystemServerThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
- mBinderThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
- for (int speed = 0; speed < numSpeeds; speed++) {
- mSystemServerThreadCpuTimesUs[cluster][speed] =
- new LongSamplingCounter(mOnBatteryTimeBase);
- mBinderThreadCpuTimesUs[cluster][speed] =
- new LongSamplingCounter(mOnBatteryTimeBase);
- }
- }
- for (int speed = 0; speed < numSpeeds; speed++) {
- mSystemServerThreadCpuTimesUs[cluster][speed].addCountLocked(
- systemServiceCpuThreadTimes.threadCpuTimesUs[index]);
- mBinderThreadCpuTimesUs[cluster][speed].addCountLocked(
- systemServiceCpuThreadTimes.binderThreadCpuTimesUs[index]);
- index++;
- }
+ if (mSystemServerCpuTimesUs == null) {
+ mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
}
+ mSystemServerCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.processCpuTimesUs);
+ mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
+ mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+
if (DEBUG_BINDER_STATS) {
Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
- long binderThreadTimeMs = 0;
+ long totalCpuTimeMs = 0;
long totalThreadTimeMs = 0;
+ long binderThreadTimeMs = 0;
int cpuIndex = 0;
+ final long[] systemServerCpuTimesUs =
+ mSystemServerCpuTimesUs.getCountsLocked(0);
+ final long[] systemServerThreadCpuTimesUs =
+ mSystemServerThreadCpuTimesUs.getCountsLocked(0);
+ final long[] binderThreadCpuTimesUs =
+ mBinderThreadCpuTimesUs.getCountsLocked(0);
+ int index = 0;
+ int numCpuClusters = mPowerProfile.getNumCpuClusters();
for (int cluster = 0; cluster < numCpuClusters; cluster++) {
StringBuilder sb = new StringBuilder();
sb.append("cpu").append(cpuIndex).append(": [");
@@ -12454,15 +12438,14 @@ public class BatteryStatsImpl extends BatteryStats {
if (speed != 0) {
sb.append(", ");
}
- long totalCountMs =
- mSystemServerThreadCpuTimesUs[cluster][speed].getCountLocked(0) / 1000;
- long binderCountMs = mBinderThreadCpuTimesUs[cluster][speed].getCountLocked(0)
- / 1000;
+ long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
+ long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
sb.append(String.format("%d/%d(%.1f%%)",
binderCountMs,
totalCountMs,
totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
+ totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
totalThreadTimeMs += totalCountMs;
binderThreadTimeMs += binderCountMs;
index++;
@@ -12470,6 +12453,8 @@ public class BatteryStatsImpl extends BatteryStats {
cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
Slog.d(TAG, sb.toString());
}
+
+ Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs);
Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
binderThreadTimeMs,
@@ -13454,7 +13439,7 @@ public class BatteryStatsImpl extends BatteryStats {
timeInRxSignalStrengthLevelMs[i] =
getPhoneSignalStrengthTime(i, rawRealTimeUs, which) / 1000;
}
- long[] txTimeMs = new long[Math.min(ModemActivityInfo.TX_POWER_LEVELS,
+ long[] txTimeMs = new long[Math.min(ModemActivityInfo.getNumTxPowerLevels(),
counter.getTxTimeCounters().length)];
long totalTxTimeMs = 0;
for (int i = 0; i < txTimeMs.length; i++) {
@@ -13708,7 +13693,7 @@ public class BatteryStatsImpl extends BatteryStats {
@Override
- public long getSystemServiceTimeAtCpuSpeed(int cluster, int step) {
+ public long[] getSystemServiceTimeAtCpuSpeeds() {
// Estimates the time spent by the system server handling incoming binder requests.
//
// The data that we can get from the kernel is this:
@@ -13724,7 +13709,7 @@ public class BatteryStatsImpl extends BatteryStats {
// - These 10 threads spent 1000 ms of CPU time in aggregate
// - Of the 10 threads 4 were execute exclusively incoming binder calls.
// - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
- // - The real time spent by the system server UID doing all of this is, say, 200 ms.
+ // - The real time spent by the system server process doing all of this is, say, 200 ms.
//
// We will assume that power consumption is proportional to the time spent by the CPU
// across all threads. This is a crude assumption, but we don't have more detailed data.
@@ -13738,41 +13723,29 @@ public class BatteryStatsImpl extends BatteryStats {
// of the total power consumed by incoming binder calls for the given cluster/speed
// combination.
- if (mSystemServerThreadCpuTimesUs == null) {
- return 0;
- }
-
- if (cluster < 0 || cluster >= mSystemServerThreadCpuTimesUs.length) {
- return 0;
- }
-
- final LongSamplingCounter[] threadTimesForCluster = mSystemServerThreadCpuTimesUs[cluster];
-
- if (step < 0 || step >= threadTimesForCluster.length) {
- return 0;
- }
-
- Uid systemUid = mUidStats.get(Process.SYSTEM_UID);
- if (systemUid == null) {
- return 0;
+ if (mSystemServerCpuTimesUs == null) {
+ return null;
}
- final long uidTimeAtCpuSpeedUs = systemUid.getTimeAtCpuSpeed(cluster, step,
+ final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
BatteryStats.STATS_SINCE_CHARGED);
- if (uidTimeAtCpuSpeedUs == 0) {
- return 0;
- }
- final long uidThreadTimeUs =
- threadTimesForCluster[step].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
+ final int size = systemServerCpuTimesUs.length;
+ final long[] results = new long[size];
- if (uidThreadTimeUs == 0) {
- return 0;
- }
+ for (int i = 0; i < size; i++) {
+ if (systemServerThreadCpuTimesUs[i] == 0) {
+ continue;
+ }
- final long binderThreadTimeUs = mBinderThreadCpuTimesUs[cluster][step].getCountLocked(
- BatteryStats.STATS_SINCE_CHARGED);
- return uidTimeAtCpuSpeedUs * binderThreadTimeUs / uidThreadTimeUs;
+ results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
+ / systemServerThreadCpuTimesUs[i];
+ }
+ return results;
}
/**
@@ -14174,18 +14147,14 @@ public class BatteryStatsImpl extends BatteryStats {
updateSystemServiceCallStats();
if (mSystemServerThreadCpuTimesUs != null) {
pw.println("Per UID System server binder time in ms:");
+ long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
for (int i = 0; i < size; i++) {
int u = mUidStats.keyAt(i);
Uid uid = mUidStats.get(u);
double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
-
long timeUs = 0;
- for (int cluster = 0; cluster < mSystemServerThreadCpuTimesUs.length; cluster++) {
- int numSpeeds = mSystemServerThreadCpuTimesUs[cluster].length;
- for (int speed = 0; speed < numSpeeds; speed++) {
- timeUs += getSystemServiceTimeAtCpuSpeed(cluster, speed)
- * proportionalSystemServiceUsage;
- }
+ for (int j = systemServiceTimeAtCpuSpeeds.length - 1; j >= 0; j--) {
+ timeUs += systemServiceTimeAtCpuSpeeds[j] * proportionalSystemServiceUsage;
}
pw.print(" ");
@@ -14569,6 +14538,7 @@ public class BatteryStatsImpl extends BatteryStats {
mDailyStartTimeMs = in.readLong();
mNextMinDailyDeadlineMs = in.readLong();
mNextMaxDailyDeadlineMs = in.readLong();
+ mBatteryTimeToFullSeconds = in.readLong();
mStartCount++;
@@ -15061,6 +15031,7 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeLong(mDailyStartTimeMs);
out.writeLong(mNextMinDailyDeadlineMs);
out.writeLong(mNextMaxDailyDeadlineMs);
+ out.writeLong(mBatteryTimeToFullSeconds);
mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
@@ -15607,7 +15578,7 @@ public class BatteryStatsImpl extends BatteryStats {
mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
NUM_BT_TX_LEVELS, in);
mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
- ModemActivityInfo.TX_POWER_LEVELS, in);
+ ModemActivityInfo.getNumTxPowerLevels(), in);
mHasWifiReporting = in.readInt() != 0;
mHasBluetoothReporting = in.readInt() != 0;
mHasModemReporting = in.readInt() != 0;
@@ -15644,6 +15615,7 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
mLastWriteTimeMs = in.readLong();
+ mBatteryTimeToFullSeconds = in.readLong();
mRpmStats.clear();
int NRPMS = in.readInt();
@@ -15718,8 +15690,10 @@ public class BatteryStatsImpl extends BatteryStats {
mUidStats.append(uid, u);
}
- mSystemServerThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
- mBinderThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
+ mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
+ mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
+ mOnBatteryTimeBase);
+ mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
}
public void writeToParcel(Parcel out, int flags) {
@@ -15768,7 +15742,7 @@ public class BatteryStatsImpl extends BatteryStats {
mScreenOnTimer.writeToParcel(out, uSecRealtime);
mScreenDozeTimer.writeToParcel(out, uSecRealtime);
- for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
}
mInteractiveTimer.writeToParcel(out, uSecRealtime);
@@ -15784,7 +15758,7 @@ public class BatteryStatsImpl extends BatteryStats {
mPhoneSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
}
mPhoneSignalScanningTimer.writeToParcel(out, uSecRealtime);
- for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+ for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; i++) {
mPhoneDataConnectionsTimer[i].writeToParcel(out, uSecRealtime);
}
for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
@@ -15799,18 +15773,18 @@ public class BatteryStatsImpl extends BatteryStats {
mWifiMulticastWakelockTimer.writeToParcel(out, uSecRealtime);
mWifiOnTimer.writeToParcel(out, uSecRealtime);
mGlobalWifiRunningTimer.writeToParcel(out, uSecRealtime);
- for (int i=0; i<NUM_WIFI_STATES; i++) {
+ for (int i = 0; i < NUM_WIFI_STATES; i++) {
mWifiStateTimer[i].writeToParcel(out, uSecRealtime);
}
- for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
+ for (int i = 0; i < NUM_WIFI_SUPPL_STATES; i++) {
mWifiSupplStateTimer[i].writeToParcel(out, uSecRealtime);
}
- for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+ for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
mWifiSignalStrengthsTimer[i].writeToParcel(out, uSecRealtime);
}
mWifiActiveTimer.writeToParcel(out, uSecRealtime);
mWifiActivity.writeToParcel(out, 0);
- for (int i=0; i< GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
+ for (int i = 0; i < GnssMetrics.NUM_GPS_SIGNAL_QUALITY_LEVELS; i++) {
mGpsSignalQualityTimer[i].writeToParcel(out, uSecRealtime);
}
mBluetoothActivity.writeToParcel(out, 0);
@@ -15843,6 +15817,7 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeLightDozeCounter.writeToParcel(out);
mDischargeDeepDozeCounter.writeToParcel(out);
out.writeLong(mLastWriteTimeMs);
+ out.writeLong(mBatteryTimeToFullSeconds);
out.writeInt(mRpmStats.size());
for (Map.Entry<String, SamplingTimer> ent : mRpmStats.entrySet()) {
@@ -15919,8 +15894,9 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
- writeCpuSpeedCountersToParcel(out, mSystemServerThreadCpuTimesUs);
- writeCpuSpeedCountersToParcel(out, mBinderThreadCpuTimesUs);
+ LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
+ LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
+ LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
}
private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) {
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index f5bef0b006f5..70b1ad49d8b8 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -119,8 +119,8 @@ public class BinderCallsStats implements BinderInternal.Observer {
if (uidEntry != null) {
ArrayMap<CallStatKey, CallStat> callStats = uidEntry.mCallStats;
mCallStatsObserver.noteCallStats(uidEntry.workSourceUid,
- uidEntry.incrementalCallCount, callStats.values(),
- mNativeTids.toArray());
+ uidEntry.incrementalCallCount, callStats.values()
+ );
uidEntry.incrementalCallCount = 0;
for (int j = callStats.size() - 1; j >= 0; j--) {
callStats.valueAt(j).incrementalCallCount = 0;
@@ -168,6 +168,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
public void setCallStatsObserver(
BinderInternal.CallStatsObserver callStatsObserver) {
mCallStatsObserver = callStatsObserver;
+ noteBinderThreadNativeIds();
noteCallsStatsDelayed();
}
@@ -182,13 +183,13 @@ public class BinderCallsStats implements BinderInternal.Observer {
@Override
@Nullable
public CallSession callStarted(Binder binder, int code, int workSourceUid) {
+ noteNativeThreadId();
+
if (!mRecordingAllTransactionsForUid
&& (mDeviceState == null || mDeviceState.isCharging())) {
return null;
}
- noteNativeThreadId();
-
final CallSession s = obtainCallSession();
s.binderClass = binder.getClass();
s.transactionCode = code;
@@ -359,6 +360,16 @@ public class BinderCallsStats implements BinderInternal.Observer {
mNativeTids = copyOnWriteArray;
}
}
+
+ noteBinderThreadNativeIds();
+ }
+
+ private void noteBinderThreadNativeIds() {
+ if (mCallStatsObserver == null) {
+ return;
+ }
+
+ mCallStatsObserver.noteBinderThreadNativeIds(getNativeTids());
}
/**
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 2645b8e84cf1..c14d8d805d29 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -143,8 +143,12 @@ public class BinderInternal {
* Notes incoming binder call stats associated with this work source UID.
*/
void noteCallStats(int workSourceUid, long incrementalCallCount,
- Collection<BinderCallsStats.CallStat> callStats,
- int[] binderThreadNativeTids);
+ Collection<BinderCallsStats.CallStat> callStats);
+
+ /**
+ * Notes the native IDs of threads taking incoming binder calls.
+ */
+ void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
}
/**
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 2ba372a47cf3..0843741d237c 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -18,10 +18,10 @@ package com.android.internal.os;
import android.annotation.Nullable;
import android.os.Process;
+import android.util.IntArray;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import java.io.IOException;
@@ -456,14 +456,14 @@ public class KernelCpuThreadReader {
* cluster has started.
*/
private static int[] getClusterStartIndices(long[] frequencies) {
- ArrayList<Integer> indices = new ArrayList<>();
+ IntArray indices = new IntArray();
indices.add(0);
for (int i = 0; i < frequencies.length - 1; i++) {
if (frequencies[i] >= frequencies[i + 1]) {
indices.add(i + 1);
}
}
- return ArrayUtils.convertToIntArray(indices);
+ return indices.toArray();
}
/** Get the index in frequencies where each bucket starts */
@@ -477,7 +477,7 @@ public class KernelCpuThreadReader {
return Arrays.copyOfRange(clusterStartIndices, 0, targetNumBuckets);
}
- ArrayList<Integer> bucketStartIndices = new ArrayList<>();
+ IntArray bucketStartIndices = new IntArray();
for (int clusterIdx = 0; clusterIdx < numClusters; clusterIdx++) {
final int clusterStartIdx = getLowerBound(clusterIdx, clusterStartIndices);
final int clusterEndIdx =
@@ -509,7 +509,7 @@ public class KernelCpuThreadReader {
bucketStartIndices.add(bucketStartIdx);
}
}
- return ArrayUtils.convertToIntArray(bucketStartIndices);
+ return bucketStartIndices.toArray();
}
private static int getLowerBound(int index, int[] startIndices) {
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
new file mode 100644
index 000000000000..e6a962312a00
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -0,0 +1,295 @@
+/*
+ * 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.internal.os;
+
+import static android.os.Process.PROC_OUT_LONG;
+import static android.os.Process.PROC_SPACE_TERM;
+
+import android.annotation.Nullable;
+import android.os.Process;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+/**
+ * Iterates over all threads owned by a given process, and return the CPU usage for
+ * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
+ * usage is collected using {@link ProcTimeInStateReader}.
+ */
+public class KernelSingleProcessCpuThreadReader {
+
+ private static final String TAG = "KernelSingleProcCpuThreadRdr";
+
+ private static final boolean DEBUG = false;
+ private static final boolean NATIVE_ENABLED = true;
+
+ /**
+ * The name of the file to read CPU statistics from, must be found in {@code
+ * /proc/$PID/task/$TID}
+ */
+ private static final String CPU_STATISTICS_FILENAME = "time_in_state";
+
+ private static final String PROC_STAT_FILENAME = "stat";
+
+ /** Directory under /proc/$PID containing CPU stats files for threads */
+ public static final String THREAD_CPU_STATS_DIRECTORY = "task";
+
+ /** Default mount location of the {@code proc} filesystem */
+ private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
+
+ /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
+ private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");
+
+ /** See https://man7.org/linux/man-pages/man5/proc.5.html */
+ private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM | PROC_OUT_LONG, // 14: utime
+ PROC_SPACE_TERM | PROC_OUT_LONG, // 15: stime
+ // Ignore remaining fields
+ };
+
+ private final long[] mProcessFullStatsData = new long[2];
+
+ private static final int PROCESS_FULL_STAT_UTIME = 0;
+ private static final int PROCESS_FULL_STAT_STIME = 1;
+
+ /** Used to read and parse {@code time_in_state} files */
+ private final ProcTimeInStateReader mProcTimeInStateReader;
+
+ private final int mPid;
+
+ /** Where the proc filesystem is mounted */
+ private final Path mProcPath;
+
+ // How long a CPU jiffy is in milliseconds.
+ private final long mJiffyMillis;
+
+ // Path: /proc/<pid>/stat
+ private final String mProcessStatFilePath;
+
+ // Path: /proc/<pid>/task
+ private final Path mThreadsDirectoryPath;
+
+ /**
+ * Count of frequencies read from the {@code time_in_state} file. Read from {@link
+ * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
+ */
+ private int mFrequencyCount;
+
+ /**
+ * Create with a path where `proc` is mounted. Used primarily for testing
+ *
+ * @param pid PID of the process whose threads are to be read.
+ * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
+ */
+ @VisibleForTesting
+ public KernelSingleProcessCpuThreadReader(
+ int pid,
+ Path procPath) throws IOException {
+ mPid = pid;
+ mProcPath = procPath;
+ mProcTimeInStateReader = new ProcTimeInStateReader(
+ mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH));
+ long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
+ mJiffyMillis = 1000 / jiffyHz;
+ mProcessStatFilePath =
+ mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString();
+ mThreadsDirectoryPath =
+ mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY);
+ }
+
+ /**
+ * Create the reader and handle exceptions during creation
+ *
+ * @return the reader, null if an exception was thrown during creation
+ */
+ @Nullable
+ public static KernelSingleProcessCpuThreadReader create(int pid) {
+ try {
+ return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get the CPU frequencies that correspond to the times reported in {@link
+ * ProcessCpuUsage#processCpuTimesMillis} etc.
+ */
+ public int getCpuFrequencyCount() {
+ if (mFrequencyCount == 0) {
+ mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
+ }
+ return mFrequencyCount;
+ }
+
+ /**
+ * Get the total and per-thread CPU usage of the process with the PID specified in the
+ * constructor.
+ *
+ * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
+ * be aggregated as a group. This is expected to be a subset
+ * of all thread IDs owned by the process.
+ */
+ @Nullable
+ public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) {
+ if (DEBUG) {
+ Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
+ + mPid);
+ }
+
+ int cpuFrequencyCount = getCpuFrequencyCount();
+ ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
+
+ if (NATIVE_ENABLED) {
+ boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
+ selectedThreadIds, processCpuUsage.processCpuTimesMillis,
+ processCpuUsage.threadCpuTimesMillis,
+ processCpuUsage.selectedThreadCpuTimesMillis);
+ if (!result) {
+ return null;
+ }
+ return processCpuUsage;
+ }
+
+ if (!isSorted(selectedThreadIds)) {
+ throw new IllegalArgumentException("selectedThreadIds is not sorted: "
+ + Arrays.toString(selectedThreadIds));
+ }
+
+ if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
+ mProcessFullStatsData, null)) {
+ Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
+ return null;
+ }
+
+ long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
+ long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
+
+ long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
+
+ try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
+ for (Path threadDirectory : threadPaths) {
+ readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
+ }
+ } catch (IOException | DirectoryIteratorException e) {
+ // Expected when a process finishes
+ return null;
+ }
+
+ // Estimate per cluster per frequency CPU time for the entire process
+ // by distributing the total process CPU time proportionately to how much
+ // CPU time its threads took on those clusters/frequencies. This algorithm
+ // works more accurately when when we have equally distributed concurrency.
+ // TODO(b/169279846): obtain actual process CPU times from the kernel
+ long totalCpuTimeAllThreads = 0;
+ for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
+ totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
+ }
+
+ for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
+ processCpuUsage.processCpuTimesMillis[i] =
+ processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
+ / totalCpuTimeAllThreads;
+ }
+
+ return processCpuUsage;
+ }
+
+ /**
+ * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
+ *
+ * @param threadDirectory the {@code /proc} directory of the thread
+ */
+ private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
+ Path threadDirectory) {
+ // Get the thread ID from the directory name
+ final int threadId;
+ try {
+ final String directoryName = threadDirectory.getFileName().toString();
+ threadId = Integer.parseInt(directoryName);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
+ return;
+ }
+
+ // Get the CPU statistics from the directory
+ final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
+ final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
+ if (cpuUsages == null) {
+ return;
+ }
+
+ final int cpuFrequencyCount = getCpuFrequencyCount();
+ final boolean isSelectedThread = Arrays.binarySearch(selectedThreadIds, threadId) >= 0;
+ for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
+ processCpuUsage.threadCpuTimesMillis[i] += cpuUsages[i];
+ if (isSelectedThread) {
+ processCpuUsage.selectedThreadCpuTimesMillis[i] += cpuUsages[i];
+ }
+ }
+ }
+
+ /** CPU usage of a process, all of its threads and a selected subset of its threads */
+ public static class ProcessCpuUsage {
+ public long[] processCpuTimesMillis;
+ public long[] threadCpuTimesMillis;
+ public long[] selectedThreadCpuTimesMillis;
+
+ public ProcessCpuUsage(int cpuFrequencyCount) {
+ processCpuTimesMillis = new long[cpuFrequencyCount];
+ threadCpuTimesMillis = new long[cpuFrequencyCount];
+ selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
+ }
+ }
+
+ private static boolean isSorted(int[] array) {
+ for (int i = 0; i < array.length - 1; i++) {
+ if (array[i] > array[i + 1]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
+ long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
+ long[] selectedThreadCpuTimesMillis);
+}
diff --git a/core/java/com/android/internal/os/ProcTimeInStateReader.java b/core/java/com/android/internal/os/ProcTimeInStateReader.java
index 2318473354e2..d54a9c7f3a46 100644
--- a/core/java/com/android/internal/os/ProcTimeInStateReader.java
+++ b/core/java/com/android/internal/os/ProcTimeInStateReader.java
@@ -18,16 +18,11 @@ package com.android.internal.os;
import android.annotation.Nullable;
import android.os.Process;
-
-import com.android.internal.util.ArrayUtils;
+import android.util.IntArray;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
/**
* Reads and parses {@code time_in_state} files in the {@code proc} filesystem.
@@ -68,24 +63,25 @@ public class ProcTimeInStateReader {
* The format of a single line of the {@code time_in_state} file that exports the frequency
* values
*/
- private static final List<Integer> TIME_IN_STATE_LINE_FREQUENCY_FORMAT = Arrays.asList(
+ private static final int[] TIME_IN_STATE_LINE_FREQUENCY_FORMAT = new int[] {
Process.PROC_OUT_LONG | Process.PROC_SPACE_TERM,
Process.PROC_NEWLINE_TERM
- );
+ };
/**
* The format of a single line of the {@code time_in_state} file that exports the time values
*/
- private static final List<Integer> TIME_IN_STATE_LINE_TIME_FORMAT = Arrays.asList(
+ private static final int[] TIME_IN_STATE_LINE_TIME_FORMAT = new int[] {
Process.PROC_SPACE_TERM,
Process.PROC_OUT_LONG | Process.PROC_NEWLINE_TERM
- );
+ };
/**
* The format of a header line of the {@code time_in_state} file
*/
- private static final List<Integer> TIME_IN_STATE_HEADER_LINE_FORMAT =
- Collections.singletonList(Process.PROC_NEWLINE_TERM);
+ private static final int[] TIME_IN_STATE_HEADER_LINE_FORMAT = new int[] {
+ Process.PROC_NEWLINE_TERM
+ };
/**
* The format of the {@code time_in_state} file to extract times, defined using {@link
@@ -166,8 +162,8 @@ public class ProcTimeInStateReader {
// formats. These formats are used to extract either the frequencies or the times from a
// time_in_state file
// Also check if each line is a header, and handle this in the created format arrays
- ArrayList<Integer> timeInStateFrequencyFormat = new ArrayList<>();
- ArrayList<Integer> timeInStateTimeFormat = new ArrayList<>();
+ IntArray timeInStateFrequencyFormat = new IntArray();
+ IntArray timeInStateTimeFormat = new IntArray();
int numFrequencies = 0;
for (int i = 0; i < timeInStateBytes.length; i++) {
// If the first character of the line is not a digit, we treat it as a header
@@ -194,12 +190,12 @@ public class ProcTimeInStateReader {
final long[] readLongs = new long[numFrequencies];
final boolean readSuccess = Process.parseProcLine(
timeInStateBytes, 0, timeInStateBytes.length,
- ArrayUtils.convertToIntArray(timeInStateFrequencyFormat), null, readLongs, null);
+ timeInStateFrequencyFormat.toArray(), null, readLongs, null);
if (!readSuccess) {
throw new IOException("Failed to parse time_in_state file");
}
- mTimeInStateTimeFormat = ArrayUtils.convertToIntArray(timeInStateTimeFormat);
+ mTimeInStateTimeFormat = timeInStateTimeFormat.toArray();
mFrequenciesKhz = readLongs;
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 095882ebe669..60f1b4438f54 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -36,7 +36,6 @@ import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.RuntimeHooks;
-import dalvik.system.ThreadPrioritySetter;
import dalvik.system.VMRuntime;
import libcore.content.type.MimeMap;
@@ -208,7 +207,6 @@ public class RuntimeInit {
*/
public static void preForkInit() {
if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
- RuntimeHooks.setThreadPrioritySetter(new RuntimeThreadPrioritySetter());
RuntimeInit.enableDdms();
// TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e.
// MimeMap.setDefault(DefaultMimeMapFactory.create());
@@ -221,35 +219,6 @@ public class RuntimeInit {
MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create);
}
- private static class RuntimeThreadPrioritySetter implements ThreadPrioritySetter {
- // Should remain consistent with kNiceValues[] in system/libartpalette/palette_android.cc
- private static final int[] NICE_VALUES = {
- Process.THREAD_PRIORITY_LOWEST, // 1 (MIN_PRIORITY)
- Process.THREAD_PRIORITY_BACKGROUND + 6,
- Process.THREAD_PRIORITY_BACKGROUND + 3,
- Process.THREAD_PRIORITY_BACKGROUND,
- Process.THREAD_PRIORITY_DEFAULT, // 5 (NORM_PRIORITY)
- Process.THREAD_PRIORITY_DEFAULT - 2,
- Process.THREAD_PRIORITY_DEFAULT - 4,
- Process.THREAD_PRIORITY_URGENT_DISPLAY + 3,
- Process.THREAD_PRIORITY_URGENT_DISPLAY + 2,
- Process.THREAD_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY)
- };
-
- @Override
- public void setPriority(int nativeTid, int priority) {
- // Check NICE_VALUES[] length first.
- if (NICE_VALUES.length != (1 + Thread.MAX_PRIORITY - Thread.MIN_PRIORITY)) {
- throw new AssertionError("Unexpected NICE_VALUES.length=" + NICE_VALUES.length);
- }
- // Priority should be in the range of MIN_PRIORITY (1) to MAX_PRIORITY (10).
- if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
- throw new IllegalArgumentException("Priority out of range: " + priority);
- }
- Process.setThreadPriority(nativeTid, NICE_VALUES[priority - Thread.MIN_PRIORITY]);
- }
- }
-
@UnsupportedAppUsage
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index 1cdd42c7403e..fbbee94feacc 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -16,13 +16,13 @@
package com.android.internal.os;
+import android.annotation.Nullable;
import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -30,11 +30,10 @@ import java.util.Arrays;
* by various threads of the System Server.
*/
public class SystemServerCpuThreadReader {
- private KernelCpuThreadReader mKernelCpuThreadReader;
- private int[] mBinderThreadNativeTids;
+ private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
+ private int[] mBinderThreadNativeTids = new int[0]; // Sorted
- private int[] mThreadCpuTimesUs;
- private int[] mBinderThreadCpuTimesUs;
+ private long[] mLastProcessCpuTimeUs;
private long[] mLastThreadCpuTimesUs;
private long[] mLastBinderThreadCpuTimesUs;
@@ -42,110 +41,77 @@ public class SystemServerCpuThreadReader {
* Times (in microseconds) spent by the system server UID.
*/
public static class SystemServiceCpuThreadTimes {
+ // The entire process
+ public long[] processCpuTimesUs;
// All threads
public long[] threadCpuTimesUs;
// Just the threads handling incoming binder calls
public long[] binderThreadCpuTimesUs;
}
- private SystemServiceCpuThreadTimes mDeltaCpuThreadTimes = new SystemServiceCpuThreadTimes();
+ private final SystemServiceCpuThreadTimes mDeltaCpuThreadTimes =
+ new SystemServiceCpuThreadTimes();
/**
* Creates a configured instance of SystemServerCpuThreadReader.
*/
public static SystemServerCpuThreadReader create() {
return new SystemServerCpuThreadReader(
- KernelCpuThreadReader.create(0, uid -> uid == Process.myUid()));
+ KernelSingleProcessCpuThreadReader.create(Process.myPid()));
}
@VisibleForTesting
- public SystemServerCpuThreadReader(Path procPath, int systemServerUid) throws IOException {
- this(new KernelCpuThreadReader(0, uid -> uid == systemServerUid, null, null,
- new KernelCpuThreadReader.Injector() {
- @Override
- public int getUidForPid(int pid) {
- return systemServerUid;
- }
- }));
+ public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException {
+ this(new KernelSingleProcessCpuThreadReader(pid, procPath));
}
@VisibleForTesting
- public SystemServerCpuThreadReader(KernelCpuThreadReader kernelCpuThreadReader) {
+ public SystemServerCpuThreadReader(KernelSingleProcessCpuThreadReader kernelCpuThreadReader) {
mKernelCpuThreadReader = kernelCpuThreadReader;
}
public void setBinderThreadNativeTids(int[] nativeTids) {
- mBinderThreadNativeTids = nativeTids;
+ mBinderThreadNativeTids = nativeTids.clone();
+ Arrays.sort(mBinderThreadNativeTids);
}
/**
* Returns delta of CPU times, per thread, since the previous call to this method.
*/
+ @Nullable
public SystemServiceCpuThreadTimes readDelta() {
- if (mBinderThreadCpuTimesUs == null) {
- int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz().length;
- mThreadCpuTimesUs = new int[numCpuFrequencies];
- mBinderThreadCpuTimesUs = new int[numCpuFrequencies];
-
+ final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
+ if (mLastProcessCpuTimeUs == null) {
+ mLastProcessCpuTimeUs = new long[numCpuFrequencies];
mLastThreadCpuTimesUs = new long[numCpuFrequencies];
mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];
+ mDeltaCpuThreadTimes.processCpuTimesUs = new long[numCpuFrequencies];
mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
}
- Arrays.fill(mThreadCpuTimesUs, 0);
- Arrays.fill(mBinderThreadCpuTimesUs, 0);
-
- ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsage =
- mKernelCpuThreadReader.getProcessCpuUsage();
- int processCpuUsageSize = processCpuUsage.size();
- for (int i = 0; i < processCpuUsageSize; i++) {
- KernelCpuThreadReader.ProcessCpuUsage pcu = processCpuUsage.get(i);
- ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = pcu.threadCpuUsages;
- if (threadCpuUsages != null) {
- int threadCpuUsagesSize = threadCpuUsages.size();
- for (int j = 0; j < threadCpuUsagesSize; j++) {
- KernelCpuThreadReader.ThreadCpuUsage tcu = threadCpuUsages.get(j);
- boolean isBinderThread = isBinderThread(tcu.threadId);
-
- final int len = Math.min(tcu.usageTimesMillis.length, mThreadCpuTimesUs.length);
- for (int k = 0; k < len; k++) {
- int usageTimeUs = tcu.usageTimesMillis[k] * 1000;
- mThreadCpuTimesUs[k] += usageTimeUs;
- if (isBinderThread) {
- mBinderThreadCpuTimesUs[k] += usageTimeUs;
- }
- }
- }
- }
+ final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
+ mKernelCpuThreadReader.getProcessCpuUsage(mBinderThreadNativeTids);
+ if (processCpuUsage == null) {
+ return null;
}
- for (int i = 0; i < mThreadCpuTimesUs.length; i++) {
- if (mThreadCpuTimesUs[i] < mLastThreadCpuTimesUs[i]) {
- mDeltaCpuThreadTimes.threadCpuTimesUs[i] = mThreadCpuTimesUs[i];
- mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
- } else {
- mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
- mThreadCpuTimesUs[i] - mLastThreadCpuTimesUs[i];
- mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
- mBinderThreadCpuTimesUs[i] - mLastBinderThreadCpuTimesUs[i];
- }
- mLastThreadCpuTimesUs[i] = mThreadCpuTimesUs[i];
- mLastBinderThreadCpuTimesUs[i] = mBinderThreadCpuTimesUs[i];
+ for (int i = numCpuFrequencies - 1; i >= 0; i--) {
+ long processCpuTimesUs = processCpuUsage.processCpuTimesMillis[i] * 1000;
+ long threadCpuTimesUs = processCpuUsage.threadCpuTimesMillis[i] * 1000;
+ long binderThreadCpuTimesUs = processCpuUsage.selectedThreadCpuTimesMillis[i] * 1000;
+ mDeltaCpuThreadTimes.processCpuTimesUs[i] =
+ Math.max(0, processCpuTimesUs - mLastProcessCpuTimeUs[i]);
+ mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
+ Math.max(0, threadCpuTimesUs - mLastThreadCpuTimesUs[i]);
+ mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
+ Math.max(0, binderThreadCpuTimesUs - mLastBinderThreadCpuTimesUs[i]);
+ mLastProcessCpuTimeUs[i] = processCpuTimesUs;
+ mLastThreadCpuTimesUs[i] = threadCpuTimesUs;
+ mLastBinderThreadCpuTimesUs[i] = binderThreadCpuTimesUs;
}
return mDeltaCpuThreadTimes;
}
-
- private boolean isBinderThread(int threadId) {
- if (mBinderThreadNativeTids != null) {
- for (int i = 0; i < mBinderThreadNativeTids.length; i++) {
- if (threadId == mBinderThreadNativeTids[i]) {
- return true;
- }
- }
- }
- return false;
- }
}
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index 481b901b3c69..fc36e50950e9 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -34,7 +34,9 @@ public class SystemServicePowerCalculator extends PowerCalculator {
private final PowerProfile mPowerProfile;
private final BatteryStats mBatteryStats;
// Tracks system server CPU [cluster][speed] power in milliAmp-microseconds
- private double[][] mSystemServicePowerMaUs;
+ // Data organized like this:
+ // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...}
+ private double[] mSystemServicePowerMaUs;
public SystemServicePowerCalculator(PowerProfile powerProfile, BatteryStats batteryStats) {
mPowerProfile = powerProfile;
@@ -50,37 +52,41 @@ public class SystemServicePowerCalculator extends PowerCalculator {
updateSystemServicePower();
}
- double cpuPowerMaUs = 0;
- int numCpuClusters = mPowerProfile.getNumCpuClusters();
- for (int cluster = 0; cluster < numCpuClusters; cluster++) {
- final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
- for (int speed = 0; speed < numSpeeds; speed++) {
- cpuPowerMaUs += mSystemServicePowerMaUs[cluster][speed] * proportionalUsage;
+ if (mSystemServicePowerMaUs != null) {
+ double cpuPowerMaUs = 0;
+ for (int i = 0; i < mSystemServicePowerMaUs.length; i++) {
+ cpuPowerMaUs += mSystemServicePowerMaUs[i] * proportionalUsage;
}
- }
- app.systemServiceCpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
+ app.systemServiceCpuPowerMah = cpuPowerMaUs / MICROSEC_IN_HR;
+ }
}
}
private void updateSystemServicePower() {
+ final long[] systemServiceTimeAtCpuSpeeds = mBatteryStats.getSystemServiceTimeAtCpuSpeeds();
+ if (systemServiceTimeAtCpuSpeeds == null) {
+ return;
+ }
+
+ if (mSystemServicePowerMaUs == null) {
+ mSystemServicePowerMaUs = new double[systemServiceTimeAtCpuSpeeds.length];
+ }
+ int index = 0;
final int numCpuClusters = mPowerProfile.getNumCpuClusters();
- mSystemServicePowerMaUs = new double[numCpuClusters][];
for (int cluster = 0; cluster < numCpuClusters; cluster++) {
final int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
- mSystemServicePowerMaUs[cluster] = new double[numSpeeds];
for (int speed = 0; speed < numSpeeds; speed++) {
- mSystemServicePowerMaUs[cluster][speed] =
- mBatteryStats.getSystemServiceTimeAtCpuSpeed(cluster, speed)
+ mSystemServicePowerMaUs[index] =
+ systemServiceTimeAtCpuSpeeds[index]
* mPowerProfile.getAveragePowerForCpuCore(cluster, speed);
+ index++;
}
}
+
if (DEBUG) {
- Log.d(TAG, "System service power per CPU cluster and frequency");
- for (int cluster = 0; cluster < numCpuClusters; cluster++) {
- Log.d(TAG, "Cluster[" + cluster + "]: "
- + Arrays.toString(mSystemServicePowerMaUs[cluster]));
- }
+ Log.d(TAG, "System service power per CPU cluster and frequency:"
+ + Arrays.toString(mSystemServicePowerMaUs));
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index a985965afe17..5e20cd067589 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -172,23 +172,11 @@ public final class Zygote {
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
/** Default external storage should be mounted. */
public static final int MOUNT_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
- /** Read-only external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_READ = IVold.REMOUNT_MODE_READ;
- /** Read-write external storage should be mounted. */
- public static final int MOUNT_EXTERNAL_WRITE = IVold.REMOUNT_MODE_WRITE;
- /**
- * Mount mode for apps that are already installed on the device before the isolated_storage
- * feature is enabled.
- */
- public static final int MOUNT_EXTERNAL_LEGACY = IVold.REMOUNT_MODE_LEGACY;
/**
* Mount mode for package installers which should give them access to
* all obb dirs in addition to their package sandboxes
*/
public static final int MOUNT_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
- /** Read-write external storage should be mounted instead of package sandbox */
- public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
-
/** The lower file system should be bind mounted directly on external storage */
public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index ed074327c3c5..32b808ab2528 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -380,16 +380,8 @@ class ZygoteArguments {
mNiceName = getAssignmentValue(arg);
} else if (arg.equals("--mount-external-default")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
- } else if (arg.equals("--mount-external-read")) {
- mMountExternal = Zygote.MOUNT_EXTERNAL_READ;
- } else if (arg.equals("--mount-external-write")) {
- mMountExternal = Zygote.MOUNT_EXTERNAL_WRITE;
- } else if (arg.equals("--mount-external-full")) {
- mMountExternal = Zygote.MOUNT_EXTERNAL_FULL;
- } else if (arg.equals("--mount-external-installer")) {
+ } else if (arg.equals("--mount-external-installer")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
- } else if (arg.equals("--mount-external-legacy")) {
- mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
} else if (arg.equals("--mount-external-pass-through")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
} else if (arg.equals("--mount-external-android-writable")) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1f8a829aaacd..4512fbac38c1 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1105,15 +1105,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
: controller.getSystemBarsAppearance();
if (insets != null) {
- final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars());
- final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
- WindowInsets.Type.systemBars());
final boolean clearCompatInsets = clearCompatInsets(attrs.type, attrs.flags,
getResources().getConfiguration().windowConfiguration.getWindowingMode());
- mLastTopInset = clearCompatInsets ? 0 : systemBarInsets.top;
- mLastBottomInset = clearCompatInsets ? 0 : systemBarInsets.bottom;
- mLastRightInset = clearCompatInsets ? 0 : systemBarInsets.right;
- mLastLeftInset = clearCompatInsets ? 0 : systemBarInsets.left;
+ final Insets stableBarInsets = insets.getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars());
+ final Insets systemInsets = clearCompatInsets
+ ? Insets.NONE
+ : Insets.min(insets.getInsets(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.displayCutout()), stableBarInsets);
+ mLastTopInset = systemInsets.top;
+ mLastBottomInset = systemInsets.bottom;
+ mLastRightInset = systemInsets.right;
+ mLastLeftInset = systemInsets.left;
// Don't animate if the presence of stable insets has changed, because that
// indicates that the window was either just added and received them for the
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2f8c45770eb5..50eed27976ad 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -1916,8 +1916,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
- getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(
- mMediaController.getSessionToken(), event);
+ getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
+ mMediaController.getSessionToken());
} else {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(event,
mVolumeControlStreamType);
@@ -1938,8 +1938,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if (mMediaController != null) {
- if (getMediaSessionManager().dispatchMediaKeyEventAsSystemService(
- mMediaController.getSessionToken(), event)) {
+ if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService(
+ event, mMediaController.getSessionToken())) {
return true;
}
}
@@ -2010,8 +2010,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
- getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(
- mMediaController.getSessionToken(), event);
+ getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
+ mMediaController.getSessionToken());
} else {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(
event, mVolumeControlStreamType);
@@ -2041,8 +2041,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if (mMediaController != null) {
- if (getMediaSessionManager().dispatchMediaKeyEventAsSystemService(
- mMediaController.getSessionToken(), event)) {
+ if (getMediaSessionManager().dispatchMediaKeyEventToSessionAsSystemService(
+ event, mMediaController.getSessionToken())) {
return true;
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 9f7436a13bdc..ce3efd35ee48 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -48,6 +48,10 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_LOCKTASK(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
+ WM_DEBUG_STATES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
+ WM_DEBUG_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
WM_DEBUG_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
WM_SHOW_TRANSACTIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -72,7 +76,11 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_WINDOW_ORGANIZER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
- TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+ WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
+ WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM),
+ TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
private volatile boolean mLogToProto;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index cc2934fd8dc5..77c7ce8cf5c4 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -102,6 +102,13 @@ oneway interface IStatusBar
void onCameraLaunchGestureDetected(int source);
/**
+ * Notifies the status bar that the Emergency Action launch gesture has been detected.
+ *
+ * TODO(b/169175022) Update method name and docs when feature name is locked.
+ */
+ void onEmergencyActionLaunchGestureDetected();
+
+ /**
* Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
*/
void showPictureInPictureMenu();
@@ -242,4 +249,10 @@ oneway interface IStatusBar
* @param connect {@code true} if needs connection, otherwise set the connection to null.
*/
void requestWindowMagnificationConnection(boolean connect);
+
+ /**
+ * Allow for pass-through arguments from `adb shell cmd statusbar <args>`, and write to the
+ * file descriptor passed in.
+ */
+ void passThroughShellCommand(in String[] args, in ParcelFileDescriptor pfd);
}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index cd69a02d4d2d..c6dea18d77d1 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -310,6 +310,10 @@ public class ArrayUtils {
return total;
}
+ /**
+ * @deprecated use {@code IntArray} instead
+ */
+ @Deprecated
public static int[] convertToIntArray(List<Integer> list) {
int[] array = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
@@ -760,6 +764,7 @@ public class ArrayUtils {
/**
* Returns an array containing elements from the given one that match the given predicate.
+ * The returned array may, in some cases, be the reference to the input array.
*/
public static @Nullable <T> T[] filter(@Nullable T[] items,
@NonNull IntFunction<T[]> arrayConstructor,
@@ -775,16 +780,13 @@ public class ArrayUtils {
matchesCount++;
}
}
- if (matchesCount == 0) {
- return items;
- }
if (matchesCount == items.length) {
return items;
}
+ T[] result = arrayConstructor.apply(matchesCount);
if (matchesCount == 0) {
- return null;
+ return result;
}
- T[] result = arrayConstructor.apply(matchesCount);
int outIdx = 0;
for (int i = 0; i < size; i++) {
if (predicate.test(items[i])) {
diff --git a/core/java/com/android/internal/util/LocationPermissionChecker.java b/core/java/com/android/internal/util/LocationPermissionChecker.java
index cd8fc350362d..59c0c0009640 100644
--- a/core/java/com/android/internal/util/LocationPermissionChecker.java
+++ b/core/java/com/android/internal/util/LocationPermissionChecker.java
@@ -218,7 +218,7 @@ public class LocationPermissionChecker {
}
private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (mContext.getPackageManager().getApplicationInfoAsUser(
packageName, 0,
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index a23fc4b57b45..0bafb2f6ff57 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -279,6 +279,7 @@ public class ScreenshotHelper {
final Runnable mScreenshotTimeout = () -> {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
+ Log.e(TAG, "Timed out before getting screenshot capture response");
mContext.unbindService(mScreenshotConnection);
mScreenshotConnection = null;
mScreenshotService = null;
@@ -353,6 +354,7 @@ public class ScreenshotHelper {
mScreenshotService = null;
// only log an error if we're still within the timeout period
if (handler.hasCallbacks(mScreenshotTimeout)) {
+ Log.e(TAG, "Screenshot service disconnected");
handler.removeCallbacks(mScreenshotTimeout);
notifyScreenshotError();
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index d5f54a199828..fff9ac9e49b7 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -43,7 +43,6 @@ public class BaseIWindow extends IWindow.Stub {
public BaseIWindow() {}
private IWindowSession mSession;
- public int mSeq;
public void setSession(IWindowSession session) {
mSession = session;
@@ -140,12 +139,6 @@ public class BaseIWindow extends IWindow.Stub {
}
@Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
- int localValue, int localChanges) {
- mSeq = seq;
- }
-
- @Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index 45090320c192..c9443b002133 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -33,4 +33,5 @@ oneway interface IInputMethodClient {
void reportPreRendered(in EditorInfo info);
void applyImeVisibility(boolean setVisible);
void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues);
+ void setImeTraceEnabled(boolean enabled);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index a1cbd3fcae79..5a06273bb173 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -77,4 +77,6 @@ interface IInputMethodManager {
void removeImeSurface();
/** Remove the IME surface. Requires passing the currently focused window. */
void removeImeSurfaceFromWindow(in IBinder windowToken);
+ void startProtoDump(in byte[] clientProtoDump);
+ boolean isImeTraceEnabled();
}
diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
new file mode 100644
index 000000000000..461e11668509
--- /dev/null
+++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
@@ -0,0 +1,202 @@
+/*
+ * 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.internal.view;
+
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * ScrollCapture for RecyclerView and <i>RecyclerView-like</i> ViewGroups.
+ * <p>
+ * Requirements for proper operation:
+ * <ul>
+ * <li>at least one visible child view</li>
+ * <li>scrolls by pixels in response to {@link View#scrollBy(int, int)}.
+ * <li>reports ability to scroll with {@link View#canScrollVertically(int)}
+ * <li>properly implements {@link ViewParent#requestChildRectangleOnScreen(View, Rect, boolean)}
+ * </ul>
+ *
+ * @see ScrollCaptureViewSupport
+ */
+public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
+
+ // Experiment
+ private static final boolean DISABLE_ANIMATORS = false;
+ // Experiment
+ private static final boolean STOP_RENDER_THREAD = false;
+
+ private static final String TAG = "RVCaptureHelper";
+ private int mScrollDelta;
+ private boolean mScrollBarWasEnabled;
+ private int mOverScrollMode;
+ private float mDurationScale;
+
+ @Override
+ public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
+ mScrollDelta = 0;
+
+ mOverScrollMode = view.getOverScrollMode();
+ view.setOverScrollMode(View.OVER_SCROLL_NEVER);
+
+ mScrollBarWasEnabled = view.isVerticalScrollBarEnabled();
+ view.setVerticalScrollBarEnabled(false);
+ if (DISABLE_ANIMATORS) {
+ mDurationScale = ValueAnimator.getDurationScale();
+ ValueAnimator.setDurationScale(0);
+ }
+ if (STOP_RENDER_THREAD) {
+ view.getThreadedRenderer().stop();
+ }
+ }
+
+ @Override
+ public ScrollResult onScrollRequested(@NonNull ViewGroup recyclerView, Rect scrollBounds,
+ Rect requestRect) {
+ ScrollResult result = new ScrollResult();
+ result.requestedArea = new Rect(requestRect);
+ result.scrollDelta = mScrollDelta;
+ result.availableArea = new Rect(); // empty
+
+ Log.d(TAG, "current scrollDelta: " + mScrollDelta);
+ if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
+ Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
+ return result; // result.availableArea == empty Rect
+ }
+
+ // move from scrollBounds-relative to parent-local coordinates
+ Rect requestedContainerBounds = new Rect(requestRect);
+ requestedContainerBounds.offset(0, -mScrollDelta);
+ requestedContainerBounds.offset(scrollBounds.left, scrollBounds.top);
+
+ // requestedContainerBounds is now in recyclerview-local coordinates
+ Log.d(TAG, "requestedContainerBounds: " + requestedContainerBounds);
+
+ // Save a copy for later
+ View anchor = findChildNearestTarget(recyclerView, requestedContainerBounds);
+ if (anchor == null) {
+ Log.d(TAG, "Failed to locate anchor view");
+ return result; // result.availableArea == null
+ }
+
+ Log.d(TAG, "Anchor view:" + anchor);
+ Rect requestedContentBounds = new Rect(requestedContainerBounds);
+ recyclerView.offsetRectIntoDescendantCoords(anchor, requestedContentBounds);
+
+ Log.d(TAG, "requestedContentBounds = " + requestedContentBounds);
+ int prevAnchorTop = anchor.getTop();
+ // Note: requestChildRectangleOnScreen may modify rectangle, must pass pass in a copy here
+ Rect input = new Rect(requestedContentBounds);
+ if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) {
+ int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement
+ Log.d(TAG, "RecyclerView scrolled by " + scrolled + " px");
+ mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++
+ result.scrollDelta = mScrollDelta;
+ Log.d(TAG, "requestedContentBounds, (post-request-rect) = " + requestedContentBounds);
+ }
+
+ requestedContainerBounds.set(requestedContentBounds);
+ recyclerView.offsetDescendantRectToMyCoords(anchor, requestedContainerBounds);
+ Log.d(TAG, "requestedContainerBounds, (post-scroll): " + requestedContainerBounds);
+
+ Rect recyclerLocalVisible = new Rect(scrollBounds);
+ recyclerView.getLocalVisibleRect(recyclerLocalVisible);
+ Log.d(TAG, "recyclerLocalVisible: " + recyclerLocalVisible);
+
+ if (!requestedContainerBounds.intersect(recyclerLocalVisible)) {
+ // Requested area is still not visible
+ Log.d(TAG, "requested bounds not visible!");
+ return result;
+ }
+ Rect available = new Rect(requestedContainerBounds);
+ available.offset(-scrollBounds.left, -scrollBounds.top);
+ available.offset(0, mScrollDelta);
+ result.availableArea = available;
+ Log.d(TAG, "availableArea: " + result.availableArea);
+ return result;
+ }
+
+ /**
+ * Find a view that is located "closest" to targetRect. Returns the first view to fully
+ * vertically overlap the target targetRect. If none found, returns the view with an edge
+ * nearest the target targetRect.
+ *
+ * @param parent the parent vertical layout
+ * @param targetRect a rectangle in local coordinates of <code>parent</code>
+ * @return a child view within parent matching the criteria or null
+ */
+ static View findChildNearestTarget(ViewGroup parent, Rect targetRect) {
+ View selected = null;
+ int minCenterDistance = Integer.MAX_VALUE;
+ int maxOverlap = 0;
+
+ // allowable center-center distance, relative to targetRect.
+ // if within this range, taller views are preferred
+ final float preferredRangeFromCenterPercent = 0.25f;
+ final int preferredDistance =
+ (int) (preferredRangeFromCenterPercent * targetRect.height());
+
+ Rect parentLocalVis = new Rect();
+ parent.getLocalVisibleRect(parentLocalVis);
+ Log.d(TAG, "findChildNearestTarget: parentVis=" + parentLocalVis
+ + " targetRect=" + targetRect);
+
+ Rect frame = new Rect();
+ for (int i = 0; i < parent.getChildCount(); i++) {
+ final View child = parent.getChildAt(i);
+ child.getHitRect(frame);
+ Log.d(TAG, "child #" + i + " hitRect=" + frame);
+
+ if (child.getVisibility() != View.VISIBLE) {
+ Log.d(TAG, "child #" + i + " is not visible");
+ continue;
+ }
+
+ int centerDistance = Math.abs(targetRect.centerY() - frame.centerY());
+ Log.d(TAG, "child #" + i + " : center to center: " + centerDistance + "px");
+
+ if (centerDistance < minCenterDistance) {
+ // closer to center
+ minCenterDistance = centerDistance;
+ selected = child;
+ } else if (frame.intersect(targetRect) && (frame.height() > preferredDistance)) {
+ // within X% pixels of center, but taller
+ selected = child;
+ }
+ }
+ return selected;
+ }
+
+
+ @Override
+ public void onPrepareForEnd(@NonNull ViewGroup view) {
+ // Restore original position and state
+ view.scrollBy(0, mScrollDelta);
+ view.setOverScrollMode(mOverScrollMode);
+ view.setVerticalScrollBarEnabled(mScrollBarWasEnabled);
+ if (DISABLE_ANIMATORS) {
+ ValueAnimator.setDurationScale(mDurationScale);
+ }
+ if (STOP_RENDER_THREAD) {
+ view.getThreadedRenderer().start();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
index c589afdeaa1a..ae1a815910ed 100644
--- a/core/java/com/android/internal/view/ScrollCaptureInternal.java
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -17,8 +17,11 @@
package com.android.internal.view;
import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.util.Log;
import android.view.ScrollCaptureCallback;
import android.view.View;
import android.view.ViewGroup;
@@ -29,6 +32,12 @@ import android.view.ViewGroup;
public class ScrollCaptureInternal {
private static final String TAG = "ScrollCaptureInternal";
+ // Log found scrolling views
+ private static final boolean DEBUG = true;
+
+ // Log all investigated views, as well as heuristic checks
+ private static final boolean DEBUG_VERBOSE = false;
+
private static final int UP = -1;
private static final int DOWN = 1;
@@ -57,38 +66,72 @@ public class ScrollCaptureInternal {
* This needs to be fast and not alloc memory. It's called on everything in the tree not marked
* as excluded during scroll capture search.
*/
- public static int detectScrollingType(View view) {
+ private static int detectScrollingType(View view) {
// Must be a ViewGroup
if (!(view instanceof ViewGroup)) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: not a subclass of ViewGroup");
+ }
return TYPE_FIXED;
}
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: is a subclass of ViewGroup");
+ }
// Confirm that it can scroll.
if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) {
// Nothing to scroll here, move along.
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: cannot be scrolled");
+ }
return TYPE_FIXED;
}
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: can be scrolled up or down");
+ }
// ScrollViews accept only a single child.
if (((ViewGroup) view).getChildCount() > 1) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: scrollable with multiple children");
+ }
return TYPE_RECYCLING;
}
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: less than two child views");
+ }
//Because recycling containers don't use scrollY, a non-zero value means Scroll view.
if (view.getScrollY() != 0) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: scrollY != 0");
+ }
return TYPE_SCROLLING;
}
+ Log.v(TAG, "hint: scrollY == 0");
// Since scrollY cannot be negative, this means a Recycling view.
if (view.canScrollVertically(UP)) {
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: able to scroll up");
+ }
return TYPE_RECYCLING;
}
- // canScrollVertically(UP) == false, getScrollY() == 0, getChildCount() == 1.
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: cannot be scrolled up");
+ }
+ // canScrollVertically(UP) == false, getScrollY() == 0, getChildCount() == 1.
// For Recycling containers, this should be a no-op (RecyclerView logs a warning)
view.scrollTo(view.getScrollX(), 1);
// A scrolling container would have moved by 1px.
if (view.getScrollY() == 1) {
view.scrollTo(view.getScrollX(), 0);
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: scrollTo caused scrollY to change");
+ }
return TYPE_SCROLLING;
}
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "hint: scrollTo did not cause scrollY to change");
+ }
return TYPE_RECYCLING;
}
@@ -99,19 +142,61 @@ public class ScrollCaptureInternal {
* @param localVisibleRect the visible area of the given view in local coordinates, as supplied
* by the view parent
* @param positionInWindow the offset of localVisibleRect within the window
- *
* @return a new callback or null if the View isn't supported
*/
@Nullable
public ScrollCaptureCallback requestCallback(View view, Rect localVisibleRect,
Point positionInWindow) {
// Nothing to see here yet.
+ if (DEBUG_VERBOSE) {
+ Log.v(TAG, "scroll capture: checking " + view.getClass().getName()
+ + "[" + resolveId(view.getContext(), view.getId()) + "]");
+ }
int i = detectScrollingType(view);
switch (i) {
case TYPE_SCROLLING:
+ if (DEBUG) {
+ Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName()
+ + "[" + resolveId(view.getContext(), view.getId()) + "]"
+ + " -> TYPE_SCROLLING");
+ }
return new ScrollCaptureViewSupport<>((ViewGroup) view,
new ScrollViewCaptureHelper());
+ case TYPE_RECYCLING:
+ if (DEBUG) {
+ Log.d(TAG, "scroll capture: FOUND " + view.getClass().getName()
+ + "[" + resolveId(view.getContext(), view.getId()) + "]"
+ + " -> TYPE_RECYCLING");
+ }
+ return new ScrollCaptureViewSupport<>((ViewGroup) view,
+ new RecyclerViewCaptureHelper());
+ case TYPE_FIXED:
+ // ignore
+ break;
+
}
return null;
}
+
+ // Lifted from ViewDebug (package protected)
+
+ private static String formatIntToHexString(int value) {
+ return "0x" + Integer.toHexString(value).toUpperCase();
+ }
+
+ static String resolveId(Context context, int id) {
+ String fieldValue;
+ final Resources resources = context.getResources();
+ if (id >= 0) {
+ try {
+ fieldValue = resources.getResourceTypeName(id) + '/'
+ + resources.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ fieldValue = "id/" + formatIntToHexString(id);
+ }
+ } else {
+ fieldValue = "NO_ID";
+ }
+ return fieldValue;
+ }
}
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
index a92e978b2fc1..9829d0b7ae8d 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
@@ -17,7 +17,6 @@
package com.android.internal.view;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.graphics.Rect;
import android.view.View;
@@ -62,8 +61,8 @@ interface ScrollCaptureViewHelper<V extends View> {
* @param view the view being captured
* @return true if the callback should respond to a request with scroll bounds
*/
- default boolean onAcceptSession(@Nullable V view) {
- return view != null && view.isVisibleToUser()
+ default boolean onAcceptSession(@NonNull V view) {
+ return view.isVisibleToUser()
&& (view.canScrollVertically(UP) || view.canScrollVertically(DOWN));
}
@@ -73,7 +72,7 @@ interface ScrollCaptureViewHelper<V extends View> {
*
* @param view the view being captured
*/
- default Rect onComputeScrollBounds(@Nullable V view) {
+ @NonNull default Rect onComputeScrollBounds(@NonNull V view) {
return new Rect(view.getPaddingLeft(), view.getPaddingTop(),
view.getWidth() - view.getPaddingRight(),
view.getHeight() - view.getPaddingBottom());
@@ -88,7 +87,7 @@ interface ScrollCaptureViewHelper<V extends View> {
* @param view the view being captured
* @param scrollBounds the bounds within {@code view} where content scrolls
*/
- void onPrepareForStart(@NonNull V view, Rect scrollBounds);
+ void onPrepareForStart(@NonNull V view, @NonNull Rect scrollBounds);
/**
* Map the request onto the screen.
@@ -105,7 +104,9 @@ interface ScrollCaptureViewHelper<V extends View> {
* content to capture for the request
* @return the result of the request as a {@link ScrollResult}
*/
- ScrollResult onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);
+ @NonNull
+ ScrollResult onScrollRequested(@NonNull V view, @NonNull Rect scrollBounds,
+ @NonNull Rect requestRect);
/**
* Restore the target after capture.
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index 7b4f73ffde2b..85fa791b429c 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -23,8 +23,8 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
import android.os.Handler;
-import android.os.SystemClock;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.view.ScrollCaptureCallback;
import android.view.ScrollCaptureSession;
import android.view.Surface;
@@ -46,8 +46,12 @@ import java.util.function.Consumer;
*/
public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback {
+ public static final long NO_FRAME_PRODUCED = -1;
+
private static final String TAG = "ScrollCaptureViewSupport";
+ private static final boolean WAIT_FOR_ANIMATION = true;
+
private final WeakReference<V> mWeakView;
private final ScrollCaptureViewHelper<V> mViewHelper;
private ViewRenderer mRenderer;
@@ -99,12 +103,16 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
V view = mWeakView.get();
if (view == null || !view.isVisibleToUser()) {
// Signal to the controller that we have a problem and can't continue.
- session.notifyBufferSent(0, null);
+ session.notifyBufferSent(NO_FRAME_PRODUCED, new Rect());
return;
}
// Ask the view to scroll as needed to bring this area into view.
ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
requestRect);
+ if (scrollResult.availableArea.isEmpty()) {
+ session.notifyBufferSent(NO_FRAME_PRODUCED, scrollResult.availableArea);
+ return;
+ }
view.invalidate(); // don't wait for vsync
// For image capture, shift back by scrollDelta to arrive at the location within the view
@@ -112,8 +120,19 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
Rect viewCaptureArea = new Rect(scrollResult.availableArea);
viewCaptureArea.offset(0, -scrollResult.scrollDelta);
- mRenderer.renderView(view, viewCaptureArea, mUiHandler,
- (frameNumber) -> session.notifyBufferSent(frameNumber, scrollResult.availableArea));
+ if (WAIT_FOR_ANIMATION) {
+ Log.d(TAG, "render: delaying until animation");
+ view.postOnAnimation(() -> {
+ Log.d(TAG, "postOnAnimation(): rendering now");
+ long resultFrame = mRenderer.renderView(view, viewCaptureArea);
+ Log.d(TAG, "notifyBufferSent: " + scrollResult.availableArea);
+
+ session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea));
+ });
+ } else {
+ long resultFrame = mRenderer.renderView(view, viewCaptureArea);
+ session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea));
+ }
}
@Override
@@ -132,8 +151,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
/**
* Internal helper class which assists in rendering sections of the view hierarchy relative to a
- * given view. Used by framework implementations of ScrollCaptureHandler to render and dispatch
- * image requests.
+ * given view.
*/
static final class ViewRenderer {
// alpha, "reasonable default" from Javadoc
@@ -157,14 +175,11 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
private final Matrix mTempMatrix = new Matrix();
private final int[] mTempLocation = new int[2];
private long mLastRenderedSourceDrawingId = -1;
-
-
- public interface FrameCompleteListener {
- void onFrameComplete(long frameNumber);
- }
+ private Surface mSurface;
ViewRenderer() {
mRenderer = new HardwareRenderer();
+ mRenderer.setName("ScrollCapture");
mCaptureRenderNode = new RenderNode("ScrollCaptureRoot");
mRenderer.setContentRoot(mCaptureRenderNode);
@@ -173,6 +188,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
}
public void setSurface(Surface surface) {
+ mSurface = surface;
mRenderer.setSurface(surface);
}
@@ -223,20 +239,38 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
mCaptureRenderNode.endRecording();
}
- public void renderView(View view, Rect sourceRect, Handler handler,
- FrameCompleteListener frameListener) {
+ public long renderView(View view, Rect sourceRect) {
if (updateForView(view)) {
setupLighting(view);
}
view.invalidate();
updateRootNode(view, sourceRect);
HardwareRenderer.FrameRenderRequest request = mRenderer.createRenderRequest();
- request.setVsyncTime(SystemClock.elapsedRealtimeNanos());
- // private API b/c request.setFrameCommitCallback does not provide access to frameNumber
- mRenderer.setFrameCompleteCallback(
- frameNr -> handler.post(() -> frameListener.onFrameComplete(frameNr)));
+ long timestamp = System.nanoTime();
+ request.setVsyncTime(timestamp);
+
+ // Would be nice to access nextFrameNumber from HwR without having to hold on to Surface
+ final long frameNumber = mSurface.getNextFrameNumber();
+
+ // Block until a frame is presented to the Surface
request.setWaitForPresent(true);
- request.syncAndDraw();
+
+ switch (request.syncAndDraw()) {
+ case HardwareRenderer.SYNC_OK:
+ case HardwareRenderer.SYNC_REDRAW_REQUESTED:
+ return frameNumber;
+
+ case HardwareRenderer.SYNC_FRAME_DROPPED:
+ Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !");
+ break;
+ case HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND:
+ Log.e(TAG, "syncAndDraw(): SYNC_LOST_SURFACE !");
+ break;
+ case HardwareRenderer.SYNC_CONTEXT_IS_STOPPED:
+ Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !");
+ break;
+ }
+ return NO_FRAME_PRODUCED;
}
public void trimMemory() {
@@ -244,6 +278,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
}
public void destroy() {
+ mSurface = null;
mRenderer.destroy();
}
@@ -254,6 +289,5 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa
mTempMatrix.mapRect(mTempRectF);
mTempRectF.round(outRect);
}
-
}
}
diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
index 1514b9a285dd..a1d202e3a39f 100644
--- a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
@@ -57,10 +57,6 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
public ScrollResult onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds,
Rect requestRect) {
- final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
- if (contentView == null) {
- return null;
- }
/*
+---------+ <----+ Content [25,25 - 275,1025] (w=250,h=1000)
| |
@@ -88,9 +84,6 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
\__ Requested Bounds[0,300 - 200,400] (200x100)
*/
- ScrollResult result = new ScrollResult();
- result.requestedArea = new Rect(requestRect);
-
// 0) adjust the requestRect to account for scroll change since start
//
// Scroll Bounds[50,50 - 250,250] (w=200,h=200)
@@ -99,6 +92,17 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
// (y-100) (scrollY - mStartScrollY)
int scrollDelta = view.getScrollY() - mStartScrollY;
+ final ScrollResult result = new ScrollResult();
+ result.requestedArea = new Rect(requestRect);
+ result.scrollDelta = scrollDelta;
+ result.availableArea = new Rect();
+
+ final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
+ if (contentView == null) {
+ // No child view? Cannot continue.
+ return result;
+ }
+
// 1) Translate request rect to make it relative to container view
//
// Container View [0,0 - 300,300] (scrollY=200)
@@ -133,7 +137,7 @@ public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGrou
// TODO: crop capture area to avoid occlusions/minimize scroll changes
Point offset = new Point();
- final Rect available = new Rect(requestedContentBounds); // empty
+ final Rect available = new Rect(requestedContentBounds);
if (!view.getChildVisibleRect(contentView, available, offset)) {
available.setEmpty();
result.availableArea = available;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index ddee81a649af..ff3543c837eb 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -17,9 +17,6 @@
package com.android.internal.widget;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ClipData;
-import android.content.ClipDescription;
-import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.method.KeyListener;
@@ -30,8 +27,6 @@ import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputContentInfo;
-import android.widget.RichContentReceiver;
import android.widget.TextView;
public class EditableInputConnection extends BaseInputConnection {
@@ -186,28 +181,6 @@ public class EditableInputConnection extends BaseInputConnection {
}
@Override
- public boolean commitContent(InputContentInfo content, int flags, Bundle opts) {
- int targetSdkVersion = mTextView.getContext().getApplicationInfo().targetSdkVersion;
- if (targetSdkVersion <= Build.VERSION_CODES.R) {
- return false;
- }
-
- final ClipDescription description = content.getDescription();
- final RichContentReceiver<TextView> receiver = mTextView.getRichContentReceiver();
- if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
- try {
- content.requestPermission();
- } catch (Exception e) {
- // TODO(b/147299828): Can we catch SecurityException instead?
- Log.w(TAG, "Can't insert content from IME; requestPermission() failed: " + e);
- return false; // Can't insert the content if we don't have permission to read it
- }
- }
- ClipData clip = new ClipData(description, new ClipData.Item(content.getContentUri()));
- return receiver.onReceive(mTextView, clip, RichContentReceiver.SOURCE_INPUT_METHOD, 0);
- }
-
- @Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 6b754ca301f3..ed663cfeb613 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -46,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -55,7 +56,6 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -95,7 +95,7 @@ public class SystemConfig {
private static final String VENDOR_SKU_PROPERTY = "ro.boot.product.vendor.sku";
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
- int[] mGlobalGids;
+ int[] mGlobalGids = EmptyArray.INT;
// These are the built-in uid -> permission mappings that were read from the
// system configuration files.
@@ -1520,7 +1520,12 @@ public class SystemConfig {
readPublicLibrariesListFile(new File("/vendor/etc/public.libraries.txt"));
String[] dirs = {"/system/etc", "/system_ext/etc", "/product/etc"};
for (String dir : dirs) {
- for (File f : (new File(dir)).listFiles()) {
+ File[] files = new File(dir).listFiles();
+ if (files == null) {
+ Slog.w(TAG, "Public libraries file folder missing: " + dir);
+ continue;
+ }
+ for (File f : files) {
String name = f.getName();
if (name.startsWith("public.libraries-") && name.endsWith(".txt")) {
readPublicLibrariesListFile(f);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 03ad73ec4093..51a5dc201afc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -186,6 +186,7 @@ cc_library_shared {
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
+ "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp",
"com_android_internal_os_KernelSingleUidTimeReader.cpp",
"com_android_internal_os_Zygote.cpp",
"com_android_internal_os_ZygoteInit.cpp",
@@ -319,4 +320,9 @@ cc_library_shared {
cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
},
},
+
+ // Workaround Clang LTO crash.
+ lto: {
+ never: true,
+ },
}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index bb2c4ce5e6a0..d46378c0fcd6 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -190,6 +190,7 @@ extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
+extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env);
extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
@@ -1586,6 +1587,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
REG_JNI(register_com_android_internal_app_ActivityTrigger),
+ REG_JNI(register_com_android_internal_os_KernelSingleProcessCpuThreadReader),
};
/*
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index be68c4aee278..243540693785 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -84,23 +84,31 @@ static int getFdCount() {
}
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
+ status_t status;
String8 name;
+ CursorWindow* window;
+
const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
name.setTo(nameStr);
env->ReleaseStringUTFChars(nameObj, nameStr);
- CursorWindow* window;
- status_t status = CursorWindow::create(name, cursorWindowSize, &window);
+ if (cursorWindowSize < 0) {
+ status = INVALID_OPERATION;
+ goto fail;
+ }
+ status = CursorWindow::create(name, cursorWindowSize, &window);
if (status || !window) {
- jniThrowExceptionFmt(env,
- "android/database/CursorWindowAllocationException",
- "Could not allocate CursorWindow '%s' of size %d due to error %d.",
- name.string(), cursorWindowSize, status);
- return 0;
+ goto fail;
}
LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
return reinterpret_cast<jlong>(window);
+
+fail:
+ jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException",
+ "Could not allocate CursorWindow '%s' of size %d due to error %d.",
+ name.string(), cursorWindowSize, status);
+ return 0;
}
static jlong nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 23f4325c0ff1..a30c37bbd11c 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -29,10 +29,21 @@
namespace android {
-static jlong nativeCreate(JNIEnv* env, jclass clazz, jlong surfaceControl, jlong width, jlong height,
- jboolean enableTripleBuffering) {
- sp<BLASTBufferQueue> queue = new BLASTBufferQueue(
- reinterpret_cast<SurfaceControl*>(surfaceControl), width, height, enableTripleBuffering);
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
+ jlong width, jlong height, jboolean enableTripleBuffering) {
+ String8 str8;
+ if (jName) {
+ const jchar* str16 = env->GetStringCritical(jName, nullptr);
+ if (str16) {
+ str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));
+ env->ReleaseStringCritical(jName, str16);
+ str16 = nullptr;
+ }
+ }
+ std::string name = str8.string();
+ sp<BLASTBufferQueue> queue =
+ new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
+ height, enableTripleBuffering);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -59,18 +70,12 @@ static void nativeUpdate(JNIEnv*env, jclass clazz, jlong ptr, jlong surfaceContr
}
static const JNINativeMethod gMethods[] = {
- /* name, signature, funcPtr */
- { "nativeCreate", "(JJJZ)J",
- (void*)nativeCreate },
- { "nativeGetSurface", "(J)Landroid/view/Surface;",
- (void*)nativeGetSurface },
- { "nativeDestroy", "(J)V",
- (void*)nativeDestroy },
- { "nativeSetNextTransaction", "(JJ)V",
- (void*)nativeSetNextTransaction },
- { "nativeUpdate", "(JJJJ)V",
- (void*)nativeUpdate }
-};
+ /* name, signature, funcPtr */
+ {"nativeCreate", "(Ljava/lang/String;JJJZ)J", (void*)nativeCreate},
+ {"nativeGetSurface", "(J)Landroid/view/Surface;", (void*)nativeGetSurface},
+ {"nativeDestroy", "(J)V", (void*)nativeDestroy},
+ {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction},
+ {"nativeUpdate", "(JJJJ)V", (void*)nativeUpdate}};
int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue",
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 859b40afb7c4..919e3513e061 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -249,6 +249,16 @@ static jint CameraMetadata_getEntryCount(JNIEnv *env, jclass thiz, jlong ptr) {
return metadata->entryCount();
}
+static jlong CameraMetadata_getBufferSize(JNIEnv *env, jclass thiz, jlong ptr) {
+ ALOGV("%s", __FUNCTION__);
+
+ CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr);
+
+ if (metadata == NULL) return 0;
+
+ return metadata->bufferSize();
+}
+
// idempotent. calling more than once has no effect.
static void CameraMetadata_close(JNIEnv *env, jclass thiz, jlong ptr) {
ALOGV("%s", __FUNCTION__);
@@ -561,6 +571,9 @@ static const JNINativeMethod gCameraMetadataMethods[] = {
{ "nativeGetEntryCount",
"(J)I",
(void*)CameraMetadata_getEntryCount },
+ { "nativeGetBufferSize",
+ "(J)J",
+ (void*)CameraMetadata_getBufferSize },
{ "nativeClose",
"(J)V",
(void*)CameraMetadata_close },
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index a0638207a841..463d909821b1 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -60,8 +60,10 @@ static struct {
jfieldID hasWallpaper;
jfieldID paused;
jfieldID trustedOverlay;
+ jfieldID touchOcclusionMode;
jfieldID ownerPid;
jfieldID ownerUid;
+ jfieldID packageName;
jfieldID inputFeatures;
jfieldID displayId;
jfieldID portalToDisplayId;
@@ -150,10 +152,13 @@ bool NativeInputWindowHandle::updateInfo() {
mInfo.paused = env->GetBooleanField(obj,
gInputWindowHandleClassInfo.paused);
mInfo.trustedOverlay = env->GetBooleanField(obj, gInputWindowHandleClassInfo.trustedOverlay);
+ mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
+ env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
mInfo.ownerPid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerPid);
mInfo.ownerUid = env->GetIntField(obj,
gInputWindowHandleClassInfo.ownerUid);
+ mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
mInfo.inputFeatures = static_cast<InputWindowInfo::Feature>(
env->GetIntField(obj, gInputWindowHandleClassInfo.inputFeatures));
mInfo.displayId = env->GetIntField(obj,
@@ -326,12 +331,17 @@ int register_android_view_InputWindowHandle(JNIEnv* env) {
GET_FIELD_ID(gInputWindowHandleClassInfo.trustedOverlay, clazz, "trustedOverlay", "Z");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.touchOcclusionMode, clazz, "touchOcclusionMode", "I");
+
GET_FIELD_ID(gInputWindowHandleClassInfo.ownerPid, clazz,
"ownerPid", "I");
GET_FIELD_ID(gInputWindowHandleClassInfo.ownerUid, clazz,
"ownerUid", "I");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.packageName, clazz, "packageName",
+ "Ljava/lang/String;");
+
GET_FIELD_ID(gInputWindowHandleClassInfo.inputFeatures, clazz,
"inputFeatures", "I");
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 5c4c5099bf4c..1ca45fe9f70b 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -338,7 +338,7 @@ static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes,
return (jint)AUDIO_JAVA_BAD_VALUE;
}
const char *address = env->GetStringUTFChars((jstring)addrJobj, NULL);
- AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address);
+ AudioDeviceTypeAddr dev = AudioDeviceTypeAddr((audio_devices_t)typesPtr[i], address);
audioDeviceTypeAddrVector.push_back(dev);
env->ReleaseStringUTFChars((jstring)addrJobj, address);
}
@@ -820,7 +820,8 @@ static void convertAudioGainConfigToNative(JNIEnv *env,
bool useInMask)
{
nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
- nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+ nAudioGainConfig->mode =
+ (audio_gain_mode_t)env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
audio_channel_mask_t nMask;
@@ -940,8 +941,8 @@ static jint convertAudioPortConfigToNativeWithDevicePort(JNIEnv *env,
jobject jAudioDevicePort = env->GetObjectField(jAudioPortConfig,
gAudioPortConfigFields.mPort);
- nAudioPortConfig->ext.device.type = env->GetIntField(jAudioDevicePort,
- gAudioPortFields.mType);
+ nAudioPortConfig->ext.device.type =
+ (audio_devices_t)env->GetIntField(jAudioDevicePort, gAudioPortFields.mType);
jstring jDeviceAddress = (jstring)env->GetObjectField(jAudioDevicePort,
gAudioPortFields.mAddress);
const char *nDeviceAddress = env->GetStringUTFChars(jDeviceAddress, NULL);
@@ -2334,7 +2335,7 @@ static jint android_media_AudioSystem_setSupportedSystemUsages(JNIEnv *env, jobj
static jint
android_media_AudioSystem_setAllowedCapturePolicy(JNIEnv *env, jobject thiz, jint uid, jint flags) {
- return AudioSystem::setAllowedCapturePolicy(uid, flags);
+ return AudioSystem::setAllowedCapturePolicy(uid, static_cast<audio_flags_mask_t>(flags));
}
static jint
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index ef0eeec2c7af..5a859aabce7b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -792,7 +792,7 @@ static jlong android_os_Debug_getFreeZramKb(JNIEnv* env, jobject clazz) {
}
static jlong android_os_Debug_getIonHeapsSizeKb(JNIEnv* env, jobject clazz) {
- jlong heapsSizeKb = 0;
+ jlong heapsSizeKb = -1;
uint64_t size;
if (meminfo::ReadIonHeapsSizeKb(&size)) {
@@ -803,7 +803,7 @@ static jlong android_os_Debug_getIonHeapsSizeKb(JNIEnv* env, jobject clazz) {
}
static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) {
- jlong poolsSizeKb = 0;
+ jlong poolsSizeKb = -1;
uint64_t size;
if (meminfo::ReadIonPoolsSizeKb(&size)) {
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 4f3f283859b4..b40491a49b14 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -16,6 +16,8 @@
#define LOG_TAG "GraphicsEnvironment"
+#include <vector>
+
#include <graphicsenv/GraphicsEnv.h>
#include <nativehelper/ScopedUtfChars.h>
#include <nativeloader/native_loader.h>
@@ -47,16 +49,36 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName,
appPackageNameChars.c_str(), vulkanVersion);
}
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName, jstring devOptIn,
- jobject rulesFd, jlong rulesOffset, jlong rulesLength) {
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
+ jstring devOptIn, jobjectArray featuresObj, jobject rulesFd,
+ jlong rulesOffset, jlong rulesLength) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars appNameChars(env, appName);
ScopedUtfChars devOptInChars(env, devOptIn);
+ std::vector<std::string> features;
+ if (featuresObj != nullptr) {
+ jsize length = env->GetArrayLength(featuresObj);
+ for (jsize i = 0; i < length; ++i) {
+ jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(featuresObj, i));
+ // null entries are ignored
+ if (jstr == nullptr) {
+ continue;
+ }
+ const char* cstr = env->GetStringUTFChars(jstr, nullptr);
+ if (cstr == nullptr) {
+ continue;
+ }
+ features.emplace_back(cstr);
+ env->ReleaseStringUTFChars(jstr, cstr);
+ }
+ }
+
int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd);
android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
- devOptInChars.c_str(), rulesFd_native, rulesOffset, rulesLength);
+ devOptInChars.c_str(), features,
+ rulesFd_native, rulesOffset, rulesLength);
}
bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -94,16 +116,25 @@ void hintActivityLaunch_native(JNIEnv* env, jobject clazz) {
}
const JNINativeMethod g_methods[] = {
- { "isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native) },
- { "setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native) },
- { "setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V", reinterpret_cast<void*>(setGpuStats_native) },
- { "setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native) },
- { "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) },
- { "getShouldUseAngle", "(Ljava/lang/String;)Z", reinterpret_cast<void*>(shouldUseAngle_native) },
- { "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) },
- { "setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native) },
- { "setDebugLayersGLES", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayersGLES_native) },
- { "hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native) },
+ {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)},
+ {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V",
+ reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native)},
+ {"setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V",
+ reinterpret_cast<void*>(setGpuStats_native)},
+ {"setInjectLayersPrSetDumpable", "()Z",
+ reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
+ {"setAngleInfo",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/"
+ "FileDescriptor;JJ)V",
+ reinterpret_cast<void*>(setAngleInfo_native)},
+ {"getShouldUseAngle", "(Ljava/lang/String;)Z",
+ reinterpret_cast<void*>(shouldUseAngle_native)},
+ {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
+ reinterpret_cast<void*>(setLayerPaths_native)},
+ {"setDebugLayers", "(Ljava/lang/String;)V", reinterpret_cast<void*>(setDebugLayers_native)},
+ {"setDebugLayersGLES", "(Ljava/lang/String;)V",
+ reinterpret_cast<void*>(setDebugLayersGLES_native)},
+ {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)},
};
const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment";
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index ff336ee64b54..4c4443fc29c3 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -990,6 +990,8 @@ static jobject JHwParcel_native_readStrongBinder(JNIEnv *env, jobject thiz) {
}
if (!validateCanUseHwBinder(binder)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Local binder is not supported in Java");
return nullptr;
}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 0892b70b9651..355ef0cd6b3f 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -612,50 +612,77 @@ static jboolean android_os_Parcel_hasFileDescriptors(jlong nativePtr)
return ret;
}
+// String tries to allocate itself on the stack, within a known size, but will
+// make a heap allocation if not.
+template <size_t StackReserve>
+class StackString {
+public:
+ StackString(JNIEnv* env, jstring str) : mEnv(env), mJStr(str) {
+ LOG_ALWAYS_FATAL_IF(str == nullptr);
+ mSize = env->GetStringLength(str);
+ if (mSize > StackReserve) {
+ mStr = new jchar[mSize];
+ } else {
+ mStr = &mBuffer[0];
+ }
+ mEnv->GetStringRegion(str, 0, mSize, mStr);
+ }
+ ~StackString() {
+ if (mStr != &mBuffer[0]) {
+ delete[] mStr;
+ }
+ }
+ const jchar* str() { return mStr; }
+ jsize size() { return mSize; }
+
+private:
+ JNIEnv* mEnv;
+ jstring mJStr;
+
+ jchar mBuffer[StackReserve];
+ // pointer to &mBuffer[0] if string fits in mBuffer, otherwise owned
+ jchar* mStr;
+ jsize mSize;
+};
+
+// This size is chosen to be longer than most interface descriptors.
+// Ones longer than this will be allocated on the heap.
+typedef StackString<64> InterfaceDescriptorString;
+
static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jclass clazz, jlong nativePtr,
jstring name)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
- if (parcel != NULL) {
- // In the current implementation, the token is just the serialized interface name that
- // the caller expects to be invoking
- const jchar* str = env->GetStringCritical(name, 0);
- if (str != NULL) {
- parcel->writeInterfaceToken(String16(
- reinterpret_cast<const char16_t*>(str),
- env->GetStringLength(name)));
- env->ReleaseStringCritical(name, str);
- }
+ if (parcel != nullptr) {
+ InterfaceDescriptorString descriptor(env, name);
+ parcel->writeInterfaceToken(reinterpret_cast<const char16_t*>(descriptor.str()),
+ descriptor.size());
}
}
static void android_os_Parcel_enforceInterface(JNIEnv* env, jclass clazz, jlong nativePtr, jstring name)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
- if (parcel != NULL) {
- const jchar* str = env->GetStringCritical(name, 0);
- if (str) {
- IPCThreadState* threadState = IPCThreadState::self();
- const int32_t oldPolicy = threadState->getStrictModePolicy();
- const bool isValid = parcel->enforceInterface(
- reinterpret_cast<const char16_t*>(str),
- env->GetStringLength(name),
- threadState);
- env->ReleaseStringCritical(name, str);
- if (isValid) {
- const int32_t newPolicy = threadState->getStrictModePolicy();
- if (oldPolicy != newPolicy) {
- // Need to keep the Java-level thread-local strict
- // mode policy in sync for the libcore
- // enforcements, which involves an upcall back
- // into Java. (We can't modify the
- // Parcel.enforceInterface signature, as it's
- // pseudo-public, and used via AIDL
- // auto-generation...)
- set_dalvik_blockguard_policy(env, newPolicy);
- }
- return; // everything was correct -> return silently
+ if (parcel != nullptr) {
+ InterfaceDescriptorString descriptor(env, name);
+ IPCThreadState* threadState = IPCThreadState::self();
+ const int32_t oldPolicy = threadState->getStrictModePolicy();
+ const bool isValid =
+ parcel->enforceInterface(reinterpret_cast<const char16_t*>(descriptor.str()),
+ descriptor.size(), threadState);
+ if (isValid) {
+ const int32_t newPolicy = threadState->getStrictModePolicy();
+ if (oldPolicy != newPolicy) {
+ // Need to keep the Java-level thread-local strict
+ // mode policy in sync for the libcore
+ // enforcements, which involves an upcall back
+ // into Java. (We can't modify the
+ // Parcel.enforceInterface signature, as it's
+ // pseudo-public, and used via AIDL
+ // auto-generation...)
+ set_dalvik_blockguard_policy(env, newPolicy);
}
+ return; // everything was correct -> return silently
}
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 3acd15a8f340..e7e9c31ef90e 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -60,7 +60,7 @@ private:
sp<MessageQueue> mMessageQueue;
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
- int64_t sharedTimelineFrameCount) override;
+ VsyncEventData vsyncEventData) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
int32_t configId, nsecs_t vsyncPeriod) override;
@@ -91,14 +91,15 @@ void NativeDisplayEventReceiver::dispose() {
}
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
- uint32_t count, int64_t frameTimelineVsyncId) {
+ uint32_t count, VsyncEventData vsyncEventData) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
if (receiverObj.get()) {
ALOGV("receiver %p ~ Invoking vsync handler.", this);
env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
- timestamp, displayId.value, count, frameTimelineVsyncId);
+ timestamp, displayId.value, count, vsyncEventData.id,
+ vsyncEventData.deadlineTimestamp);
ALOGV("receiver %p ~ Returned from vsync handler.", this);
}
@@ -198,7 +199,8 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gDisplayEventReceiverClassInfo.dispatchVsync =
- GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJIJ)V");
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
+ "(JJIJJ)V");
gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchConfigChanged = GetMethodIDOrDie(env,
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 2436b23a45d0..d4a746290baa 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -36,6 +36,9 @@ namespace android {
static struct {
jclass clazz;
+ jmethodID mCtor;
+ jmethodID mSetNativeInputChannel;
+
jfieldID mPtr; // native object attached to the DVM InputChannel
} gInputChannelClassInfo;
@@ -43,7 +46,7 @@ static struct {
class NativeInputChannel {
public:
- explicit NativeInputChannel(const std::shared_ptr<InputChannel>& inputChannel);
+ explicit NativeInputChannel(std::unique_ptr<InputChannel> inputChannel);
~NativeInputChannel();
inline std::shared_ptr<InputChannel> getInputChannel() { return mInputChannel; }
@@ -59,8 +62,8 @@ private:
// ----------------------------------------------------------------------------
-NativeInputChannel::NativeInputChannel(const std::shared_ptr<InputChannel>& inputChannel)
- : mInputChannel(inputChannel), mDisposeCallback(nullptr) {}
+NativeInputChannel::NativeInputChannel(std::unique_ptr<InputChannel> inputChannel)
+ : mInputChannel(std::move(inputChannel)), mDisposeCallback(nullptr) {}
NativeInputChannel::~NativeInputChannel() {
}
@@ -110,13 +113,33 @@ void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChan
}
static jlong android_view_InputChannel_createInputChannel(
- JNIEnv* env, std::shared_ptr<InputChannel> inputChannel) {
+ JNIEnv* env, std::unique_ptr<InputChannel> inputChannel) {
std::unique_ptr<NativeInputChannel> nativeInputChannel =
- std::make_unique<NativeInputChannel>(inputChannel);
+ std::make_unique<NativeInputChannel>(std::move(inputChannel));
return reinterpret_cast<jlong>(nativeInputChannel.release());
}
+jobject android_view_InputChannel_createJavaObject(JNIEnv* env,
+ std::unique_ptr<InputChannel> inputChannel) {
+ std::string name = inputChannel->getName();
+ jlong ptr = android_view_InputChannel_createInputChannel(env, std::move(inputChannel));
+ jobject javaInputChannel =
+ env->NewObject(gInputChannelClassInfo.clazz, gInputChannelClassInfo.mCtor);
+ if (!javaInputChannel) {
+ ALOGE("Failed to create a Java InputChannel for channel %s.", name.c_str());
+ return nullptr;
+ }
+
+ env->CallVoidMethod(javaInputChannel, gInputChannelClassInfo.mSetNativeInputChannel, ptr);
+ if (env->ExceptionOccurred()) {
+ ALOGE("Failed to set native ptr to the Java InputChannel for channel %s.",
+ inputChannel->getName().c_str());
+ return nullptr;
+ }
+ return javaInputChannel;
+}
+
static jlongArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclass clazz, jstring nameObj) {
ScopedUtfChars nameChars(env, nameObj);
@@ -180,9 +203,10 @@ static jlong android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
- std::shared_ptr<InputChannel> inputChannel = std::make_shared<InputChannel>();
+ std::unique_ptr<InputChannel> inputChannel = std::make_unique<InputChannel>();
inputChannel->readFromParcel(parcel);
- NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
+ NativeInputChannel* nativeInputChannel =
+ new NativeInputChannel(std::move(inputChannel));
return reinterpret_cast<jlong>(nativeInputChannel);
}
}
@@ -233,13 +257,13 @@ static jlong android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jlong
return 0;
}
- std::shared_ptr<InputChannel> dupInputChannel = inputChannel->dup();
+ std::unique_ptr<InputChannel> dupInputChannel = inputChannel->dup();
if (dupInputChannel == nullptr) {
std::string message = android::base::StringPrintf(
"Could not duplicate input channel %s", inputChannel->getName().c_str());
jniThrowRuntimeException(env, message.c_str());
}
- return reinterpret_cast<jlong>(new NativeInputChannel(dupInputChannel));
+ return reinterpret_cast<jlong>(new NativeInputChannel(std::move(dupInputChannel)));
}
static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj, jlong channel) {
@@ -281,6 +305,11 @@ int register_android_view_InputChannel(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/view/InputChannel");
gInputChannelClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+ gInputChannelClassInfo.mCtor =
+ GetMethodIDOrDie(env, gInputChannelClassInfo.clazz, "<init>", "()V");
+ gInputChannelClassInfo.mSetNativeInputChannel =
+ GetMethodIDOrDie(env, gInputChannelClassInfo.clazz, "setNativeInputChannel", "(J)V");
+
gInputChannelClassInfo.mPtr = GetFieldIDOrDie(env, gInputChannelClassInfo.clazz, "mPtr", "J");
return res;
diff --git a/core/jni/android_view_InputChannel.h b/core/jni/android_view_InputChannel.h
index 8030c96ab19f..50b9aed4b12c 100644
--- a/core/jni/android_view_InputChannel.h
+++ b/core/jni/android_view_InputChannel.h
@@ -36,6 +36,8 @@ extern std::shared_ptr<InputChannel> android_view_InputChannel_getInputChannel(
extern void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
InputChannelObjDisposeCallback callback, void* data = NULL);
+extern jobject android_view_InputChannel_createJavaObject(
+ JNIEnv* env, std::unique_ptr<InputChannel> inputChannel);
} // namespace android
#endif // _ANDROID_OS_INPUTCHANNEL_H
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index d1369db52ad9..b84407525aee 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -32,9 +32,10 @@
#include "android_os_Parcel.h"
#include <binder/Parcel.h>
+#include <gui/BLASTBufferQueue.h>
#include <gui/Surface.h>
-#include <gui/view/Surface.h>
#include <gui/SurfaceControl.h>
+#include <gui/view/Surface.h>
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
@@ -300,6 +301,26 @@ static jlong nativeGetFromSurfaceControl(JNIEnv* env, jclass clazz,
return reinterpret_cast<jlong>(surface.get());
}
+static jlong nativeGetFromBlastBufferQueue(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jlong blastBufferQueueNativeObj) {
+ Surface* self(reinterpret_cast<Surface*>(nativeObject));
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(blastBufferQueueNativeObj);
+ const sp<IGraphicBufferProducer>& bufferProducer = queue->getIGraphicBufferProducer();
+ // If the underlying IGBP's are the same, we don't need to do anything.
+ if (self != nullptr &&
+ IInterface::asBinder(self->getIGraphicBufferProducer()) ==
+ IInterface::asBinder(bufferProducer)) {
+ return nativeObject;
+ }
+
+ sp<Surface> surface(new Surface(bufferProducer, true));
+ if (surface != NULL) {
+ surface->incStrong(&sRefBaseOwner);
+ }
+
+ return reinterpret_cast<jlong>(surface.get());
+}
+
static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
@@ -474,6 +495,7 @@ static const JNINativeMethod gSurfaceMethods[] = {
{"nativeSetSharedBufferModeEnabled", "(JZ)I", (void*)nativeSetSharedBufferModeEnabled},
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFI)I", (void*)nativeSetFrameRate},
+ {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
};
int register_android_view_Surface(JNIEnv* env)
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9efe4b15ce7a..a61903dcb7c8 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -481,7 +481,7 @@ static void nativeSetRelativeLayer(JNIEnv* env, jclass clazz, jlong transactionO
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
auto relative = reinterpret_cast<SurfaceControl *>(relativeToObject);
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setRelativeLayer(ctrl, relative->getHandle(), zorder);
+ transaction->setRelativeLayer(ctrl, relative, zorder);
}
static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -1327,20 +1327,9 @@ static jboolean nativeGetAnimationFrameStats(JNIEnv* env, jclass clazz, jobject
static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jlong barrierObject, jlong frameNumber) {
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- auto barrier = reinterpret_cast<SurfaceControl *>(barrierObject);
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->deferTransactionUntil_legacy(ctrl, barrier->getHandle(), frameNumber);
-}
-
-static void nativeDeferTransactionUntilSurface(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong nativeObject,
- jlong surfaceObject, jlong frameNumber) {
+ sp<SurfaceControl> ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ sp<SurfaceControl> barrier = reinterpret_cast<SurfaceControl*>(barrierObject);
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- sp<Surface> barrier = reinterpret_cast<Surface *>(surfaceObject);
-
transaction->deferTransactionUntil_legacy(ctrl, barrier, frameNumber);
}
@@ -1351,7 +1340,7 @@ static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionO
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->reparentChildren(ctrl, newParent->getHandle());
+ transaction->reparentChildren(ctrl, newParent);
}
static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -1360,7 +1349,7 @@ static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->reparent(ctrl, newParent != NULL ? newParent->getHandle() : NULL);
+ transaction->reparent(ctrl, newParent);
}
static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -1371,15 +1360,6 @@ static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong transactionObj,
transaction->detachChildren(ctrl);
}
-static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong nativeObject,
- jint scalingMode) {
- auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
- auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- transaction->setOverrideScalingMode(ctrl, scalingMode);
-}
-
static jobject nativeGetHdrCapabilities(JNIEnv* env, jclass clazz, jobject tokenObject) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
if (token == NULL) return NULL;
@@ -1434,7 +1414,8 @@ static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj)
doThrowNPE(env);
return 0;
}
- sp<SurfaceControl> surface = SurfaceControl::readFromParcel(parcel);
+ sp<SurfaceControl> surface;
+ SurfaceControl::readFromParcel(*parcel, &surface);
if (surface == nullptr) {
return 0;
}
@@ -1462,7 +1443,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz,
}
SurfaceControl* const self = reinterpret_cast<SurfaceControl *>(nativeObject);
if (self != nullptr) {
- self->writeToParcel(parcel);
+ self->writeToParcel(*parcel);
}
}
@@ -1557,6 +1538,13 @@ static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionO
displayId);
}
+static void nativeSetFrameTimelineVsync(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong frameTimelineVsyncId) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ transaction->setFrameTimelineVsync(frameTimelineVsyncId);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -1691,16 +1679,12 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeGetProtectedContentSupport },
{"nativeDeferTransactionUntil", "(JJJJ)V",
(void*)nativeDeferTransactionUntil },
- {"nativeDeferTransactionUntilSurface", "(JJJJ)V",
- (void*)nativeDeferTransactionUntilSurface },
{"nativeReparentChildren", "(JJJ)V",
(void*)nativeReparentChildren } ,
{"nativeReparent", "(JJJ)V",
(void*)nativeReparent },
{"nativeSeverChildren", "(JJ)V",
(void*)nativeSeverChildren } ,
- {"nativeSetOverrideScalingMode", "(JJI)V",
- (void*)nativeSetOverrideScalingMode },
{"nativeCaptureDisplay",
"(Landroid/view/SurfaceControl$DisplayCaptureArgs;Landroid/view/SurfaceControl$ScreenCaptureListener;)I",
(void*)nativeCaptureDisplay },
@@ -1741,6 +1725,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetFixedTransformHint},
{"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Landroid/os/IBinder;I)V",
(void*)nativeSetFocusedWindow},
+ {"nativeSetFrameTimelineVsync", "(JJ)V",
+ (void*)nativeSetFrameTimelineVsync },
// clang-format on
};
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
new file mode 100644
index 000000000000..52bed6bcfce3
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+#include <dirent.h>
+
+#include <android-base/file.h>
+#include <android-base/parseint.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android_runtime/Log.h>
+
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+
+namespace android {
+
+// Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
+static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
+
+// Given a PID, returns a vector of all TIDs for the process' tasks. Thread IDs are
+// file names in the /proc/<pid>/task directory.
+static bool getThreadIds(const std::string &procPath, const pid_t pid,
+ std::vector<pid_t> &outThreadIds) {
+ std::string taskPath = android::base::StringPrintf("%s/%u/task", procPath.c_str(), pid);
+
+ struct dirent **dirlist;
+ int threadCount = scandir(taskPath.c_str(), &dirlist, NULL, NULL);
+ if (threadCount == -1) {
+ ALOGE("Cannot read directory %s", taskPath.c_str());
+ return false;
+ }
+
+ outThreadIds.reserve(threadCount);
+
+ for (int i = 0; i < threadCount; i++) {
+ pid_t tid;
+ if (android::base::ParseInt<pid_t>(dirlist[i]->d_name, &tid)) {
+ outThreadIds.push_back(tid);
+ }
+ free(dirlist[i]);
+ }
+ free(dirlist);
+
+ return true;
+}
+
+// Reads contents of a time_in_state file and returns times as a vector of times per frequency
+// A time_in_state file contains pairs of frequency - time (in jiffies):
+//
+// cpu0
+// 300000 30
+// 403200 0
+// cpu4
+// 710400 10
+// 825600 20
+// 940800 30
+//
+static bool getThreadTimeInState(const std::string &procPath, const pid_t pid, const pid_t tid,
+ const size_t frequencyCount,
+ std::vector<uint64_t> &outThreadTimeInState) {
+ std::string timeInStateFilePath =
+ android::base::StringPrintf("%s/%u/task/%u/time_in_state", procPath.c_str(), pid, tid);
+ std::string data;
+
+ if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
+ ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
+ return false;
+ }
+
+ auto lines = android::base::Split(data, "\n");
+ size_t index = 0;
+ for (const auto &line : lines) {
+ if (line.empty()) {
+ continue;
+ }
+
+ auto numbers = android::base::Split(line, " ");
+ if (numbers.size() != 2) {
+ continue;
+ }
+ uint64_t timeInState;
+ if (!android::base::ParseUint<uint64_t>(numbers[1], &timeInState)) {
+ ALOGE("Invalid time_in_state file format: %s", timeInStateFilePath.c_str());
+ return false;
+ }
+ if (index < frequencyCount) {
+ outThreadTimeInState[index] = timeInState;
+ }
+ index++;
+ }
+
+ if (index != frequencyCount) {
+ ALOGE("Incorrect number of frequencies %u in %s. Expected %u",
+ (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(),
+ (uint32_t)frequencyCount);
+ return false;
+ }
+
+ return true;
+}
+
+static int pidCompare(const void *a, const void *b) {
+ return (*(pid_t *)a - *(pid_t *)b);
+}
+
+static inline bool isSelectedThread(const pid_t tid, const pid_t *selectedThreadIds,
+ const size_t selectedThreadCount) {
+ return bsearch(&tid, selectedThreadIds, selectedThreadCount, sizeof(pid_t), pidCompare) != NULL;
+}
+
+// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency
+// time in state data for all threads. Also, separately aggregates time in state for
+// selected threads whose TIDs are passes as selectedThreadIds.
+static void aggregateThreadCpuTimes(const std::string &procPath, const pid_t pid,
+ const std::vector<pid_t> &threadIds,
+ const size_t frequencyCount, const pid_t *selectedThreadIds,
+ const size_t selectedThreadCount,
+ uint64_t *threadCpuTimesMillis,
+ uint64_t *selectedThreadCpuTimesMillis) {
+ for (size_t j = 0; j < frequencyCount; j++) {
+ threadCpuTimesMillis[j] = 0;
+ selectedThreadCpuTimesMillis[j] = 0;
+ }
+
+ for (size_t i = 0; i < threadIds.size(); i++) {
+ pid_t tid = threadIds[i];
+ std::vector<uint64_t> timeInState(frequencyCount);
+ if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
+ continue;
+ }
+
+ bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
+ for (size_t j = 0; j < frequencyCount; j++) {
+ threadCpuTimesMillis[j] += timeInState[j];
+ if (selectedThread) {
+ selectedThreadCpuTimesMillis[j] += timeInState[j];
+ }
+ }
+ }
+ for (size_t i = 0; i < frequencyCount; i++) {
+ threadCpuTimesMillis[i] *= gJiffyMillis;
+ selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
+ }
+}
+
+// Reads process utime and stime from the /proc/<pid>/stat file.
+// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
+static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
+ uint64_t &outTimeMillis) {
+ std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
+ std::string data;
+ if (!android::base::ReadFileToString(statFilePath, &data)) {
+ return false;
+ }
+
+ auto fields = android::base::Split(data, " ");
+ uint64_t utime, stime;
+
+ // Field 14 (counting from 1) is utime - process time in user space, in jiffies
+ // Field 15 (counting from 1) is stime - process time in system space, in jiffies
+ if (fields.size() < 15 || !android::base::ParseUint(fields[13], &utime) ||
+ !android::base::ParseUint(fields[14], &stime)) {
+ ALOGE("Invalid file format %s", statFilePath.c_str());
+ return false;
+ }
+
+ outTimeMillis = (utime + stime) * gJiffyMillis;
+ return true;
+}
+
+// Estimates per cluster per frequency CPU time for the entire process
+// by distributing the total process CPU time proportionately to how much
+// CPU time its threads took on those clusters/frequencies. This algorithm
+// works more accurately when when we have equally distributed concurrency.
+// TODO(b/169279846): obtain actual process CPU times from the kernel
+static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
+ const uint64_t *threadCpuTimesMillis,
+ const size_t frequencyCount,
+ uint64_t *processCpuTimesMillis) {
+ uint64_t totalCpuTimeAllThreads = 0;
+ for (size_t i = 0; i < frequencyCount; i++) {
+ totalCpuTimeAllThreads += threadCpuTimesMillis[i];
+ }
+
+ if (totalCpuTimeAllThreads != 0) {
+ for (size_t i = 0; i < frequencyCount; i++) {
+ processCpuTimesMillis[i] =
+ processCpuTimeMillis * threadCpuTimesMillis[i] / totalCpuTimeAllThreads;
+ }
+ } else {
+ for (size_t i = 0; i < frequencyCount; i++) {
+ processCpuTimesMillis[i] = 0;
+ }
+ }
+}
+
+static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
+ jintArray selectedThreadIdArray,
+ jlongArray processCpuTimesMillisArray,
+ jlongArray threadCpuTimesMillisArray,
+ jlongArray selectedThreadCpuTimesMillisArray) {
+ ScopedUtfChars procPathChars(env, procPath);
+ ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
+ ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
+ ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
+ ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
+
+ std::string procPathStr(procPathChars.c_str());
+
+ // Get all thread IDs for the process.
+ std::vector<pid_t> threadIds;
+ if (!getThreadIds(procPathStr, pid, threadIds)) {
+ ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
+ return false;
+ }
+
+ size_t frequencyCount = processCpuTimesMillis.size();
+
+ if (threadCpuTimesMillis.size() != frequencyCount) {
+ ALOGE("Invalid array length: threadCpuTimesMillis");
+ return false;
+ }
+ if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
+ ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
+ return false;
+ }
+
+ aggregateThreadCpuTimes(procPathStr, pid, threadIds, frequencyCount, selectedThreadIds.get(),
+ selectedThreadIds.size(),
+ reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+ reinterpret_cast<uint64_t *>(selectedThreadCpuTimesMillis.get()));
+
+ uint64_t processCpuTime;
+ bool ret = getProcessCpuTime(procPathStr, pid, processCpuTime);
+ if (ret) {
+ estimateProcessTimeInState(processCpuTime,
+ reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
+ frequencyCount,
+ reinterpret_cast<uint64_t *>(processCpuTimesMillis.get()));
+ }
+ return ret;
+}
+
+static const JNINativeMethod g_single_methods[] = {
+ {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
+};
+
+int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleProcessCpuThreadReader",
+ g_single_methods, NELEM(g_single_methods));
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c785d99f47ba..a3641e3114b2 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -329,31 +329,15 @@ static std::array<UsapTableEntry, USAP_POOL_SIZE_MAX_LIMIT> gUsapTable;
static FileDescriptorTable* gOpenFdTable = nullptr;
// Must match values in com.android.internal.os.Zygote.
-// The order of entries here must be kept in sync with ExternalStorageViews array values.
+// Note that there are gaps in the constants:
+// This is to further keep the values consistent with IVold.aidl
enum MountExternalKind {
- MOUNT_EXTERNAL_NONE = 0,
- MOUNT_EXTERNAL_DEFAULT = 1,
- MOUNT_EXTERNAL_READ = 2,
- MOUNT_EXTERNAL_WRITE = 3,
- MOUNT_EXTERNAL_LEGACY = 4,
- MOUNT_EXTERNAL_INSTALLER = 5,
- MOUNT_EXTERNAL_FULL = 6,
- MOUNT_EXTERNAL_PASS_THROUGH = 7,
- MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
- MOUNT_EXTERNAL_COUNT = 9
-};
-
-// The order of entries here must be kept in sync with MountExternalKind enum values.
-static const std::array<const std::string, MOUNT_EXTERNAL_COUNT> ExternalStorageViews = {
- "", // MOUNT_EXTERNAL_NONE
- "/mnt/runtime/default", // MOUNT_EXTERNAL_DEFAULT
- "/mnt/runtime/read", // MOUNT_EXTERNAL_READ
- "/mnt/runtime/write", // MOUNT_EXTERNAL_WRITE
- "/mnt/runtime/write", // MOUNT_EXTERNAL_LEGACY
- "/mnt/runtime/write", // MOUNT_EXTERNAL_INSTALLER
- "/mnt/runtime/full", // MOUNT_EXTERNAL_FULL
- "/mnt/runtime/full", // MOUNT_EXTERNAL_PASS_THROUGH (only used w/ FUSE)
- "/mnt/runtime/full", // MOUNT_EXTERNAL_ANDROID_WRITABLE (only used w/ FUSE)
+ MOUNT_EXTERNAL_NONE = 0,
+ MOUNT_EXTERNAL_DEFAULT = 1,
+ MOUNT_EXTERNAL_INSTALLER = 5,
+ MOUNT_EXTERNAL_PASS_THROUGH = 7,
+ MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
+ MOUNT_EXTERNAL_COUNT = 9
};
// Must match values in com.android.internal.os.Zygote.
diff --git a/core/jni/core_jni_helpers.h b/core/jni/core_jni_helpers.h
index d629e0dae6dd..013c65faa241 100644
--- a/core/jni/core_jni_helpers.h
+++ b/core/jni/core_jni_helpers.h
@@ -90,6 +90,12 @@ static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
return res;
}
+static inline jobject jniGetReferent(JNIEnv* env, jobject ref) {
+ jclass cls = FindClassOrDie(env, "java/lang/ref/Reference");
+ jmethodID get = GetMethodIDOrDie(env, cls, "get", "()Ljava/lang/Object;");
+ return env->CallObjectMethod(ref, get);
+}
+
/**
* Read the specified field from jobject, and convert to std::string.
* If the field cannot be obtained, return defaultValue.
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 4892faaceafe..542d26fa233e 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -14,8 +14,12 @@ per-file settings_enums.proto=tmfang@google.com
# Frameworks
ogunwale@google.com
jjaggi@google.com
+roosa@google.com
per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com
+# Biometrics
+kchyn@google.com
+
# Launcher
hyunyoungs@google.com
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 7fac615aa88d..99fe1afc9c9d 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2751,4 +2751,14 @@ enum PageId {
// OS: R QPR2
BLUETOOTH_PAIRING_RECEIVER = 1851;
+
+ // OPEN: Settings > Display > Screen timeout
+ // CATEGORY: SETTINGS
+ // OS: S
+ SCREEN_TIMEOUT = 1852;
+
+ // OPEN: Settings > Accessibility > Reduce Bright Colors
+ // CATEGORY: SETTINGS
+ // OS: S
+ REDUCE_BRIGHT_COLORS_SETTINGS = 1853;
}
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 7fa0ff64f8bf..d578414eb177 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -49,6 +49,7 @@ message ConfigurationProto {
optional uint32 density_dpi = 18;
optional .android.app.WindowConfigurationProto window_configuration = 19;
optional string locale_list = 20;
+ optional uint32 force_bold_text = 21;
}
/**
diff --git a/core/proto/android/inputmethodservice/inputmethodservice.proto b/core/proto/android/inputmethodservice/inputmethodservice.proto
new file mode 100644
index 000000000000..3b4ebb5d73e7
--- /dev/null
+++ b/core/proto/android/inputmethodservice/inputmethodservice.proto
@@ -0,0 +1,61 @@
+/*
+ * 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";
+
+import "frameworks/base/core/proto/android/inputmethodservice/softinputwindow.proto";
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+
+package android.inputmethodservice;
+
+option java_multiple_files = true;
+
+message InputMethodServiceProto {
+ optional SoftInputWindowProto soft_input_window = 1;
+ optional bool views_created= 2;
+ optional bool decor_view_visible = 3;
+ optional bool decor_view_was_visible = 4;
+ optional bool window_visible = 5;
+ optional bool in_show_window = 6;
+ optional string configuration = 7;
+ optional string token = 8;
+ optional string input_binding = 9;
+ optional bool input_started = 10;
+ optional bool input_view_started = 11;
+ optional bool candidates_view_started = 12;
+ optional .android.view.inputmethod.EditorInfoProto input_editor_info = 13;
+ optional bool show_input_requested = 14;
+ optional bool last_show_input_requested = 15;
+ optional bool can_pre_render = 16;
+ optional bool is_pre_rendered = 17;
+ optional int32 show_input_flags = 18;
+ optional int32 candidates_visibility = 19;
+ optional bool fullscreen_applied = 20;
+ optional bool is_fullscreen = 21;
+ optional bool extract_view_hidden = 22;
+ optional int32 extracted_token = 23;
+ optional bool is_input_view_shown = 24;
+ optional int32 status_icon = 25;
+ optional InsetsProto last_computed_insets = 26;
+ optional string settings_observer = 27;
+
+ message InsetsProto {
+ optional int32 content_top_insets = 1;
+ optional int32 visible_top_insets = 2;
+ optional int32 touchable_insets = 3;
+ optional string touchable_region = 4;
+ }
+} \ No newline at end of file
diff --git a/core/proto/android/inputmethodservice/softinputwindow.proto b/core/proto/android/inputmethodservice/softinputwindow.proto
new file mode 100644
index 000000000000..85b7d7382805
--- /dev/null
+++ b/core/proto/android/inputmethodservice/softinputwindow.proto
@@ -0,0 +1,32 @@
+/*
+ * 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";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.inputmethodservice;
+
+option java_multiple_files = true;
+
+message SoftInputWindowProto {
+ optional string name = 1;
+ optional int32 window_type = 2;
+ optional int32 gravity = 3;
+ optional bool takes_focus = 4;
+ optional .android.graphics.RectProto bounds = 5;
+ optional int32 window_state = 6;
+} \ No newline at end of file
diff --git a/core/proto/android/providers/settings/config.proto b/core/proto/android/providers/settings/config.proto
index d433c0e1859d..f343c3a30927 100644
--- a/core/proto/android/providers/settings/config.proto
+++ b/core/proto/android/providers/settings/config.proto
@@ -29,6 +29,7 @@ message ConfigSettingsProto {
repeated NamespaceProto extra_namespaces = 2;
repeated SettingProto activity_manager_native_boot_settings = 3;
repeated SettingProto activity_manager_settings = 4;
+ repeated SettingProto alarm_manager_settings = 26;
repeated SettingProto app_compat_settings = 5;
repeated SettingProto autofill_settings = 6;
repeated SettingProto blobstore_settings = 23;
@@ -51,10 +52,10 @@ message ConfigSettingsProto {
repeated SettingProto telephony_settings = 21;
repeated SettingProto textclassifier_settings = 22;
- // Next tag: 26
+ // Next tag: 27
message NamespaceProto {
optional string namespace = 1;
repeated SettingProto settings = 2;
}
-} \ No newline at end of file
+}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 06f4041b242f..c8f3ce50811b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -47,7 +47,7 @@ message GlobalSettingsProto {
}
optional AirplaneMode airplane_mode = 5;
- optional SettingProto alarm_manager_constants = 6;
+ reserved 6; // alarm_manager_constants
optional SettingProto allow_user_switching_when_system_user_locked = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
// This is a key=value list, separated by commas.
optional SettingProto always_on_display_constants = 8;
@@ -463,6 +463,8 @@ message GlobalSettingsProto {
// Updatable Driver - List of Apps selected to use updatable prerelease driver
// i.e. <pkg1>,<pkg2>,...,<pkgN>
optional SettingProto updatable_driver_prerelease_opt_in_apps = 18;
+
+ optional SettingProto angle_egl_features = 19;
}
optional Gpu gpu = 59;
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 7f743ef037a2..83c53915694a 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -120,6 +120,14 @@ message SecureSettingsProto {
}
optional Assist assist = 7;
+ message AssistHandles {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto learning_time_elapsed_millis = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto learning_event_count = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional AssistHandles assist_handles = 86;
+
message Autofill {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -444,6 +452,15 @@ message SecureSettingsProto {
}
optional QuickSettings qs = 45;
+ message ReduceBrightColors {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto activated = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto level = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto persist_across_reboots = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional ReduceBrightColors reduce_bright_colors = 87;
+
message Rotation {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -518,11 +535,11 @@ message SecureSettingsProto {
}
optional Sounds sounds = 72;
+ optional SettingProto swipe_bottom_to_notification_enabled = 82 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Defines whether managed profile ringtones should be synced from its
// parent profile.
optional SettingProto sync_parent_sounds = 55 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto system_navigation_keys_enabled = 56 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto swipe_bottom_to_notification_enabled = 82 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto theme_customization_overlay_packages = 75 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto trust_agents_initialized = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -617,5 +634,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 86;
+ // Next tag = 88;
}
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
new file mode 100644
index 000000000000..35aae8f92d93
--- /dev/null
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -0,0 +1,50 @@
+/*
+ * 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";
+
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+
+package android.server.inputmethod;
+
+option java_multiple_files = true;
+
+message InputMethodManagerServiceProto {
+ optional string cur_method_id = 1;
+ optional int32 cur_seq = 2;
+ optional string cur_client = 3;
+ optional string cur_focused_window_name = 4;
+ optional string last_ime_target_window_name = 5;
+ optional string cur_focused_window_soft_input_mode = 6;
+ optional .android.view.inputmethod.EditorInfoProto cur_attribute = 7;
+ optional string cur_id = 8;
+ optional bool show_requested = 9;
+ optional bool show_explicitly_requested = 10;
+ optional bool show_forced = 11;
+ optional bool input_shown = 12;
+ optional bool in_fullscreen_mode = 13;
+ optional string cur_token = 14;
+ optional int32 cur_token_display_id = 15;
+ optional bool system_ready = 16;
+ optional int32 last_switch_user_id = 17;
+ optional bool have_connection = 18;
+ optional bool bound_to_method = 19;
+ optional bool is_interactive = 20;
+ optional int32 back_disposition = 21;
+ optional int32 ime_window_visibility = 22;
+ optional bool show_ime_with_hard_keyboard = 23;
+ optional bool accessibility_requesting_no_soft_keyboard = 24;
+} \ No newline at end of file
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index 59556c4414ce..c465233036c4 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -46,6 +46,10 @@ message ConversationInfoProto {
// The notification channel id of the conversation.
optional string notification_channel_id = 4 [(.android.privacy).dest = DEST_EXPLICIT];
+ // The parent notification channel ID of the conversation. This is the notification channel where
+ // the notifications are posted before this conversation is customized by the user.
+ optional string parent_notification_channel_id = 8 [(.android.privacy).dest = DEST_EXPLICIT];
+
// Integer representation of shortcut bit flags.
optional int32 shortcut_flags = 5;
@@ -54,6 +58,11 @@ message ConversationInfoProto {
// The phone number of the contact.
optional string contact_phone_number = 7 [(.android.privacy).dest = DEST_EXPLICIT];
+
+ // The timestamp of the last event in millis.
+ optional int64 last_event_timestamp = 9;
+
+ // Next tag: 10
}
// On disk data of events.
diff --git a/core/proto/android/server/vibratorservice.proto b/core/proto/android/server/vibratorservice.proto
index 281a25e55dc2..9e42e9edfd27 100644
--- a/core/proto/android/server/vibratorservice.proto
+++ b/core/proto/android/server/vibratorservice.proto
@@ -21,6 +21,12 @@ option java_multiple_files = true;
import "frameworks/base/core/proto/android/privacy.proto";
+message OneShotProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 duration = 1;
+ repeated int32 amplitude = 2;
+}
+
message WaveformProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
repeated int32 timings = 1;
@@ -35,20 +41,41 @@ message PrebakedProto {
optional int32 fallback = 3;
}
+message ComposedProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated int32 effect_ids = 1;
+ repeated float effect_scales = 2;
+ repeated int32 delays = 3;
+}
+
// A com.android.os.VibrationEffect object.
message VibrationEffectProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional OneShotProto oneshot = 3;
optional WaveformProto waveform = 1;
optional PrebakedProto prebaked = 2;
+ optional ComposedProto composed = 4;
+}
+
+message VibrationAttributesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 usage = 1;
+ optional int32 audio_usage = 2;
+ optional int32 flags = 3;
}
+// Next id: 7
message VibrationProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 start_time = 1;
+ optional int64 end_time = 4;
optional VibrationEffectProto effect = 2;
- optional VibrationEffectProto origin_effect = 3;
+ optional VibrationEffectProto original_effect = 3;
+ optional VibrationAttributesProto attributes = 5;
+ optional int32 status = 6;
}
+// Next id: 17
message VibratorServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional VibrationProto current_vibration = 1;
@@ -57,10 +84,14 @@ message VibratorServiceDumpProto {
optional bool vibrator_under_external_control = 4;
optional bool low_power_mode = 5;
optional int32 haptic_feedback_intensity = 6;
+ optional int32 haptic_feedback_default_intensity = 14;
optional int32 notification_intensity = 7;
+ optional int32 notification_default_intensity = 15;
optional int32 ring_intensity = 8;
+ optional int32 ring_default_intensity = 16;
repeated VibrationProto previous_ring_vibrations = 9;
repeated VibrationProto previous_notification_vibrations = 10;
repeated VibrationProto previous_alarm_vibrations = 11;
repeated VibrationProto previous_vibrations = 12;
+ repeated VibrationProto previous_external_vibrations = 13;
} \ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index d4b226dead21..d315ff27ef9a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -30,6 +30,10 @@ import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
import "frameworks/base/core/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/typedef.proto";
+import "frameworks/base/core/proto/android/view/surfacecontrol.proto";
+import "frameworks/base/core/proto/android/view/insetssource.proto";
+import "frameworks/base/core/proto/android/view/insetssourcecontrol.proto";
+
package com.android.server.wm;
option java_multiple_files = true;
@@ -47,6 +51,7 @@ message WindowManagerServiceDumpProto {
optional int32 rotation = 7 [(.android.typedef) = "android.view.Surface.Rotation"];
optional int32 last_orientation = 8 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"];
optional int32 focused_display_id = 9;
+ optional bool hard_keyboard_available = 10;
}
/* represents RootWindowContainer object */
@@ -195,6 +200,13 @@ message DisplayContentProto {
optional .com.android.server.wm.IdentifierProto resumed_activity = 24;
repeated TaskProto tasks = 25 [deprecated=true];
optional bool display_ready = 26;
+
+ optional WindowStateProto input_method_target = 27;
+ optional WindowStateProto input_method_input_target = 28;
+ optional WindowStateProto input_method_control_target = 29;
+ optional WindowStateProto current_focus = 30;
+ optional ImeInsetsSourceProviderProto ime_insets_source_provider = 31;
+ optional bool can_show_ime = 32;
}
/* represents DisplayArea object */
@@ -226,6 +238,8 @@ message DisplayFramesProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional .android.graphics.RectProto stable_bounds = 1;
+ optional .android.graphics.RectProto dock = 2;
+ optional .android.graphics.RectProto current = 3;
}
/* represents DockedStackDividerController */
@@ -488,3 +502,31 @@ message WindowFramesProto {
optional .android.graphics.RectProto stable_insets = 14;
optional .android.graphics.RectProto outsets = 15;
}
+
+message InsetsSourceProviderProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional .android.view.InsetsSourceProto source = 1;
+ optional .android.graphics.RectProto frame = 2;
+ optional .android.view.InsetsSourceControlProto fake_control = 3;
+ optional .android.view.InsetsSourceControlProto control = 4;
+ optional WindowStateProto control_target = 5;
+ optional WindowStateProto pending_control_target = 6;
+ optional WindowStateProto fake_control_target = 7;
+ optional .android.view.SurfaceControlProto captured_leash = 8;
+ optional .android.graphics.RectProto ime_overridden_frame = 9;
+ optional bool is_leash_ready_for_dispatching = 10;
+ optional bool client_visible = 11;
+ optional bool server_visible = 12;
+ optional bool seamless_rotating = 13;
+ optional int64 finish_seamless_rotate_frame_number = 14;
+ optional bool controllable = 15;
+}
+
+message ImeInsetsSourceProviderProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional InsetsSourceProviderProto insets_source_provider = 1;
+ optional WindowStateProto ime_target_from_ime = 2;
+ optional bool is_ime_layout_drawn = 3;
+} \ No newline at end of file
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 004b096496db..d289e00d3467 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -124,6 +124,11 @@ message PackageProto {
optional string originating_package_name = 2;
}
+ message StatesProto {
+ optional bool is_startable = 1;
+ optional bool is_loading = 2;
+ }
+
// Name of package. e.g. "com.android.providers.telephony".
optional string name = 1;
// UID for this package as assigned by Android OS.
@@ -145,4 +150,6 @@ message PackageProto {
repeated UserInfoProto users = 9;
// Where the request to install this package came from,
optional InstallSourceProto install_source = 10;
+ // Whether the package is startable or is still loading
+ optional StatesProto states = 11;
}
diff --git a/core/proto/android/stats/style/style_enums.proto b/core/proto/android/stats/style/style_enums.proto
index f3f491ff34cd..828e4127a708 100644
--- a/core/proto/android/stats/style/style_enums.proto
+++ b/core/proto/android/stats/style/style_enums.proto
@@ -38,6 +38,9 @@ enum Action {
LIVE_WALLPAPER_APPLIED = 16;
LIVE_WALLPAPER_INFO_SELECT = 17;
LIVE_WALLPAPER_CUSTOMIZE_SELECT = 18;
+ LIVE_WALLPAPER_QUESTIONNAIRE_SELECT = 19;
+ LIVE_WALLPAPER_QUESTIONNAIRE_APPLIED = 20;
+ LIVE_WALLPAPER_EFFECT_SHOW = 21;
}
enum LocationPreference {
@@ -46,3 +49,9 @@ enum LocationPreference {
LOCATION_CURRENT = 2;
LOCATION_MANUAL = 3;
}
+
+enum DatePreference {
+ DATE_PREFERENCE_UNSPECIFIED = 0;
+ DATE_UNAVAILABLE = 1;
+ DATE_MANUAL = 2;
+}
diff --git a/core/proto/android/telephony/enums.proto b/core/proto/android/telephony/enums.proto
index f14e3ed1872d..d2c0ed4e4736 100644
--- a/core/proto/android/telephony/enums.proto
+++ b/core/proto/android/telephony/enums.proto
@@ -159,3 +159,64 @@ enum SimStateEnum {
*/
SIM_STATE_PRESENT = 11;
}
+
+// Format of SMS message
+enum SmsFormatEnum {
+ /** Unknown format */
+ SMS_FORMAT_UNKNOWN = 0;
+ /** Format compliant with 3GPP TS 23.040 */
+ SMS_FORMAT_3GPP = 1;
+ /** Format compliant with 3GPP2 TS C.S0015-B */
+ SMS_FORMAT_3GPP2 = 2;
+}
+
+// Technology used to carry an SMS message
+enum SmsTechEnum {
+ /**
+ * Unknown SMS technology used to carry the SMS.
+ * This value is also used for injected SMS.
+ */
+ SMS_TECH_UNKNOWN = 0;
+ /** The SMS was carried over CS bearer in 3GPP network */
+ SMS_TECH_CS_3GPP = 1;
+ /** The SMS was carried over CS bearer in 3GPP2 network */
+ SMS_TECH_CS_3GPP2 = 2;
+ /** The SMS was carried over IMS */
+ SMS_TECH_IMS = 3;
+}
+
+// Types of SMS message
+enum SmsTypeEnum {
+ /** Normal type. */
+ SMS_TYPE_NORMAL = 0;
+ /** SMS-PP (point-to-point). */
+ SMS_TYPE_SMS_PP = 1;
+ /** Voicemail indication. */
+ SMS_TYPE_VOICEMAIL_INDICATION = 2;
+ /** Type 0 message (3GPP TS 23.040 9.2.3.9). */
+ SMS_TYPE_ZERO = 3;
+ /** WAP-PUSH message. */
+ SMS_TYPE_WAP_PUSH = 4;
+}
+
+// Incoming SMS errors
+enum SmsIncomingErrorEnum {
+ SMS_SUCCESS = 0;
+ SMS_ERROR_GENERIC = 1;
+ SMS_ERROR_NO_MEMORY = 2;
+ SMS_ERROR_NOT_SUPPORTED = 3;
+}
+
+// Outgoing SMS results
+enum SmsSendResultEnum {
+ // Unknown error
+ SMS_SEND_RESULT_UNKNOWN = 0;
+ // Success
+ SMS_SEND_RESULT_SUCCESS = 1;
+ // Permanent error
+ SMS_SEND_RESULT_ERROR = 2;
+ // Temporary error, retry
+ SMS_SEND_RESULT_ERROR_RETRY = 3;
+ // Error over IMS, retry on CS
+ SMS_SEND_RESULT_ERROR_FALLBACK = 4;
+}
diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto
index 680916345a31..5bee81bdc7cd 100644
--- a/core/proto/android/view/imeinsetssourceconsumer.proto
+++ b/core/proto/android/view/imeinsetssourceconsumer.proto
@@ -17,6 +17,7 @@
syntax = "proto2";
import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto";
package android.view;
@@ -26,6 +27,7 @@ option java_multiple_files = true;
* Represents a {@link android.view.ImeInsetsSourceConsumer} object.
*/
message ImeInsetsSourceConsumerProto {
- optional .android.view.inputmethod.EditorInfoProto focused_editor = 1;
- optional bool is_requested_visible_awaiting_control = 2;
+ optional InsetsSourceConsumerProto insets_source_consumer = 1;
+ optional .android.view.inputmethod.EditorInfoProto focused_editor = 2;
+ optional bool is_requested_visible_awaiting_control = 3;
} \ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
index 732213966014..856d8da1a1ea 100644
--- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
+++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
@@ -22,11 +22,13 @@ package android.view.inputmethod;
import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto";
import "frameworks/base/core/proto/android/view/viewrootimpl.proto";
import "frameworks/base/core/proto/android/view/insetscontroller.proto";
-import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto";
import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto";
import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
import "frameworks/base/core/proto/android/view/imefocuscontroller.proto";
+import "frameworks/base/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto";
+import "frameworks/base/core/proto/android/inputmethodservice/inputmethodservice.proto";
+
/**
* Represents a file full of input method editor trace entries.
* Encoded, it should start with 0x9 0x49 0x4d 0x45 0x54 0x52 0x41 0x43 0x45 (.IMETRACE), such
@@ -54,14 +56,21 @@ message InputMethodEditorProto {
/* required: elapsed realtime in nanos since boot of when this entry was logged */
optional fixed64 elapsed_realtime_nanos = 1;
- optional ClientSideProto client_side_dump = 2;
+ optional ClientsProto clients = 2;
+ optional .android.inputmethodservice.InputMethodServiceProto input_method_service = 3;
+ optional .android.server.inputmethod.InputMethodManagerServiceProto input_method_manager_service = 4;
+
+ // this wrapper helps to simplify the dumping logic
+ message ClientsProto {
+ repeated ClientSideProto client = 1;
+ }
/* groups together the dump from ime related client side classes */
message ClientSideProto {
- optional InputMethodManagerProto input_method_manager = 1;
- optional ViewRootImplProto view_root_impl = 2;
- optional InsetsControllerProto insets_controller = 3;
- optional InsetsSourceConsumerProto insets_source_consumer = 4;
+ optional int32 display_id = 1;
+ optional InputMethodManagerProto input_method_manager = 2;
+ optional ViewRootImplProto view_root_impl = 3;
+ optional InsetsControllerProto insets_controller = 4;
optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5;
optional EditorInfoProto editor_info = 6;
optional ImeFocusControllerProto ime_focus_controller = 7;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c09b51dc4a93..67067250acb8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -42,6 +42,9 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_STARTABLE" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_UNSTARTABLE" />
+ <protected-broadcast android:name="android.intent.action.PACKAGE_FULLY_LOADED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />
<protected-broadcast android:name="android.intent.action.ROLLBACK_COMMITTED" />
@@ -147,7 +150,7 @@
<protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.UUID" />
<protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
- <protected-broadcast android:name="android.bluetooth.action.ALIAS_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
<protected-broadcast android:name="android.bluetooth.device.action.CLASS_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.ACL_CONNECTED" />
@@ -542,6 +545,7 @@
<protected-broadcast android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_REGISTERED" />
<protected-broadcast android:name="android.telecom.action.PHONE_ACCOUNT_UNREGISTERED" />
+ <protected-broadcast android:name="android.telecom.action.POST_CALL" />
<protected-broadcast android:name="android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION" />
<protected-broadcast android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
<protected-broadcast android:name="android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED" />
@@ -1277,8 +1281,19 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_recordAudio"
android:description="@string/permdesc_recordAudio"
+ android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO"
android:protectionLevel="dangerous|instant" />
+ <!-- Allows an application to record audio while in the background.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_recordBackgroundAudio"
+ android:description="@string/permdesc_recordBackgroundAudio"
+ android:permissionFlags="hardRestricted|installerExemptIgnored"
+ android:protectionLevel="dangerous" />
+
<!-- ====================================================================== -->
<!-- Permissions for activity recognition -->
<!-- ====================================================================== -->
@@ -1346,8 +1361,19 @@
android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera"
+ android:backgroundPermission="android.permission.BACKGROUND_CAMERA"
android:protectionLevel="dangerous|instant" />
+ <!-- Required to be able to access the camera device in the background.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.BACKGROUND_CAMERA"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_backgroundCamera"
+ android:description="@string/permdesc_backgroundCamera"
+ android:permissionFlags="hardRestricted|installerExemptIgnored"
+ android:protectionLevel="dangerous" />
+
<!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
system only camera devices.
<p>Protection level: system|signature
@@ -1591,6 +1617,13 @@
<permission android:name="android.permission.INSTALL_LOCATION_PROVIDER"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to install a LocationTimeZoneProvider into the
+ LocationTimeZoneProviderManager.
+ <p>Not for use by third-party applications.
+ -->
+ <permission android:name="android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allows HDMI-CEC service to access device and configuration files.
This should only be used by HDMI-CEC service.
-->
@@ -2497,8 +2530,9 @@
android:protectionLevel="signature|privileged" />
<!-- @SystemApi @hide Allows an application to create, remove users and get the list of
- users on the device. Applications holding this permission can only create restricted,
- guest, managed, demo, and ephemeral users. For creating other kind of users,
+ users on the device. Applications holding this permission can create users (including
+ normal, restricted, guest, managed, and demo users) and can optionally endow them with the
+ ephemeral property. For creating users with other kinds of properties,
{@link android.Manifest.permission#MANAGE_USERS} is needed.
This permission is not available to third party applications. -->
<permission android:name="android.permission.CREATE_USERS"
@@ -2724,6 +2758,14 @@
<permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
android:protectionLevel="signature" />
+ <!-- Allows applications like settings to manage configuration associated with automatic time
+ and time zone detection.
+ <p>Not for use by third-party applications.
+ @SystemApi @hide
+ -->
+ <permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION"
+ android:protectionLevel="signature|privileged" />
+
<!-- ==================================================== -->
<!-- Permissions related to changing status bar -->
<!-- ==================================================== -->
@@ -3484,6 +3526,14 @@
<permission android:name="android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"
android:protectionLevel="signature" />
+ <!-- Must be declared by a android.service.musicrecognition.MusicRecognitionService,
+ to ensure that only the system can bind to it.
+ @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_MUSIC_RECOGNITION_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a android.service.autofill.augmented.AugmentedAutofillService,
to ensure that only the system can bind to it.
@SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -3583,6 +3633,13 @@
android:protectionLevel="signature" />
<uses-permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID" />
+ <!-- This permission is required by Media Resource Observer Service when
+ accessing its registerObserver Api.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER"
+ android:protectionLevel="signature|privileged" />
<!-- Must be required by a {@link android.media.routing.MediaRouteService}
to ensure that only the system can interact with it.
@@ -3952,6 +4009,14 @@
<permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"
android:protectionLevel="signature" />
+ <!-- Allows an application to override the display mode requests
+ so the app requested mode will be selected and user settings and display
+ policies will be ignored.
+ @hide
+ @TestApi -->
+ <permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -4014,6 +4079,15 @@
<permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
android:protectionLevel="signature|privileged" />
+ <!-- Puts an application in the chain of trust for sound trigger
+ operations. Being in the chain of trust allows an application to
+ delegate an identity of a separate entity to the sound trigger system
+ and vouch for the authenticity of this identity.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to modify audio routing and override policy decisions.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -4342,6 +4416,10 @@
<permission android:name="android.permission.WRITE_DREAM_STATE"
android:protectionLevel="signature|privileged" />
+ <!-- @hide Allows applications to read whether ambient display is suppressed. -->
+ <permission android:name="android.permission.READ_DREAM_SUPPRESSION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allow an application to read and write the cache partition.
@hide -->
<permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
@@ -4478,6 +4556,12 @@
<permission android:name="android.permission.MANAGE_NOTIFICATIONS"
android:protectionLevel="signature" />
+ <!-- @SystemApi @TestApi Allows adding/removing enabled notification listener components.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
+
<!-- Allows notifications to be colorized
<p>Not for use by third-party applications. @hide -->
<permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
@@ -4501,6 +4585,12 @@
<permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT"
android:protectionLevel="signature" />
+ <!-- Allows access to TestApis for various components in the biometric stack, including
+ FingerprintService, FaceService, BiometricService. Used by com.android.server.biometrics
+ CTS tests. @hide @TestApi -->
+ <permission android:name="android.permission.TEST_BIOMETRIC"
+ android:protectionLevel="signature" />
+
<!-- Allows direct access to the <Biometric>Service interfaces. Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC"
android:protectionLevel="signature" />
@@ -4847,6 +4937,11 @@
<permission android:name="android.permission.MANAGE_CONTENT_CAPTURE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows an application to manage the music recognition service.
+ @hide <p>Not for use by third-party applications.</p> -->
+ <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to manage the content suggestions service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index f5facea7fc26..bd81519e93b3 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"verander jou klankinstellings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"neem klank op"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Hierdie program kan enige tyd oudio met die mikrofoon opneem."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Hierdie die program kan oudio met die mikrofoon opneem terwyl die program gebruik word."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"neem oudio op die agtergrond op"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Hierdie program kan enige tyd oudio met die mikrofoon opneem."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"stuur bevele na die SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Laat die program toe om bevele na die SIM te stuur. Dit is baie gevaarlik."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"herken fisieke aktiwiteit"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Hierdie program kan jou fisieke aktiwiteit herken."</string>
<string name="permlab_camera" msgid="6320282492904119413">"neem foto\'s en video\'s"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Hierdie program kan enige tyd met die kamera foto\'s neem en video\'s opneem."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Hierdie program kan met die kamera foto\'s neem en video\'s opneem terwyl die program gebruik word."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"neem foto\'s en video\'s op die agtergrond"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Hierdie program kan enige tyd met die kamera foto\'s neem en video\'s opneem."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Gee \'n program of diens toegang tot stelselkameras om foto\'s en video\'s te neem"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Hierdie bevoorregte of stelselprogram kan enige tyd met \'n stelselkamera foto\'s neem en video\'s opneem. Vereis dat die program ook die android.permission.CAMERA-toestemming het"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Laat \'n program of diens toe om terugbeloproepe te ontvang oor kameratoestelle wat oopgemaak of toegemaak word."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Verhoog volume bo aanbevole vlak?\n\nOm lang tydperke teen hoë volume te luister, kan jou gehoor beskadig."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gebruik toeganklikheidkortpad?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Skakel toeganklikheidkenmerke aan?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Skakel kortpad vir toeganklikheidskenmerke aan?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit toeganklikheidkenmerke aan. Dit kan verander hoe jou toestel werk.\n\nHuidige kenmerke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJy kan geselekteerde kenmerke in Instellings en Toeganklikheid verander."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Skakel <xliff:g id="SERVICE">%1$s</xliff:g> aan?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Skakel <xliff:g id="SERVICE">%1$s</xliff:g>-kortpad aan?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"As jy albei volumesleutels vir \'n paar sekondes hou, skakel dit <xliff:g id="SERVICE">%1$s</xliff:g>, \'n toeganklikheidkenmerk, aan. Dit kan verander hoe jou toestel werk.\n\nJy kan hierdie kortpad na \'n ander kenmerk in Instellings en Toeganklikheid verander."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Skakel aan"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Moenie aanskakel nie"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d uur lank</item>
<item quantity="one">1 uur lank</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgende wekker)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Totdat jy dit afskakel"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index bea18616cba9..0bbe23262c92 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"የድምፅ ቅንብሮችን ለውጥ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"መተግበሪያው አንደ የድምጽ መጠን እና ለውጽአት የትኛውን የድምጽ ማጉያ ጥቅም ላይ እንደዋለ የመሳሰሉ ሁለንተናዊ የድምጽ ቅንብሮችን እንዲያስተካክል ይፈቅድለታል።"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ኦዲዮ ይቅዱ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ይህ መተግበሪያ በማናቸውም ጊዜ ማይክራፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ይህ መተግበሪያ መተግበሪያው ስራ ላይ ሳለ ማይክሮፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"በበስተጀርባ ኦዲዮን ይቅዱ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ይህ መተግበሪያ በማናቸውም ጊዜ ማይክራፎኑን በመጠቀም ኦዲዮን መቅዳት ይችላል።"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ወደ ሲሙ ትዕዛዞችን መላክ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"መተግበሪያው ትዕዛዞችን ወደ ሲሙ እንዲልክ ያስችለዋል። ይሄ በጣማ አደገኛ ነው።"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"አካላዊ እንቅስቃሴን ለይቶ ማወቅ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ይህ መተግበሪያ አካላዊ እንቅስቃሴዎን ለይቶ ሊያውቅ ይችላል።"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ፎቶዎች እና ቪዲዮዎች ያንሱ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ይህ መተግበሪያ በማናቸውም ጊዜ ካሜራውን በመጠቀም ፎቶ ሊያነሳ እና ቪዲዮዎችን ሊቀርጽ ይችላል።"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ይህ መተግበሪያ መተግበሪያው ስራ ላይ ሳለ ካሜራውን በመጠቀም ሥዕሎችን ማንሳት እና ቪዲዮዎችን መቅዳት ይችላል።"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"በበስተጀርባ ስዕሎችን እና ቪዲዮዎችን ያንሱ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ይህ መተግበሪያ በማናቸውም ጊዜ ካሜራውን በመጠቀም ፎቶ ሊያነሳ እና ቪዲዮዎችን ሊቀርጽ ይችላል።"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ሥዕሎችን ለማንሣት እና ቪዲዮዎችን ለመቅረጽ እንዲችሉ ወደ ሥርዓት ካሜራዎች ለመተግበሪያ ወይም ለአገልግሎት መዳረሻ ይፍቀዱ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ይህ ልዩ ፈቃድ ያለው የሥርዓት መተግበሪያ በማንኛውም ጊዜ የሥርዓት ካሜራን በመጠቀም ሥዕሎችን ማንሣት እና ቪዲዮ መቅረጽ ይችላል። የandroid.permission.CAMERA ፈቃዱ በመተግበሪያውም ጭምር እንዲያዝ ያስፈልገዋል።"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"አንድ መተግበሪያ ወይም አገልግሎት እየተከፈቱ ወይም እየተዘጉ ስላሉ የካሜራ መሣሪያዎች መልሶ ጥሪዎችን እንዲቀበል ይፍቀዱ።"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"አቋራጩ ሲበራ ሁለቱንም የድምጽ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"የተደራሽነት ባሕሪያት ይብሩ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"የተደራሽነት ባህሪዎች አቋራጭ ይብራ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nየአሁን ባሕሪያት፦\n<xliff:g id="SERVICE">%1$s</xliff:g>\nበቅንብሮች &gt; ተደራሽነት ውስጥ የተመረጡትን ባሕሪያት መለወጥ ይችላሉ።"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ይብራ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"የ<xliff:g id="SERVICE">%1$s</xliff:g> አቋራጭ ይብራ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ሁለቱንም የድምፅ ቁልፎች ወደ ታች ለጥቂት ሰከንዶች መያዝ የተደራሽነት ባሕሪያትን <xliff:g id="SERVICE">%1$s</xliff:g> ያበራል። ይህ የእርስዎ መሣሪያ እንዴት እንደሚሠራ ሊለውጥ ይችላል።\n\nበቅንብሮች &gt; ተደራሽነት ውስጥ ወደ ሌላ ባሕሪ ይህን አቋራጭ መለወጥ ይችላሉ።"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"አብራ"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"አታብራ"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">ለ%d ሰዓት</item>
<item quantity="other">ለ%d ሰዓት</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ድረስ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"እስከ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ቀጣይ ማንቂያ)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"እስኪያጠፉት ድረስ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68323448a44f..8868fb67ccc8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -444,13 +444,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغيير إعداداتك الصوتية"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"للسماح للتطبيق بتعديل إعدادات الصوت العامة مثل مستوى الصوت وأي السماعات يتم استخدامها للاستماع."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"تسجيل الصوت"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون في أي وقت."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون عندما يكون التطبيق قيد الاستخدام."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"تسجيل الصوت في الخلفية"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"يمكن لهذا التطبيق تسجيل الصوت باستخدام الميكروفون في أي وقت."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"‏إرسال أوامر إلى شريحة SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"‏السماح للتطبيق بإرسال أوامر إلى شريحة SIM. وهذا أمر بالغ الخطورة."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"التعرّف على النشاط البدني"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"يمكن لهذا التطبيق التعرّف على نشاطك البدني."</string>
<string name="permlab_camera" msgid="6320282492904119413">"التقاط صور وفيديوهات"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"يمكن لهذا التطبيق التقاط صور وتسجيل فيديوهات باستخدام الكاميرا في أي وقت."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"يمكن لهذا التطبيق التقاط صور وتسجيل فيديوهات باستخدام الكاميرا عندما يكون التطبيق قيد الاستخدام."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"التقاط صور وتصوير فيديوهات في الخلفية"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"يمكن لهذا التطبيق التقاط صور وتسجيل فيديوهات باستخدام الكاميرا في أي وقت."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"السماح لتطبيق أو خدمة بالوصول إلى كاميرات النظام لالتقاط صور وتسجيل فيديوهات"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"‏إنّ تطبيق النظام هذا، أو التطبيق المزوّد بأذونات مميّزة، يمكنه التقاط صور وتسجيل فيديوهات باستخدام كاميرا النظام في أي وقت. ويجب أن يحصل التطبيق أيضًا على الإذن android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"يسمح الإذن لتطبيق أو خدمة بتلقّي استدعاءات عما إذا كانت أجهزة الكاميرات مفتوحة أو مغلقة."</string>
@@ -1710,10 +1714,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟\n\nقد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"هل تريد استخدام اختصار \"سهولة الاستخدام\"؟"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"عند تفعيل الاختصار، يؤدي الضغط على زرّي التحكّم في مستوى الصوت معًا لمدة 3 ثوانٍ إلى تفعيل إحدى ميزات إمكانية الوصول."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"هل تريد تفعيل ميزات إمكانية الوصول؟"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"هل تريد تفعيل الاختصار لميزات إمكانية الوصول؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"يؤدي الضغط مع الاستمرار على كلا مفتاحَي التحكّم في مستوى الصوت لبضع ثوانٍ إلى تفعيل ميزات إمكانية الوصول. قد يؤدي هذا الإجراء إلى تغيير طريقة عمل جهازك.\n\nالميزات الحالية:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nيمكنك تغيير الميزات المحددة في الإعدادات &gt; إمكانية الوصول."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"هل تريد تفعيل <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"هل تريد تفعيل اختصار <xliff:g id="SERVICE">%1$s</xliff:g>؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"‏يؤدي الضغط مع الاستمرار لبضع ثوانٍ على كلا مفتاحَي التحكّم في مستوى الصوت إلى تفعيل <xliff:g id="SERVICE">%1$s</xliff:g> وهي إحدى ميزات إمكانية الوصول. يمكن أن يؤدي هذا الإجراء إلى تغيير كيفية عمل جهازك.\n\nيمكنك تغيير هذا الاختصار لاستخدامه مع ميزة أخرى في الإعدادات &gt; أدوات تمكين الوصول."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"تفعيل"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"عدم التفعيل"</string>
@@ -1953,6 +1957,7 @@
<item quantity="other">‏لمدة %d من الساعات</item>
<item quantity="one">لمدة ساعة</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"حتى <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (التنبيه التالي)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"إلى أن يتم إيقاف الوضع"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index bad330e75864..619666b9bf0d 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপোনাৰ অডিঅ\' ছেটিংসমূহ সলনি কৰক"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"এপটোক ভলিউমৰ দৰে গ্ল\'বেল অডিঅ\' ছেটিংসমূহ যাৰ স্পীকাৰক আউটপুটৰ বাবে ব্যৱহাৰ হয় তাক সলনি কৰিবলৈ অনুমতি দিয়ে৷"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"অডিঅ\' ৰেকর্ড কৰক"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"এই এপটোৱে যিকোনো সময়তে মাইক্ৰ\'ফ\'ন ব্যৱহাৰ কৰি অডিঅ\' ৰেকৰ্ড কৰিব পাৰে।"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"এই এপ্‌টোৱে ইয়াক ব্যৱহাৰ কৰি থাকোঁতে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকর্ড কৰিব পাৰে।"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"নেপথ্যত অডিঅ’ ৰেকৰ্ড কৰক"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই এপ্‌টোৱে যিকোনো সময়তে মাইক্ৰ’ফ’ন ব্যৱহাৰ কৰি অডিঅ’ ৰেকৰ্ড কৰিব পাৰে।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ছিমলৈ নিৰ্দেশ পঠিয়াব পাৰে"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ছিমলৈ নিৰ্দেশসমূহ প্ৰেৰণ কৰিবলৈ এপক অনুমতি দিয়ে। ই অতি ক্ষতিকাৰক।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰক"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"এই এপটোৱে আপোনাৰ শাৰীৰিক কাৰ্যকলাপ চিনাক্ত কৰিব পাৰে।"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ফট\' তোলা আৰু ভিডিঅ\' ৰেকৰ্ড কৰা"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"এই এপে যিকোনো সময়তে কেমেৰা ব্যৱহাৰ কৰি ফট\' তুলিব আৰু ভিডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"এই এপ্‌টোৱে ইয়াক ব্যৱহাৰ কৰি থাকোঁতে কেমেৰা ব্যৱহাৰ কৰি ফট’ তুলিব আৰু ভিডিঅ’ ৰেকর্ড কৰিব পাৰে।"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"নেপথ্যত ফট’ তোলক আৰু ভিডিঅ’ ৰেকৰ্ড কৰক"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"এই এপ্‌টোৱে যিকোনো সময়তে কেমেৰা ব্যৱহাৰ কৰি ফট’ তুলিব আৰু ভিডিঅ’ ৰেকর্ড কৰিব পাৰে।"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ফট’ উঠাবলৈ আৰু ভিডিঅ’ ৰেকৰ্ড কৰিবলৈ এটা এপ্লিকেশ্বন অথবা সেৱাক ছিষ্টেম কেমেৰাসমূহ এক্সেছ কৰিবলৈ দিয়ক"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"এই বিশেষাধিকাৰ প্ৰাপ্ত অথবা ছিষ্টেম এপ্‌টোৱে এটা ছিষ্টেম কেমেৰা ব্যৱহাৰ কৰি যিকোনো সময়তে ফট’ উঠাব পাৰে আৰু ভিডিঅ’ ৰেকৰ্ড কৰিব পাৰে। লগতে এপ্‌টোৰো android.permission.CAMERAৰ অনুমতি থকাটো প্ৰয়োজনীয়"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনো এপ্লিকেশ্বন অথবা সেৱাক কেমেৰা ডিভাইচসমূহ খোলা অথবা বন্ধ কৰাৰ বিষয়ে কলবেকসমূহ গ্ৰহণ কৰিবলৈ অনুমতি দিয়ক।"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"দিব্যাংগসকলৰ সুবিধাৰ শ্বৰ্টকাট ব্যৱহাৰ কৰেনে?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শ্বৰ্টকাটটো অন হৈ থকাৰ সময়ত দুয়োটা ভলিউম বুটাম ৩ ছেকেণ্ডৰ বাবে হেঁচি ধৰি ৰাখিলে এটা সাধ্য সুবিধা আৰম্ভ হ’ব।"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"সাধ্য-সুবিধাসমূহ অন কৰিবনে?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"সাধ্য সুবিধাসমূহৰ বাবে শ্বৰ্টকাট অন কৰিবনে?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে সাধ্য-সুবিধাসমূহ অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nবর্তমানৰ সুবিধাসমূহ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nআপুনি ছেটিংসমূহ &gt; সাধ্য-সুবিধাত কিছুমান নিৰ্দিষ্ট সুবিধা সলনি কৰিব পাৰে।"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> অন কৰিবনে?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>ৰ শ্বৰ্টকাট অন কৰিবনে?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"দুয়োটা ভলিউম কী কিছুসময়ৰ বাবে ধৰি থাকিলে এটা সাধ্য- সুবিধা <xliff:g id="SERVICE">%1$s</xliff:g> অন কৰে। এইটোৱে আপোনাৰ ডিভাইচটোৱে কাম কৰাৰ ধৰণ সলনি কৰিব পাৰে।\n\nআপুনি ছেটিংসমূহ &gt; সাধ্য-সুবিধাসমূহত এই শ্বৰ্টকাটটো অন্য এটা সুবিধালৈ সলনি কৰিব পাৰে।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"অন কৰক"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"অন নকৰিব"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d ঘণ্টাৰ বাবে</item>
<item quantity="other">%d ঘণ্টাৰ বাবে</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পৰ্যন্ত"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পৰ্যন্ত"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (পৰৱৰ্তী এলার্ম) পর্যন্ত"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"আপুনি অফ নকৰা পর্যন্ত"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index c721bbf8c829..ed708cce9856 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio ayarlarınızı dəyişir"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tətbiqə səs və hansı spikerin çıxış üçün istifadə olunduğu kimi qlobal səs ayarlarını dəyişdirməyə imkan verir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"səs yaz"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu tətbiq istədiyiniz zaman mikrofonu istifadə edərək audio qeyd edə bilər."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu tətbiq istifadə edilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arxa fonda audio yazmaq"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu tətbiq istənilən zaman mikrofondan istifadə edərək audio yaza bilər."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"əmrləri SIM\'ə göndərin"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tətbiqə SIM-ə əmrlər göndərməyə imkan verir. Bu, çox təhlükəlidir."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziki fəaliyyəti tanıyın"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu tətbiq fiziki fəaliyyətinizi tanıya bilər."</string>
<string name="permlab_camera" msgid="6320282492904119413">"şəkil və video çəkmək"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Bu tətbiq istədiyiniz zaman kameranı istifadə edərək şəkil çəkə və video qeydə ala bilər."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Bu tətbiq istifadə edilən zaman kameradan istifadə edərək şəkil çəkə və video yaza bilər."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"arxa fonda şəkillər və videolar çəkmək"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Bu tətbiq istənilən zaman kameradan istifadə edərək şəkil çəkə və video yaza bilər."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Şəkil və video çəkmək üçün tətbiq və ya xidmətlərin sistem kameralarına girişinə icazə verin"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Bu icazəli və ya sistem tətbiqi istənilən vaxt sistem kamerasından istifadə edərək şəkil və videolar çəkə bilər. android.permission.CAMERA icazəsinin də tətbiq tərəfindən saxlanılmasını tələb edir"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tətbiqə və ya xidmətə kamera cihazlarının açılması və ya bağlanması haqqında geri zənglər qəbul etməyə icazə verin."</string>
@@ -704,7 +708,7 @@
<string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"Bəzi ekran kilidi funksiyalarını deaktiv edin"</string>
<string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Bəzi ekran funksiyaları istifadəsinin qarşısını alın."</string>
<string-array name="phoneTypes">
- <item msgid="8996339953292723951">"Əsas səhifə"</item>
+ <item msgid="8996339953292723951">"Ev"</item>
<item msgid="7740243458912727194">"Mobil"</item>
<item msgid="8526146065496663766">"İş"</item>
<item msgid="8150904584178569699">"İş Faksı"</item>
@@ -714,19 +718,19 @@
<item msgid="6216981255272016212">"Şəxsi"</item>
</string-array>
<string-array name="emailAddressTypes">
- <item msgid="7786349763648997741">"Ana səhifə"</item>
+ <item msgid="7786349763648997741">"Ev"</item>
<item msgid="435564470865989199">"İş"</item>
<item msgid="4199433197875490373">"Digər"</item>
<item msgid="3233938986670468328">"Fərdi"</item>
</string-array>
<string-array name="postalAddressTypes">
- <item msgid="3861463339764243038">"Əsas səhifə"</item>
+ <item msgid="3861463339764243038">"Ev"</item>
<item msgid="5472578890164979109">"İş"</item>
<item msgid="5718921296646594739">"Digər"</item>
<item msgid="5523122236731783179">"Düzənləyin"</item>
</string-array>
<string-array name="imAddressTypes">
- <item msgid="588088543406993772">"Əsas səhifə"</item>
+ <item msgid="588088543406993772">"Ev"</item>
<item msgid="5503060422020476757">"İş"</item>
<item msgid="2530391194653760297">"Digər"</item>
<item msgid="7640927178025203330">"Fərdi"</item>
@@ -772,16 +776,16 @@
<string name="eventTypeAnniversary" msgid="4684702412407916888">"İldönümü"</string>
<string name="eventTypeOther" msgid="530671238533887997">"Digər"</string>
<string name="emailTypeCustom" msgid="1809435350482181786">"Fərdi"</string>
- <string name="emailTypeHome" msgid="1597116303154775999">"Əsas səhifə"</string>
+ <string name="emailTypeHome" msgid="1597116303154775999">"Şəxsi"</string>
<string name="emailTypeWork" msgid="2020095414401882111">"İş"</string>
<string name="emailTypeOther" msgid="5131130857030897465">"Digər"</string>
<string name="emailTypeMobile" msgid="787155077375364230">"Mobil"</string>
<string name="postalTypeCustom" msgid="5645590470242939129">"Fərdi"</string>
- <string name="postalTypeHome" msgid="7562272480949727912">"Əsas səhifə"</string>
+ <string name="postalTypeHome" msgid="7562272480949727912">"Ev"</string>
<string name="postalTypeWork" msgid="8553425424652012826">"İş"</string>
<string name="postalTypeOther" msgid="7094245413678857420">"Digər"</string>
<string name="imTypeCustom" msgid="5653384545085765570">"Fərdi"</string>
- <string name="imTypeHome" msgid="6996507981044278216">"Ana səhifə"</string>
+ <string name="imTypeHome" msgid="6996507981044278216">"Ev"</string>
<string name="imTypeWork" msgid="2099668940169903123">"İş"</string>
<string name="imTypeOther" msgid="8068447383276219810">"Digər"</string>
<string name="imProtocolCustom" msgid="4437878287653764692">"Şəxsi"</string>
@@ -813,7 +817,7 @@
<string name="relationTypeSister" msgid="3721676005094140671">"Bacı"</string>
<string name="relationTypeSpouse" msgid="6916682664436031703">"Həyat yoldaşı"</string>
<string name="sipAddressTypeCustom" msgid="6283889809842649336">"Fərdi"</string>
- <string name="sipAddressTypeHome" msgid="5918441930656878367">"Əsas səhifə"</string>
+ <string name="sipAddressTypeHome" msgid="5918441930656878367">"Ev"</string>
<string name="sipAddressTypeWork" msgid="7873967986701216770">"İş"</string>
<string name="sipAddressTypeOther" msgid="6317012577345187275">"Digər"</string>
<string name="quick_contacts_not_available" msgid="1262709196045052223">"Bu kontakta baxmaq üçün heç bir tətbiq tapılmadı."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Səsin həcmi tövsiyə olunan səviyyədən artıq olsun?\n\nYüksək səsi uzun zaman dinləmək eşitmə qabiliyyətinizə zərər vura bilər."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Əlçatımlılıq Qısayolu istifadə edilsin?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Qısayol aktiv olduqda, hər iki səs düyməsinə 3 saniyə basıb saxlamaqla əlçatımlılıq funksiyası başladılacaq."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Əlçatımlılıq funksiyaları aktiv edilsin?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Əlçatımlılıq funksiyaları üçün qısayol aktiv edilsin?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyaları aktiv olur. Bu, cihazınızın işləmə qaydasını dəyişə bilər.\n\nCari funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAyarlar və Əlçatımlılıq bölməsində seçilmiş funksiyaları dəyişə bilərsiniz."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktiv edilsin?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> qısayolu aktiv edilsin?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hər iki səs səviyyəsi düyməsinə bir neçə saniyə basıb saxladıqda əlçatımlılıq funksiyası olan <xliff:g id="SERVICE">%1$s</xliff:g> aktiv olur. Bu, cihazınızın işləmə qaydasını dəyişə bilər.\n\nAyarlar və Əlçatımlılıq bölməsində bu qısayolu başqa bir funksiyata dəyişə bilərsiniz."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktiv edin"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktiv etməyin"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d saat üçün</item>
<item quantity="one">1 saat üçün</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Bu vaxtadək: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Saat <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> qədər"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> radəsinə qədər (növbəti siqnal)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Deaktiv edənə qədər"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 93ddbe521ac5..0b215456b150 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -243,7 +243,7 @@
<string name="global_action_power_off" msgid="4404936470711393203">"Isključi"</string>
<string name="global_action_power_options" msgid="1185286119330160073">"Napajanje"</string>
<string name="global_action_restart" msgid="4678451019561687074">"Restartuj"</string>
- <string name="global_action_emergency" msgid="1387617624177105088">"Hitni poziv"</string>
+ <string name="global_action_emergency" msgid="1387617624177105088">"Hitan poziv"</string>
<string name="global_action_bug_report" msgid="5127867163044170003">"Izveštaj o grešci"</string>
<string name="global_action_logout" msgid="6093581310002476511">"Završi sesiju"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Snimak ekrana"</string>
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promena audio podešavanja"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Dozvoljava aplikaciji da menja globalna audio podešavanja kao što su jačina zvuka i izbor zvučnika koji se koristi kao izlaz."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audio zapisa"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ova aplikacija može da snima zvuk pomoću mikrofona dok se aplikacija koristi."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"da snima zvuk u pozadini"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može da snima zvuk pomoću mikrofona u bilo kom trenutku."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi na SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji da šalje komande SIM kartici. To je veoma opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičkih aktivnosti"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može da prepozna fizičke aktivnosti."</string>
<string name="permlab_camera" msgid="6320282492904119413">"snimanje fotografija i video snimaka"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ova aplikacija može da snima fotografije i video snimke pomoću kamere u bilo kom trenutku."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ova aplikacija može da snima slike i video snimke pomoću kamere dok se aplikacija koristi."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"da snima slike i video snimke u pozadini"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ova aplikacija može da snima fotografije i video snimke pomoću kamere u bilo kom trenutku."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Dozvolite nekoj aplikaciji ili usluzi da pristupa kamerama sistema da bi snimala slike i video snimke"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova privilegovana sistemska aplikacija može da snima slike i video snimke pomoću kamere sistema u bilo kom trenutku. Aplikacija treba da ima i dozvolu android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dozvolite aplikaciji ili usluzi da dobija povratne pozive o otvaranju ili zatvaranju uređaja sa kamerom."</string>
@@ -835,7 +839,7 @@
<string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pritisnite „Meni“ da biste otključali telefon ili uputite hitan poziv."</string>
<string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pritisnite „Meni“ za otključavanje."</string>
<string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Unesite šablon za otključavanje"</string>
- <string name="lockscreen_emergency_call" msgid="7549683825868928636">"Hitni poziv"</string>
+ <string name="lockscreen_emergency_call" msgid="7549683825868928636">"Hitan poziv"</string>
<string name="lockscreen_return_to_call" msgid="3156883574692006382">"Nazad na poziv"</string>
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Tačno!"</string>
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Probajte ponovo"</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li da koristite prečicu za pristupačnost?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite li da uključite funkcije pristupačnosti?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite da uključite prečicu za funkcije pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključiće se funkcije pristupačnosti. To može da promeni način rada uređaja.\n\nPostojeće funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nMožete da promenite izabrane funkcije u odeljku Podešavanja &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite li da uključite uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite da uključite prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako zadržite oba tastera za jačinu zvuka par sekundi, uključuje se <xliff:g id="SERVICE">%1$s</xliff:g>, funkcija pristupačnosti. To može da promeni način rada uređaja.\n\nMožete da promenite funkciju na koju se odnosi ova prečica u odeljku Podešavanja &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne uključuj"</string>
@@ -1815,7 +1819,7 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Ažurirao je administrator"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Izbrisao je administrator"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Potvrdi"</string>
- <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Da bi se produžilo trajanje baterije, Ušteda baterije:\n\n• uključuje tamnu temu\n• isključuje ili ograničava aktivnosti u pozadini, neke vizuelne efekte i druge funkcije, na primer, „Ok Google“\n\n"<annotation id="url">"Saznajte više"</annotation></string>
+ <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Da bi se produžilo trajanje baterije, Ušteda baterije:\n\n• uključuje tamnu temu\n• isključuje ili ograničava aktivnosti u pozadini, neke vizuelne efekte i druge funkcije, na primer „Ok Google“\n\n"<annotation id="url">"Saznajte više"</annotation></string>
<string name="battery_saver_description" msgid="6794188153647295212">"Da bi se produžilo trajanje baterije, Ušteda baterije:\n\n• uključuje tamnu temu\n• isključuje ili ograničava aktivnosti u pozadini, neke vizuelne efekte i druge funkcije, na primer, „Ok Google“"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Da bi se smanjila potrošnja podataka, Ušteda podataka sprečava neke aplikacije da šalju ili primaju podatke u pozadini. Aplikacija koju trenutno koristite može da pristupa podacima, ali će to činiti ređe. Na primer, slike se neće prikazivati dok ih ne dodirnete."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Želite da uključite Uštedu podataka?"</string>
@@ -1860,6 +1864,7 @@
<item quantity="few">Za %d s</item>
<item quantity="other">Za %d s</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sledeći alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dok ne isključite"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 08aeacf9307e..6a0ad98d759d 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -98,9 +98,9 @@
<string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi-тэлефанія"</string>
<string name="notification_channel_sim" msgid="5098802350325677490">"Статус SIM-карты"</string>
<string name="notification_channel_sim_high_prio" msgid="642361929452850928">"Стан SIM-карты з высокім прыярытэтам"</string>
- <string name="peerTtyModeFull" msgid="337553730440832160">"Аднарангавая прылада запытала рэжым TTY FULL"</string>
- <string name="peerTtyModeHco" msgid="5626377160840915617">"Аднарангавая прылада запытала рэжым TTY НСО"</string>
- <string name="peerTtyModeVco" msgid="572208600818270944">"Аднарангавая прылада запытала рэжым TTY VCO"</string>
+ <string name="peerTtyModeFull" msgid="337553730440832160">"Аднарангавая прылада запытала рэжым поўнафункцыянальнага TTY"</string>
+ <string name="peerTtyModeHco" msgid="5626377160840915617">"Аднарангавая прылада запытала рэжым TTY з магчымасцю чуць суразмоўніка"</string>
+ <string name="peerTtyModeVco" msgid="572208600818270944">"Аднарангавая прылада запытала рэжым TTY з магчымасцю чуць суразмоўніка"</string>
<string name="peerTtyModeOff" msgid="2420380956369226583">"Аднарангавая прылада запытала рэжым TTY OFF"</string>
<string name="serviceClassVoice" msgid="2065556932043454987">"Голас"</string>
<string name="serviceClassData" msgid="4148080018967300248">"Дадзеныя"</string>
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змяняць налады аудыё"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дазваляе прыкладанням змяняць глабальныя налады гуку, такія як моц і тое, што дынамік выкарыстоўваецца для выхаду."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"запіс аўдыя"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Гэта праграма можа у любы час запісваць аўдыя, выкарыстоўваючы мікрафон."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Гэта праграма падчас яе выкарыстання можа запісваць аўдыя з дапамогай мікрафона."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"запісваць аўдыя ў фонавым рэжыме"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Гэта праграма можа ў любы час запісваць аўдыя з дапамогай мікрафона."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"адпраўляць каманды на SIM-карту"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Дазваляе праграме адпраўляць каманды SIM-карце. Гэта вельмі небяспечна."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"распазнаваць фізічную актыўнасць"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Гэта праграма можа распазнаваць фізічную актыўнасць."</string>
<string name="permlab_camera" msgid="6320282492904119413">"рабіць фатаграфіі і відэа"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Гэта праграма можа рабіць фота і запісваць відэа з дапамогай камеры ў любы час."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Гэта праграма падчас яе выкарыстання можа рабіць фота і запісваць відэа з дапамогай камеры."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"знімаць фота і відэа ў фонавым рэжыме"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Гэта праграма можа ў любы час рабіць фота і запісваць відэа з дапамогай камеры."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Дазволіць праграме або сэрвісу атрымліваць доступ да сістэмных камер, каб здымаць фота і запісваць відэа"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Гэта прыярытэтная ці сістэмная праграма можа здымаць фота і запісваць відэа з дапамогай сістэмнай камеры. Праграме таксама патрэбны дазвол android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дазволіць праграме ці сэрвісу атрымліваць зваротныя выклікі наконт адкрыцця ці закрыцця прылад камеры."</string>
@@ -970,7 +974,7 @@
<string name="permlab_addVoicemail" msgid="4770245808840814471">"дадаць галасавое паведамленне"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"Дазваляе прыкладанням дадаваць паведамленні ў вашу скрыню галасавой пошты."</string>
<string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"змяніць дазволы геапазіцыянавання для браўзэра"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Дазваляе прыкладанням змяняць дазволы геалакацыі браўзэра. Шкоднасныя прыкладанні могуць выкарыстоўваць гэта, каб дазваляць адпраўку інфармацыі аб месцазнаходжанні выпадковым вэб-сайтам."</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Дазваляе праграме змяняць дазволы геалакацыі браўзера. Шкодныя праграмы могуць выкарыстоўваць гэта, каб адпраўляць даныя аб месцазнаходжанні на любыя вэб-сайты."</string>
<string name="save_password_message" msgid="2146409467245462965">"Вы хочаце, каб браўзэр запомніў гэты пароль?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"Не зараз"</string>
<string name="save_password_remember" msgid="6490888932657708341">"Запомніць"</string>
@@ -1345,8 +1349,8 @@
<string name="usb_tether_notification_title" msgid="8828527870612663771">"Рэжым USB-мадэма"</string>
<string name="usb_midi_notification_title" msgid="7404506788950595557">"MIDI праз USB"</string>
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB-прылада падключана"</string>
- <string name="usb_notification_message" msgid="4715163067192110676">"Дакраніцеся, каб атрымаць іншыя параметры."</string>
- <string name="usb_power_notification_message" msgid="7284765627437897702">"Зарадка падключанай прылады. Націсніце, каб убачыць іншыя параметры."</string>
+ <string name="usb_notification_message" msgid="4715163067192110676">"Дакраніцеся, каб убачыць іншыя параметры."</string>
+ <string name="usb_power_notification_message" msgid="7284765627437897702">"Падключаная прылада зараджаецца. Дакраніцеся, каб убачыць іншыя параметры."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Выяўлены аксесуар аналагавага аўдыя"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Далучаная прылада не сумяшчальная з гэтым тэлефонам. Націсніце, каб даведацца больш."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"Адладка па USB падключана"</string>
@@ -1542,7 +1546,7 @@
<string name="activitychooserview_choose_application_error" msgid="6937782107559241734">"Не атрымалася запусціць <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
<string name="shareactionprovider_share_with" msgid="2753089758467748982">"Апублікаваць з дапамогай"</string>
<string name="shareactionprovider_share_with_application" msgid="4902832247173666973">"Адправiць з дапамогай прыкладання <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
- <string name="content_description_sliding_handle" msgid="982510275422590757">"Ручка для перасоўвання. Націсніце і ўтрымлівайце."</string>
+ <string name="content_description_sliding_handle" msgid="982510275422590757">"Маркер для перасоўвання. Дакраніцеся і ўтрымлівайце."</string>
<string name="description_target_unlock_tablet" msgid="7431571180065859551">"Прагартайце, каб разблакаваць."</string>
<string name="action_bar_home_description" msgid="1501655419158631974">"Перайсці да пачатковай старонкі"</string>
<string name="action_bar_up_description" msgid="6611579697195026932">"Перайсці ўверх"</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Павялiчыць гук вышэй рэкамендаванага ўзроўню?\n\nДоўгае праслухоўванне музыкi на вялiкай гучнасцi можа пашкодзiць ваш слых."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Выкарыстоўваць камбінацыю хуткага доступу для спецыяльных магчымасцей?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Калі хуткі доступ уключаны, вы можаце націснуць абедзве кнопкі гучнасці і ўтрымліваць іх 3 секунды, каб запусціць функцыю спецыяльных магчымасцей."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Уключыць спецыяльныя магчымасці?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Уключыць хуткі доступ да спецыяльных магчымасцей?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае спецыяльныя магчымасці. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nБягучыя функцыі:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВыбраныя функцыі можна змяніць у меню \"Налады &gt; Спецыяльныя магчымасці\"."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Уключыць службу \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Уключыць хуткі доступ да сэрвісу \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Утрымліванне націснутымі абедзвюх клавіш гучнасці на працягу некалькіх секунд уключае службу \"<xliff:g id="SERVICE">%1$s</xliff:g>\", якая з\'яўляецца спецыяльнай магчымасцю. У выніку ваша прылада можа пачаць працаваць па-іншаму.\n\nВы можаце задаць гэта спалучэнне клавіш для іншай функцыі ў меню \"Налады &gt; Спецыяльныя магчымасці\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Уключыць"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не ўключаць"</string>
@@ -1891,6 +1895,7 @@
<item quantity="many">На %d гадз</item>
<item quantity="other">На %d гадз</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Да <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Да <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Да <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (наступны будзільнік)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Пакуль не выключыце"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index e49ae26a93ac..cdd0ec04df0f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промяна на настройките ви за звука"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Разрешава на приложението да променя глобалните настройки за звука, като например силата и това, кой високоговорител се използва за изход."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"записва звук"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Това приложение може по всяко време да записва звук посредством микрофона."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Когато се използва, това приложение може да записва аудио посредством микрофона."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записва аудио на заден план"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Това приложение може по всяко време да записва аудио посредством микрофона."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"изпращане на команди до SIM картата"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Разрешава на приложението да изпраща команди до SIM картата. Това е много опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"разпознаване на физическата активност"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Това приложение може да разпознава физическата ви активност."</string>
<string name="permlab_camera" msgid="6320282492904119413">"правене на снимки и видеоклипове"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Това приложение може по всяко време да прави снимки и да записва видео посредством камерата."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Когато се използва, това приложение може да прави снимки и да записва видеоклипове посредством камерата."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"прави снимки и видеоклипове на заден план"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Това приложение може по всяко време да прави снимки и да записва видеоклипове посредством камерата."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Разрешаване на достъп на приложение или услуга до системните камери с цел правене на снимки и видеоклипове"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Това привилегировано или системно приложение може по всяко време да прави снимки и да записва видео посредством системна камера. Необходимо е също на приложението да бъде дадено разрешението android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Разрешаване на приложение или услуга да получават обратни повиквания за отварянето или затварянето на снимачни устройства."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да се увеличи ли силата на звука над препоръчителното ниво?\n\nПродължителното слушане при висока сила на звука може да увреди слуха ви."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Искате ли да използвате пряк път към функцията за достъпност?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Когато прекият път е включен, можете да стартирате дадена функция за достъпност, като натиснете двата бутона за силата на звука и ги задържите за 3 секунди."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Включване на функциите за достъпност?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Искате ли да включите прекия път за функциите за достъпност?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функциите за достъпност. Това може да промени начина, по който работи устройството ви.\n\nТекущи функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените избраните функции от „Настройки“ &gt; „Достъпност“."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Да се включи ли <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Искате ли да включите прекия път за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Натиснете двата бутона за силата на звука и ги задръжте за няколко секунди, за да включите функцията за достъпност <xliff:g id="SERVICE">%1$s</xliff:g>. Това може да промени начина, по който работи устройството ви.\n\nМожете да зададете друга функция за този пряк път от „Настройки“ &gt; „Достъпност“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включване"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Без включване"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">За %d ч</item>
<item quantity="one">За 1 ч</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"До следващия будилник (<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"До изключване"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 22589cd00b06..779fd3ef67e1 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"আপনার অডিও সেটিংস পরিবর্তন করে"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ভলিউম এবং যেখানে স্পিকার আউটপুট হিসাবে ব্যবহৃত হয় সেই সব ক্ষেত্রে গ্লোবাল অডিও সেটিংসের সংশোধন করতে অ্যাপ্লিকেশনটিকে মঞ্জুর করে৷"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"অডিও রেকর্ড"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"এই অ্যাপটি মাইক্রোফোন ব্যবহার করে যে কোনো সময় অডিও রেকর্ড করতে পারে৷"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"এই অ্যাপটি যখন ব্যবহার করা হচ্ছে, তখন মাইক্রোফোন ব্যবহার করে অডিও রেকর্ড করতে পারবে।"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ব্যাকগ্রাউন্ডে অডিও রেকর্ড করতে পারবে"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"এই অ্যাপটি মাইক্রোফোন ব্যবহার করে যেকোনও সময় অডিও রেকর্ড করতে পারবে।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"সিম এ আদেশগুলি পাঠান"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"অ্যাপ্লিকেশানটিকে সিম কার্ডে কমান্ডগুলি পাঠানোর অনুমতি দেয়৷ এটি খুবই বিপজ্জনক৷"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"শারীরিক অ্যাক্টিভিটি শনাক্ত করুন"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"এই অ্যাপ আপনার শারীরিক অ্যাক্টিভিটি শনাক্ত করতে পারবে।"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ছবি এবং ভিডিও তোলে"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"এই অ্যাপটি যে কোনো সময় ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারে৷"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"এই অ্যাপটি যখন ব্যবহার করা হচ্ছে, তখন ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারবে।"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ব্যাকগ্রাউন্ডে ছবি এবং ভিডিও তুলতে পারবে"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"এই অ্যাপটি যেকোনও সময় ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারবে।"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"সিস্টেম ক্যামেরা ব্যবহার করে ফটো এবং ভিডিও নেওয়ার জন্য অ্যাপ্লিকেশন বা পরিষেবা অ্যাক্সেসের অনুমতি দিন"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"এই প্রিভিলেজ বা সিস্টেম অ্যাপ যেকোনও সময় সিস্টেম ক্যামেরা ব্যবহার করে ছবি তুলতে ও ভিডিও রেকর্ড করতে পারে। এই অ্যাপকে android.permission.CAMERA অনুমতি দিতে হবে"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"কোনও অ্যাপ্লিকেশন বা পরিষেবাকে ক্যামেরা ডিভাইসগুলি খোলা বা বন্ধ হওয়া সম্পর্কে কলব্যাকগুলি গ্রহণ করার অনুমতি দিন।"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"প্রস্তাবিত স্তরের চেয়ে বেশি উঁচুতে ভলিউম বাড়াবেন?\n\nউঁচু ভলিউমে বেশি সময় ধরে কিছু শুনলে আপনার শ্রবনশক্তির ক্ষতি হতে পারে।"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"অ্যাক্সেসযোগ্যতা শর্টকাট ব্যবহার করবেন?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"শর্টকাট চালু করা থাকাকালীন দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি ফিচার চালু হবে।"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"অ্যাক্সেসিবিলিটি ফিচার চালু করতে চান?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"অ্যাক্সেসিবিলিটি ফিচারের শর্টকাট বন্ধ করতে চান?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে অ্যাক্সেসিবিলিটি ফিচার চালু হয়ে যাবে। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nবর্তমান ফিচার:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nসেটিংস &gt; অ্যাক্সেসিবিলিটি বিকল্প থেকে আপনি বাছাই করা ফিচার পরিবর্তন করতে পারবেন।"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> চালু করতে চান?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> শর্টকাট চালু করতে চান?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"উভয় ভলিউম কী কয়েক সেকেন্ড ধরে থাকলে <xliff:g id="SERVICE">%1$s</xliff:g> চালু হয়ে যাবে। এটি একটি অ্যাক্সেসিবিলিটি ফিচার। এর ফলে, আপনার ডিভাইস কীভাবে কাজ করবে সেটিতে পরিবর্তন হতে পারে।\n\nসেটিংস &gt; অ্যাক্সেসিবিলিটি থেকে আপনি এই শর্টকাট পরিবর্তন করতে পারবেন।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"চালু করুন"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"চালু করবেন না"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d ঘন্টার জন্য</item>
<item quantity="other">%d ঘন্টার জন্য</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> পর্যন্ত (পরবর্তী অ্যালার্ম)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"যতক্ষণ না আপনি বন্ধ করছেন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 755ee472afb3..fab6c912d3fe 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"izmjene postavki zvuka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Omogućava aplikaciji izmjenu općih postavki zvuka, kao što su jačina zvuka i izbor izlaznog zvučnika."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje audiozapisa"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ova aplikacija može u svakom trenutku snimati zvuk koristeći mikrofon."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Za vrijeme korištenja, ova aplikacija može snimati zvuk koristeći mikrofon."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimanje zvuka u pozadini"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ova aplikacija može u svakom trenutku snimati zvuk koristeći mikrofon."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"slanje komandi SIM kartici"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućava aplikaciji slanje naredbi na SIM. Ovo je vrlo opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje fizičke aktivnosti"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može prepoznati vašu fizičku aktivnost."</string>
<string name="permlab_camera" msgid="6320282492904119413">"snimanje slika i videozapisa"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ova aplikacija može slikati fotografije i snimati videozapise koristeći kameru bilo kada."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Za vrijeme korištenja, ova aplikacija može snimati slike i videozapise koristeći kameru."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"snimanje slika i videozapisa u pozadini"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ova aplikacija može snimati slike i videozapise koristeći kameru bilo kada."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Dopustite aplikaciji ili usluzi da pristupa kamerama sistema radi snimanja fotografija i videozapisa"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova povlaštena ili sistemska aplikacija u svakom trenutku može snimati fotografije i videozapise pomoću kamere sistema. Aplikacija također mora imati odobrenje android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dozvoliti aplikaciji ili usluzi da prima povratne pozive o otvaranju ili zatvaranju kamera."</string>
@@ -593,7 +597,7 @@
<string name="face_acquired_not_detected" msgid="2945945257956443257">"Postavite lice direktno ispred telefona"</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Previše pokreta. Držite telefon mirno."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Ponovo registrirajte lice."</string>
- <string name="face_acquired_too_different" msgid="4699657338753282542">"Nije više moguće prepoznati lice. Pokušajte opet."</string>
+ <string name="face_acquired_too_different" msgid="4699657338753282542">"Više nije moguće prepoznati lice. Pokušajte opet."</string>
<string name="face_acquired_too_similar" msgid="7684650785108399370">"Previše slično, promijenite položaj."</string>
<string name="face_acquired_pan_too_extreme" msgid="7822191262299152527">"Malo manje zakrenite glavu."</string>
<string name="face_acquired_tilt_too_extreme" msgid="8119978324129248059">"Malo manje zakrenite glavu."</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučenog nivoa?\n\nDužim slušanjem glasnog zvuka možete oštetiti sluh."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li koristiti Prečicu za pristupačnost?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kada je prečica uključena, pritiskom i držanjem oba dugmeta za jačinu zvuka u trajanju od 3 sekunde pokrenut će se funkcija pristupačnosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Uključiti funkcije pristupačnosti?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Uključiti prečicu za funkcije pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkcije pristupačnosti. Ovo može uticati na način rada uređaja.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane funkcije možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Uključiti <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Uključiti prečicu za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako nekoliko sekundi držite pritisnute obje tipke za jačinu zvuka, uključit ćete funkciju pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Ovo može promijeniti način rada uređaja.\n\nOvu prečicu možete zamijeniti drugom funkcijom u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nemoj uključiti"</string>
@@ -1860,6 +1864,7 @@
<item quantity="few">%d sata</item>
<item quantity="other">%d sati</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dok ne isključite"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 4f55bf51c826..96c6b6a85736 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"canviar la configuració d\'àudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar àudio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aquesta aplicació pot gravar àudio amb el micròfon mentre s\'està utilitzant."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar àudio en segon pla"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar ordres a la SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconèixer l\'activitat física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aquesta aplicació pot reconèixer la teva activitat física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"fer fotos i vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera en qualsevol moment."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera mentre s\'està utilitzant."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"fer fotos i gravar vídeos en segon pla"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera en qualsevol moment."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permet que una aplicació o un servei tinguin accés a les càmeres del sistema per fer fotos i vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Aquesta aplicació del sistema amb privilegis pot fer fotos i gravar vídeos amb una càmera del sistema en qualsevol moment. L\'aplicació també ha de tenir el permís android.permission.CAMERA per accedir-hi."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permet que una aplicació o un servei pugui rebre crides de retorn sobre els dispositius de càmera que s\'obren o es tanquen."</string>
@@ -991,8 +995,8 @@
<string name="searchview_description_submit" msgid="6771060386117334686">"Envia la consulta"</string>
<string name="searchview_description_voice" msgid="42360159504884679">"Cerca per veu"</string>
<string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Vols activar l\'exploració tàctil?"</string>
- <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interactuar amb la tauleta."</string>
- <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració per tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interactuar amb el telèfon."</string>
+ <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interaccionar amb la tauleta."</string>
+ <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració per tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interaccionar amb el telèfon."</string>
<string name="oneMonthDurationPast" msgid="4538030857114635777">"Fa 1 mes"</string>
<string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Fa més d\'1 mes"</string>
<plurals name="last_num_days" formatted="false" msgid="687443109145393632">
@@ -1224,7 +1228,7 @@
<string name="volume_music_hint_silent_ringtone_selected" msgid="1514829655029062233">"S\'ha establert el so de silenci"</string>
<string name="volume_call" msgid="7625321655265747433">"Volum en trucada"</string>
<string name="volume_bluetooth_call" msgid="2930204618610115061">"Volum en trucada per Bluetooth"</string>
- <string name="volume_alarm" msgid="4486241060751798448">"Volum de l\'alarma"</string>
+ <string name="volume_alarm" msgid="4486241060751798448">"Volum d\'alarma"</string>
<string name="volume_notification" msgid="6864412249031660057">"Volum de notificacions"</string>
<string name="volume_unknown" msgid="4041914008166576293">"Volum"</string>
<string name="volume_icon_description_bluetooth" msgid="7540388479345558400">"Volum del Bluetooth"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vols apujar el volum per sobre del nivell recomanat?\n\nSi escoltes música a un volum alt durant períodes llargs, pots danyar-te l\'oïda."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vols fer servir la drecera d\'accessibilitat?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si la drecera està activada, prem els dos botons de volum durant 3 segons per iniciar una funció d\'accessibilitat."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vols activar les funcions d\'accessibilitat?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vols desactivar la drecera de les funcions d\'accessibilitat?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantens premudes les dues tecles de volum durant uns segons, s\'activaran les funcions d\'accessibilitat. Això podria canviar el funcionament del teu dispositiu.\n\nFuncions actuals:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPots canviar les funcions seleccionades a Configuració &gt; Accessibilitat."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vols activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vols activar la drecera de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantens premudes les dues tecles de volum durant uns segons, la funció d\'accessibilitat <xliff:g id="SERVICE">%1$s</xliff:g> s\'activarà. Això podria canviar el funcionament del teu dispositiu.\n\nPots canviar la funció d\'aquesta drecera a Configuració &gt; Accessibilitat."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activa"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activis"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Durant %d h</item>
<item quantity="one">Durant 1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Finalitza: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (propera alarma)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Fins que no el desactivis"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index fcf9b837cfa1..9a5f069c8afb 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"změna nastavení zvuku"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či reproduktor pro výstup zvuku."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávání zvuku"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Tato aplikace může pomocí mikrofonu kdykoli zaznamenat zvuk."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Tato aplikace může pomocí mikrofonu během svého používání zaznamenat zvuk."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"zaznamenávat zvuk na pozadí"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tato aplikace může pomocí mikrofonu kdykoli zaznamenat zvuk."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"odesílání příkazů do SIM karty"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikaci odesílat příkazy na kartu SIM. Toto oprávnění je velmi nebezpečné."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávání fyzické aktivity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Tyto aplikace dokážou rozpoznat vaši fyzickou aktivitu."</string>
<string name="permlab_camera" msgid="6320282492904119413">"pořizování fotografií a videí"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Tato aplikace může pomocí fotoaparátu kdykoli pořídit snímek nebo nahrát video."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Tato aplikace může pomocí fotoaparátu během svého používání pořídit snímek nebo nahrát video."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"pořizovat snímky a nahrávat videa na pozadí"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Tato aplikace může pomocí fotoaparátu kdykoli pořídit snímek nebo nahrát video."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Povolte aplikaci nebo službě k systémovým fotoaparátům za účelem pořizování fotek a videí"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Tato privilegovaná nebo systémová aplikace může pomocí fotoaparátu kdykoli pořídit snímek nebo nahrát video. Aplikace musí zároveň mít oprávnění android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Povolte aplikaci nebo službě přijímat zpětná volání o otevření nebo zavření zařízení s fotoaparátem."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšit hlasitost nad doporučenou úroveň?\n\nDlouhodobý poslech hlasitého zvuku může poškodit sluch."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použít zkratku přístupnosti?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Když je tato zkratka zapnutá, můžete funkci přístupnosti spustit tím, že na tři sekundy podržíte obě tlačítka hlasitosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Zapnout funkce pro usnadnění přístupu?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Zapnout zkratku funkcí pro usnadnění přístupu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkce pro usnadnění přístupu. Tato funkce může změnit fungování zařízení.\n\nAktuální funkce:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkce můžete změnit v Nastavení &gt; Přístupnost."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Zapnout <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Zapnout zkratku služby <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Podržením obou tlačítek hlasitosti po dobu několika sekund zapnete funkci pro usnadnění přístupu <xliff:g id="SERVICE">%1$s</xliff:g>. Tato funkce může změnit fungování zařízení.\n\nZkratku můžete nastavit na jinou funkci v Nastavení &gt; Přístupnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnout"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nezapínat"</string>
@@ -1891,6 +1895,7 @@
<item quantity="other">%d h</item>
<item quantity="one">1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (příští budík)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dokud tuto funkci nevypnete"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 0b7e396839ef..480e87cfff2a 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"skifte dine lydindstillinger"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillader, at appen kan ændre globale lydindstillinger, som f.eks. lydstyrke og hvilken højttaler der bruges til output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"optage lyd"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Denne app kan til enhver tid optage lyd via mikrofonen."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne app kan optage lyd med mikrofonen, mens appen er i brug."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"optag lyd i baggrunden"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne app kan optage lyd med mikrofonen når som helst."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send kommandoer til SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillader, at appen sender kommandoer til SIM-kortet. Dette er meget farligt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"genkend fysisk aktivitet"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Denne app kan genkende din fysiske aktivitet."</string>
<string name="permlab_camera" msgid="6320282492904119413">"tage billeder og optage video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Med denne app kan du tage billeder og optage video med kameraet når som helst."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Denne app kan tage billeder og optage video med kameraet, mens appen er i brug."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"tag billeder, og optag video i baggrunden"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Med denne app kan du tage billeder og optage video med kameraet når som helst."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Giv en app eller tjeneste adgang til systemkameraer for at tage billeder og optage video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Denne privilegerede app eller systemapp kan til enhver tid tage billeder og optage video med et systemkamera. Appen skal også have tilladelsen android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillad, at en app eller tjeneste modtager tilbagekald om kameraenheder, der åbnes eller lukkes."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du skrue højere op end det anbefalede lydstyrkeniveau?\n\nDu kan skade hørelsen ved at lytte til meget høj musik over længere tid."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruge genvejen til Hjælpefunktioner?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når genvejen er aktiveret, kan du starte en hjælpefunktion ved at trykke på begge lydstyrkeknapper i tre sekunder."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vil du aktivere hjælpefunktionerne?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du aktivere genvejen til hjælpefunktioner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionerne. Det kan ændre på, hvordan din enhed fungerer.\n\nAktuelle funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ændre de valgte funktioner i Indstillinger &gt; Hjælpefunktioner."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vil du aktivere <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du aktivere <xliff:g id="SERVICE">%1$s</xliff:g>-genvejen?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder begge lydstyrkeknapperne nede i et par sekunder, aktiveres hjælpefunktionen <xliff:g id="SERVICE">%1$s</xliff:g>. Det kan ændre på, hvordan din enhed fungerer.\n\nDu kan ændre denne genvej til en anden funktion i Indstillinger &gt; Hjælpefunktioner."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivér"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktivér ikke"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">I %d t.</item>
<item quantity="other">I %d t.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Indtil <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næste alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Indtil du deaktiverer"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index faa78df458e0..ff8e6ad43252 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Audio-Einstellungen ändern"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"Audio aufnehmen"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Diese App kann jederzeit Audio über das Mikrofon aufnehmen."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Diese App darf mit dem Mikrofon Audioaufnahmen machen, solange sie verwendet wird."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioaufnahmen im Hintergrund machen"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Diese App darf mit dem Mikrofon jederzeit Audioaufnahmen machen."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"Befehle an die SIM senden"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ermöglicht der App das Senden von Befehlen an die SIM-Karte. Dies ist äußerst risikoreich."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"Körperliche Aktivitäten erkennen"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Diese App kann deine körperliche Aktivität erkennen."</string>
<string name="permlab_camera" msgid="6320282492904119413">"Bilder und Videos aufnehmen"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Diese App kann mit der Kamera jederzeit Bilder und Videos aufnehmen."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Diese App darf mit der Kamera Bilder und Videos aufnehmen, solange die App verwendet wird."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Bilder und Videos im Hintergrund aufnehmen"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Diese App darf mit der Kamera jederzeit Bilder und Videos aufnehmen."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufnehmen zu können"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Diese privilegierte App oder System-App kann jederzeit mit einer Systemkamera Bilder und Videos aufnehmen. Die App benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Einer App oder einem Dienst den Empfang von Callbacks erlauben, wenn eine Kamera geöffnet oder geschlossen wird."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lautstärke über den Schwellenwert anheben?\n\nWenn du über einen längeren Zeitraum Musik in hoher Lautstärke hörst, kann dies dein Gehör schädigen."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Verknüpfung für Bedienungshilfen verwenden?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Wenn die Verknüpfung aktiviert ist, kannst du die beiden Lautstärketasten drei Sekunden lang gedrückt halten, um eine Bedienungshilfe zu starten."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bedienungshilfen aktivieren?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Verknüpfung für Bedienungshilfen aktivieren?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfen. Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nAktuelle Funktionen:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kannst ausgewählte Funktionen unter \"Einstellungen\" &gt; \"Bedienungshilfen\" ändern."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Verknüpfung für <xliff:g id="SERVICE">%1$s</xliff:g> aktivieren?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Wenn du beide Lautstärketasten einige Sekunden lang gedrückt hältst, aktivierst du die Bedienungshilfe \"<xliff:g id="SERVICE">%1$s</xliff:g>\". Dadurch kann sich die Funktionsweise deines Geräts ändern.\n\nUnter \"Einstellungen &gt; \"Bedienungshilfen\" kannst du dieser Verknüpfung eine andere Funktion zuweisen."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivieren"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nicht aktivieren"</string>
@@ -1792,8 +1796,8 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Von deinem Administrator aktualisiert"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Von deinem Administrator gelöscht"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
- <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Der Energiesparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt\n\n"<annotation id="url">"Weitere Informationen"</annotation></string>
- <string name="battery_saver_description" msgid="6794188153647295212">"Der Energiesparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt"</string>
+ <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Der Stromsparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt\n\n"<annotation id="url">"Weitere Informationen"</annotation></string>
+ <string name="battery_saver_description" msgid="6794188153647295212">"Der Stromsparmodus sorgt für eine längere Akkulaufzeit:\n\n• Das dunkle Design wird aktiviert\n• Hintergrundaktivitäten, einige optische Effekte und weitere Funktionen wie \"Ok Google\" werden abgeschaltet oder eingeschränkt"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Der Datensparmodus verhindert zum einen, dass manche Apps im Hintergrund Daten senden oder empfangen, sodass weniger Daten verbraucht werden. Zum anderen werden die Datenzugriffe der gerade aktiven App eingeschränkt, was z. B. dazu führen kann, dass Bilder erst angetippt werden müssen, bevor sie sichtbar werden."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Datensparmodus aktivieren?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Aktivieren"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Für %d h</item>
<item quantity="one">Für 1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Bis <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nächste Weckzeit)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Bis zur Deaktivierung"</string>
@@ -2000,9 +2005,9 @@
<string name="notification_feedback_indicator" msgid="663476517711323016">"Feedback geben"</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Infomitteilung zum Ablaufmodus"</string>
<string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Dein Akku könnte vor der gewöhnlichen Ladezeit leer sein"</string>
- <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Energiesparmodus aktiviert, um die Akkulaufzeit zu verlängern"</string>
- <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Energiesparmodus"</string>
- <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Energiesparmodus deaktiviert"</string>
+ <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Stromsparmodus aktiviert, um die Akkulaufzeit zu verlängern"</string>
+ <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Stromsparmodus"</string>
+ <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Stromsparmodus deaktiviert"</string>
<string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Das Smartphone ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string>
<string name="battery_saver_charged_notification_summary" product="tablet" msgid="4426317048139996888">"Das Tablet ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string>
<string name="battery_saver_charged_notification_summary" product="device" msgid="1031562417867646649">"Das Gerät ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 2e1b56ad9ff9..6f03f80e04cc 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"αλλάζει τις ρυθμίσεις ήχου"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Επιτρέπει στην εφαρμογή την τροποποίηση καθολικών ρυθμίσεων ήχου, όπως η ένταση και ποιο ηχείο χρησιμοποιείται για έξοδο."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"εγγράφει ήχο"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Αυτή η εφαρμογή μπορεί να κάνει εγγραφή ήχου χρησιμοποιώντας το μικρόφωνο, ανά πάσα στιγμή."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, όταν τη χρησιμοποιείτε."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"εγγραφή ήχου στο παρασκήνιο"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Αυτή η εφαρμογή μπορεί να εγγράφει ήχο μέσω του μικροφώνου, ανά πάσα στιγμή."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"στέλνει εντολές στην κάρτα SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Επιτρέπει στην εφαρμογή την αποστολή εντολών στην κάρτα SIM. Αυτό είναι εξαιρετικά επικίνδυνο."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"αναγνώριση σωματικής δραστηριότητας"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Αυτή η εφαρμογή μπορεί να αναγνωρίσει τη σωματική σας δραστηριότητα."</string>
<string name="permlab_camera" msgid="6320282492904119413">"κάνει λήψη φωτογραφιών και βίντεο"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Αυτή η εφαρμογή μπορεί να τραβήξει φωτογραφίες και βίντεο χρησιμοποιώντας την κάμερα, ανά πάσα στιγμή."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Αυτή η εφαρμογή μπορεί να τραβάει φωτογραφίες και να εγγράφει βίντεο μέσω της κάμερας, όταν τη χρησιμοποιείτε."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"λήψη φωτογραφιών και βίντεο στο παρασκήνιο"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Αυτή η εφαρμογή μπορεί να τραβάει φωτογραφίες και να εγγράφει βίντεο μέσω της κάμερας, ανά πάσα στιγμή."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Παραχωρήστε σε μια εφαρμογή ή υπηρεσία πρόσβαση στις κάμερες του συστήματος για τη λήψη φωτογραφιών και βίντεο"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Αυτή η προνομιακή εφαρμογή ή εφαρμογή συστήματος μπορεί να τραβάει φωτογραφίες και να εγγράφει βίντεο, χρησιμοποιώντας μια κάμερα του συστήματος ανά πάσα στιγμή. Απαιτείται, επίσης, η εφαρμογή να έχει την άδεια android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Επιτρέψτε σε μια εφαρμογή ή μια υπηρεσία να λαμβάνει επανάκλησεις σχετικά με το άνοιγμα ή το κλείσιμο συσκευών κάμερας."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Αυξάνετε την ένταση ήχου πάνω από το επίπεδο ασφαλείας;\n\nΑν ακούτε μουσική σε υψηλή ένταση για μεγάλο χρονικό διάστημα ενδέχεται να προκληθεί βλάβη στην ακοή σας."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Να χρησιμοποιείται η συντόμευση προσβασιμότητας;"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Όταν η συντόμευση είναι ενεργοποιημένη, το πάτημα και των δύο κουμπιών έντασης ήχου για 3 δευτερόλεπτα θα ξεκινήσει μια λειτουργία προσβασιμότητας."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Θέλετε να ενεργοποιήσετε τις λειτουργίες προσβασιμότητας;"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ενεργοποίηση συντόμευσης για λειτουργίες προσβασιμότητας;"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Για να ενεργοποιήσετε τις λειτουργίες προσβασιμότητας, πατήστε παρατεταμένα τα δύο πλήκτρα έντασης για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΤρέχουσες λειτουργίες:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nΜπορείτε να αλλάξετε τις επιλεγμένες λειτουργίες στις Ρυθμίσεις &gt; Προσβασιμότητα."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Θέλετε να ενεργοποιήσετε τη λειτουργία <xliff:g id="SERVICE">%1$s</xliff:g>;"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ενεργοποίηση συντόμευσης <xliff:g id="SERVICE">%1$s</xliff:g>;"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Μπορείτε να ενεργοποιήσετε τη λειτουργία <xliff:g id="SERVICE">%1$s</xliff:g>, η οποία είναι μία από τις λειτουργίες προσβασιμότητας, πατώντας παρατεταμένα ταυτόχρονα τα δύο πλήκτρα έντασης ήχου για μερικά δευτερόλεπτα. Αυτό ενδέχεται να αλλάξει τον τρόπο λειτουργίας της συσκευής σας.\n\nΜπορείτε να αλλάξετε αυτή τη συντόμευση σε μια άλλη λειτουργία στις Ρυθμίσεις &gt; Προσβασιμότητα."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ενεργοποίηση"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Να μην ενεργοποιηθούν"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Για %d ώρες</item>
<item quantity="one">Για 1 ώρα</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Έως <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Έως τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Μέχρι τις <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (επόμενο ξυπνητήρι)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Μέχρι την απενεργοποίηση"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index dcfbf5a49748..fac2f2223c99 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">For %d hr</item>
<item quantity="one">For 1 hr</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 65f2426b6e08..8497658c52bb 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
@@ -1123,8 +1127,8 @@
<string name="loading" msgid="3138021523725055037">"Loading…"</string>
<string name="capital_on" msgid="2770685323900821829">"ON"</string>
<string name="capital_off" msgid="7443704171014626777">"OFF"</string>
- <string name="checked" msgid="9179896827054513119">"ticked"</string>
- <string name="not_checked" msgid="7972320087569023342">"not ticked"</string>
+ <string name="checked" msgid="9179896827054513119">"checked"</string>
+ <string name="not_checked" msgid="7972320087569023342">"not checked"</string>
<string name="whichApplication" msgid="5432266899591255759">"Complete action using"</string>
<string name="whichApplicationNamed" msgid="6969946041713975681">"Complete action using %1$s"</string>
<string name="whichApplicationLabel" msgid="7852182961472531728">"Complete action"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">For %d hr</item>
<item quantity="one">For 1 hr</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
@@ -2024,7 +2029,7 @@
<string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> spreadsheet"</string>
<string name="mime_type_presentation" msgid="1145384236788242075">"Presentation"</string>
<string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> presentation"</string>
- <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth will stay on during aeroplane mode"</string>
+ <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth will stay on in Airplane mode"</string>
<string name="car_loading_profile" msgid="8219978381196748070">"Loading"</string>
<plurals name="file_count" formatted="false" msgid="7063513834724389247">
<item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> files</item>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index d38e2fe844ad..12881e7e70f1 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">For %d hr</item>
<item quantity="one">For 1 hr</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 30a9bf88619d..cb2eb7ab17ca 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Allows the app to modify global audio settings such as volume and which speaker is used for output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"record audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"This app can record audio using the microphone while the app is in use."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"record audio in the background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Turn on accessibility features?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Turn on <xliff:g id="SERVICE">%1$s</xliff:g> shortcut?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Holding down both volume keys for a few seconds turns on <xliff:g id="SERVICE">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Turn on"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Don’t turn on"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">For %d hr</item>
<item quantity="one">For 1 hr</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 6503f7ce65a7..5750e50476b8 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎change your audio settings‎‏‎‎‏‎"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎Allows the app to modify global audio settings such as volume and which speaker is used for output.‎‏‎‎‏‎"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎record audio‎‏‎‎‏‎"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎This app can record audio using the microphone at any time.‎‏‎‎‏‎"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎This app can record audio using the microphone while the app is in use.‎‏‎‎‏‎"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎record audio in the background‎‏‎‎‏‎"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎This app can record audio using the microphone at any time.‎‏‎‎‏‎"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎send commands to the SIM‎‏‎‎‏‎"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎Allows the app to send commands to the SIM. This is very dangerous.‎‏‎‎‏‎"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎recognize physical activity‎‏‎‎‏‎"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎This app can recognize your physical activity.‎‏‎‎‏‎"</string>
<string name="permlab_camera" msgid="6320282492904119413">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎take pictures and videos‎‏‎‎‏‎"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎This app can take pictures and record videos using the camera at any time.‎‏‎‎‏‎"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎This app can take pictures and record videos using the camera while the app is in use.‎‏‎‎‏‎"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‎take pictures and videos in the background‎‏‎‎‏‎"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‎This app can take pictures and record videos using the camera at any time.‎‏‎‎‏‎"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎Allow an application or service access to system cameras to take pictures and videos‎‏‎‎‏‎"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well‎‏‎‎‏‎"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‏‏‎Allow an application or service to receive callbacks about camera devices being opened or closed.‎‏‎‎‏‎"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎Raise volume above recommended level?‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Listening at high volume for long periods may damage your hearing.‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎Use Accessibility Shortcut?‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.‎‏‎‎‏‎"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎Turn on accessibility features?‎‏‎‎‏‎"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎Turn on shortcut for accessibility features?‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Current features:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can change selected features in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‎‏‏‎‎‏‎Turn on ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎Turn on ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ shortcut?‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‎‎‏‎Holding down both volume keys for a few seconds turns on ‎‏‎‎‏‏‎<xliff:g id="SERVICE">%1$s</xliff:g>‎‏‎‎‏‏‏‎, an accessibility feature. This may change how your device works.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎You can change this shortcut to another feature in Settings &gt; Accessibility.‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎Turn on‎‏‎‎‏‎"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎Don’t turn on‎‏‎‎‏‎"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎For %d hr‎‏‎‎‏‎</item>
<item quantity="one">‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎For 1 hr‎‏‎‎‏‎</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎Until ‎‏‎‎‏‏‎<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‎Until ‎‏‎‎‏‏‎<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎Until ‎‏‎‎‏‏‎<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (next alarm)‎‏‎‎‏‎"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎Until you turn off‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 2308b5a521bd..8e7d1a33b98d 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta app puede grabar audio con el micrófono en cualquier momento."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app puede grabar audio con el micrófono mientras está en uso."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"grabar audio en segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app puede grabar audio con el micrófono en cualquier momento."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Usar este permiso es peligroso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta app puede reconocer tu actividad física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"tomar fotografías y grabar videos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Esta app puede tomar fotos y grabar videos con la cámara en cualquier momento."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Esta app puede tomar fotos y grabar videos con la cámara mientras está en uso."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"capturar fotos y videos en segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Esta app puede tomar fotos y grabar videos con la cámara en cualquier momento."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que una aplicación o un servicio accedan a las cámaras del sistema para tomar fotos y grabar videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta app del sistema o con privilegios puede tomar fotografías y grabar videos con una cámara del sistema en cualquier momento. Para ello, requiere tener el permiso android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permite que una aplicación o un servicio reciba devoluciones de llamada cuando se abren o cierran dispositivos de cámara."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar a un alto volumen durante largos períodos puede dañar tu audición."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Usar acceso directo de accesibilidad?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cuando la combinación de teclas está activada, puedes presionar los botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Quieres activar las funciones de accesibilidad?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar la combinación de teclas para las funciones de accesibilidad?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si mantienes presionadas las dos teclas de volumen durante unos segundos, se activarán las funciones de accesibilidad. Esto puede cambiar el funcionamiento de tu dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Configuración &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"¿Quieres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo de <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si mantienes presionadas ambas teclas de volumen durante unos segundos, se activará la función de accesibilidad <xliff:g id="SERVICE">%1$s</xliff:g>. Esto podría cambiar la forma en que funciona tu dispositivo.\n\nPuedes cambiar este acceso directo a otra función en Configuración &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Durante %d horas</item>
<item quantity="one">Durante 1 hora</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Hasta la(s) <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Hasta la hora <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Hasta que lo desactives"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 67350cb04fe1..b9f0f584dc82 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -327,7 +327,7 @@
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controla el posicionamiento y el nivel de zoom de la pantalla."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Realizar gestos"</string>
<string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Puedes tocar y pellizcar la pantalla, deslizar el dedo y hacer otros gestos."</string>
- <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de huellas digitales"</string>
+ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos de huella digital"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Puede capturar los gestos realizados en el sensor de huellas digitales del dispositivo."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Hacer captura"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede hacer capturas de la pantalla."</string>
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar la configuración de audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global (por ejemplo, el volumen y el altavoz de salida)."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar sonido"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta aplicación puede grabar audio con el micrófono en cualquier momento."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta aplicación puede grabar audio con el micrófono mientras la estés usando."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Grabar audio en segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación puede grabar audio con el micrófono en cualquier momento."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos a la tarjeta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que la aplicación envíe comandos a la tarjeta SIM. Este permiso es muy peligroso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconocer actividad física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta aplicación puede reconocer tu actividad física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"realizar fotografías y vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Esta aplicación puede hacer fotografías y grabar vídeos con la cámara en cualquier momento."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Esta aplicación puede hacer fotografías y grabar vídeos con la cámara mientras la estés usando."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Hacer fotografías y grabar vídeos en segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Esta aplicación puede hacer fotografías y grabar vídeos con la cámara en cualquier momento."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que una aplicación o servicio acceda a las cámaras del sistema para hacer fotos y vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta aplicación del sistema o con privilegios puede hacer fotos y grabar vídeos en cualquier momento con una cámara del sistema, aunque debe tener también el permiso android.permission.CAMERA para hacerlo"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que una aplicación o servicio reciba retrollamadas cada vez que se abra o cierre una cámara."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar sonidos fuertes durante mucho tiempo puede dañar los oídos."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Utilizar acceso directo de accesibilidad?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Si el acceso directo está activado, pulsa los dos botones de volumen durante 3 segundos para iniciar una función de accesibilidad."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"¿Activar funciones de accesibilidad?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"¿Quieres activar el acceso directo a las funciones de accesibilidad?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Al mantener pulsadas las dos teclas de volumen durante unos segundos, se activan las funciones de accesibilidad, que pueden cambiar el funcionamiento del dispositivo.\n\nFunciones actuales:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuedes cambiar las funciones seleccionadas en Ajustes &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"¿Quieres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"¿Quieres activar el acceso directo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Al mantener pulsadas ambas teclas de volumen durante unos segundos se activa <xliff:g id="SERVICE">%1$s</xliff:g>, una función de accesibilidad. Esta función puede modificar el funcionamiento del dispositivo.\n\nPuedes asignar este acceso directo a otra función en Ajustes &gt; Accesibilidad."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"No activar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Durante %d horas</item>
<item quantity="one">Durante 1 hora</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Hasta <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Hasta las <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Hasta que lo desactives"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 3929ed4d50da..d825429032bb 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuda heliseadeid"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Võimaldab rakendusel muuta üldiseid heliseadeid, näiteks helitugevust ja seda, millist kõlarit kasutatakse väljundiks."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"salvesta heli"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"See rakendus saab mikrofoni kasutades mis tahes ajal heli salvestada."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"See rakendus saab mikrofoniga heli salvestada siis, kui rakendus on kasutusel."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Taustal heli salvestamine."</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"See rakendus saab mikrofoniga heli salvestada mis tahes ajal."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-kaardile käskluste saatmine"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lubab rakendusel saata käske SIM-kaardile. See on väga ohtlik."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"füüsiliste tegevuste tuvastamine"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"See rakendus saab tuvastada teie füüsilised tegevused."</string>
<string name="permlab_camera" msgid="6320282492904119413">"piltide ja videote tegemine"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"See rakendus saab mis tahes ajal kaameraga pildistada ja videoid salvestada."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"See rakendus saab kaameraga pildistada ja videoid salvestada siis, kui rakendus on kasutusel."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Taustal pildistamine ja videote salvestamine."</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"See rakendus saab kaameraga pildistada ja videoid salvestada mis tahes ajal."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Rakendusel või teenusel lubatakse süsteemi kaameratele juurde pääseda, et pilte ja videoid jäädvustada"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"See privileegidega või süsteemirakendus saab süsteemi kaameraga alati pilte ja videoid jäädvustada. Rakendusel peab olema ka luba android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Lubab rakendusel või teenusel kaameraseadmete avamise või sulgemise kohta tagasikutseid vastu võtta."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Kas suurendada helitugevuse taset üle soovitatud taseme?\n\nPikaajaline valju helitugevusega kuulamine võib kuulmist kahjustada."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Kas kasutada juurdepääsetavuse otseteed?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Kas lülitada juurdepääsufunktsioonid sisse?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kas lülitada juurdepääsufunktsioonide otsetee sisse?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hoidke juurdepääsufunktsioonide sisselülitamiseks mõlemat helitugevuse klahvi mõni sekund all. See võib teie seadme tööviisi muuta.\n\nPraegused funktsioonid:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nValitud funktsioone saab muuta jaotises Seaded &gt; Juurdepääsetavus."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Kas lülitada <xliff:g id="SERVICE">%1$s</xliff:g> sisse?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kas lülitada teenuse <xliff:g id="SERVICE">%1$s</xliff:g> otsetee sisse?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Kui hoiate mõlemat helitugevuse klahvi mõni sekund all, lülitatakse juurdepääsufunktsioon <xliff:g id="SERVICE">%1$s</xliff:g> sisse. See võib teie seadme tööviisi muuta.\n\nSelle otsetee saab asendada muu otseteega jaotises Seaded &gt; Juurdepääsetavus."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Lülita sisse"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ära lülita sisse"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d h</item>
<item quantity="one">1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Kuni <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (järgmine äratus)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Kuni välja lülitate"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index c93dbbfda5ef..05d40509e996 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"aldatu audio-ezarpenak"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Audio-ezarpen orokorrak aldatzeko baimena ematen dio; besteak beste, bolumena eta irteerarako zer bozgorailu erabiltzen den."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabatu audioa"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikazioak abian den bitartean erabil dezake mikrofonoa audioa grabatzeko."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Audioa grabatu atzeko planoan."</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikazioak edonoiz erabil dezake mikrofonoa audioa grabatzeko."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"bidali aginduak SIM txartelera"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM txartelera aginduak bidaltzeko aukera ematen die aplikazioei. Oso arriskutsua da."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"hauteman ariketa fisikoa"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikazioak ariketa fisikoa hauteman dezake."</string>
<string name="permlab_camera" msgid="6320282492904119413">"atera argazkiak eta grabatu bideoak"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Aplikazioak edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Aplikazioak abian den bitartean erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Argazkiak atera eta bideoak grabatu atzeko planoan."</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Aplikazioak edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"onartu aplikazio edo zerbitzu bati sistemako kamerak atzitzea argazkiak eta bideoak ateratzeko"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Pribilegioa duen edo sistemakoa den aplikazio honek edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko. Halaber, android.permission.CAMERA baimena izan behar du aplikazioak."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"eman jakinarazpenak jasotzeko baimena aplikazioari edo zerbitzuari kamerak ireki edo ixten direnean."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bolumena gomendatutako mailatik gora igo nahi duzu?\n\nMusika bolumen handian eta denbora luzez entzuteak entzumena kalte diezazuke."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erabilerraztasun-lasterbidea erabili nahi duzu?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Erabilerraztasun-eginbideak aktibatu nahi dituzu?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erabilerraztasun-eginbideetarako lasterbidea aktibatu nahi duzu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Eduki sakatuta bolumen-botoiak segundo batzuez erabilerraztasun-eginbideak aktibatzeko. Hori eginez gero, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nUneko eginbideak:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nHautatutako eginbideak aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> aktibatu nahi duzu?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> zerbitzuaren lasterbidea aktibatu nahi duzu?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Eduki sakatuta bolumen-botoiak segundo batzuez <xliff:g id="SERVICE">%1$s</xliff:g> izeneko erabilerraztasun-eginbidea aktibatzeko. Honen bidez, baliteke zure mugikorraren funtzionamendua aldatzea.\n\nLasterbide hau beste eginbide batengatik aldatzeko, joan Ezarpenak &gt; Erabilerraztasuna atalera."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktibatu"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ez aktibatu"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d orduz</item>
<item quantity="one">Ordubetez</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> arte (hurrengo alarma)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Zuk desaktibatu arte"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 1ee4be59733b..907fe5bc20ca 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"تغییر تنظیمات صوتی"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"به برنامه امکان می‌دهد تنظیمات صوتی کلی مانند میزان صدا و بلندگوی مورد استفاده برای پخش صدا را تغییر دهد."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ضبط صدا"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"این برنامه می‌تواند در هرزمانی با استفاده از میکروفون صدا ضبط کند."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"این برنامه وقتی درحال استفاده است، می‌تواند بااستفاده از میکروفون صدا ضبط کند."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ضبط صدا در پس‌زمینه"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"این برنامه می‌تواند در هرزمانی با استفاده از میکروفون صدا ضبط کند."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ارسال فرمان به سیم کارت"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"به برنامه اجازه ارسال دستورات به سیم کارت را می‌دهد. این بسیار خطرناک است."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"تشخیص فعالیت فیزیکی"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"این برنامه نمی‌تواند فعالیت فیزیکی‌تان را تشخیص دهد."</string>
<string name="permlab_camera" msgid="6320282492904119413">"عکسبرداری و فیلمبرداری"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"این برنامه می‌تواند در هرزمانی با استفاده از دوربین عکس و فیلم بگیرد."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"این برنامه وقتی درحال استفاده است، می‌تواند بااستفاده از دوربین عکس و فیلم بگیرد."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"گرفتن عکس و فیلم در پس‌زمینه"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"این برنامه می‌تواند در هرزمانی با استفاده از دوربین عکس و فیلم بگیرد."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"به برنامه یا سرویسی اجازه دهید برای عکس‌برداری و فیلم‌برداری به دوربین‌های سیستم دسترسی داشته باشد"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"‏این برنامه سیستم یا دارای امتیاز، می‌تواند با استفاده از دوربین سیستم در هرزمانی عکس‌برداری و فیلم‌برداری کند. برنامه به اجازه android.permission.CAMERA هم نیاز دارد."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"مجاز کردن برنامه یا سرویس برای دریافت پاسخ تماس درباره دستگاه‌های دوربینی که باز یا بسته می‌شوند."</string>
@@ -1267,7 +1271,7 @@
<string name="sms_control_title" msgid="4748684259903148341">"درحال ارسال پیامک‌ها"</string>
<string name="sms_control_message" msgid="6574313876316388239">"‏&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; درحال ارسال تعداد زیادی پیامک است. آیا اجازه می‌دهید این برنامه همچنان پیامک ارسال کند؟"</string>
<string name="sms_control_yes" msgid="4858845109269524622">"مجاز است"</string>
- <string name="sms_control_no" msgid="4845717880040355570">"اجازه ندارد"</string>
+ <string name="sms_control_no" msgid="4845717880040355570">"مجاز نبودن"</string>
<string name="sms_short_code_confirm_message" msgid="1385416688897538724">"‏&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; مایل است پیامی به &lt;b&gt;<xliff:g id="DEST_ADDRESS">%2$s</xliff:g>&lt;/b&gt; ارسال کند."</string>
<string name="sms_short_code_details" msgid="2723725738333388351">"این مورد "<b>"شاید هزینه‌ای"</b>" را به حساب دستگاه همراهتان بگذارد."</string>
<string name="sms_premium_short_code_details" msgid="1400296309866638111"><b>"این مورد هزینه‌ای را به حساب دستگاه همراهتان می‌گذارد."</b></string>
@@ -1413,7 +1417,7 @@
<string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"می‌خواهید به این درخواست اجازه دهید؟"</string>
<string name="grant_permissions_header_text" msgid="3420736827804657201">"درخواست دسترسی"</string>
<string name="allow" msgid="6195617008611933762">"ارزیابی‌شده"</string>
- <string name="deny" msgid="6632259981847676572">"اجازه ندارد"</string>
+ <string name="deny" msgid="6632259981847676572">"مجاز نبودن"</string>
<string name="permission_request_notification_title" msgid="1810025922441048273">"مجوز درخواست شد"</string>
<string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"مجوز\nبرای حساب <xliff:g id="ACCOUNT">%s</xliff:g> درخواست شد."</string>
<string name="forward_intent_to_owner" msgid="4620359037192871015">"شما از این برنامه در خارج از نمایه کاری‌تان استفاده می‌کنید"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"میزان صدا را به بالاتر از حد توصیه شده افزایش می‌دهید؟\n\nگوش دادن به صداهای بلند برای مدت طولانی می‌تواند به شنوایی‌تان آسیب وارد کند."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"از میان‌بر دسترس‌پذیری استفاده شود؟"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"وقتی میان‌بر روشن باشد، با فشار دادن هردو دکمه صدا به‌مدت ۳ ثانیه ویژگی دسترس‌پذیری فعال می‌شود."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ویژگی‌های دسترس‌پذیری روشن شود؟"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"میان‌بر برای ویژگی‌های دسترس‌پذیری روشن شود؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"با پایین نگه داشتن هردو کلید میزان صدا به‌مدت چند ثانیه، ویژگی‌های دسترس‌پذیری روشن می‌شود. با این کار نحوه عملکرد دستگاهتان تغییر می‌کند.\n\nویژگی‌های فعلی:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nمی‌توانید ویژگی‌های انتخابی را در «تنظیمات &gt; دسترس‌پذیری» تغییر دهید."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"میان‌بر <xliff:g id="SERVICE">%1$s</xliff:g> روشن شود؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"با پایین نگه داشتن هردو کلید میزان صدا به‌مدت چند ثانیه، <xliff:g id="SERVICE">%1$s</xliff:g> (یکی از ویژگی‌های دسترس‌پذیری) روشن می‌شود. با این کار نحوه عملکرد دستگاهتان تغییر می‌کند.\n\nمی‌توانید در «تنظیمات &gt; دسترس‌پذیری»،‌این میان‌بر را به ویژگی دیگری تغییر دهید."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"روشن شود"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"روشن نشود"</string>
@@ -1639,7 +1643,7 @@
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"مشاهده و انجام کنش‌ها"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"این عملکرد می‌تواند با برنامه یا حسگری سخت‌افزاری تعاملاتتان را ردیابی کند و ازطرف شما با برنامه‌ها تعامل داشته باشد."</string>
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"مجاز"</string>
- <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"رد کردن"</string>
+ <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"مجاز نبودن"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"برای استفاده از ویژگی، روی آن ضربه بزنید:"</string>
<string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"انتخاب ویژگی‌های موردنظر برای استفاده با دکمه دسترس‌پذیری"</string>
<string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"انتخاب ویژگی‌های موردنظر برای استفاده با میان‌بر کلید میزان صدا"</string>
@@ -1654,11 +1658,11 @@
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"کلیدهای میزان صدا پایین نگه داشته شد. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> خاموش شد."</string>
<string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"برای استفاده از <xliff:g id="SERVICE_NAME">%1$s</xliff:g>، هر دو کلید صدا را فشار دهید و سه ثانیه نگه دارید"</string>
<string name="accessibility_button_prompt_text" msgid="8343213623338605305">"ویژگی را انتخاب کنید که هنگام ضربه زدن روی دکمه دسترس‌پذیری استفاده می‌شود:"</string>
- <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"ویژگی را برای استفاده با اشاره دسترس‌پذیری انتخاب کنید (با دو انگشت صفحه را از پایین تند به بالا بکشید):"</string>
- <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"ویزگی را برای استفاده با اشاره دسترس‌پذیری انتخاب کنید (با سه انگشت صفحه را از پایین تند به بالا بکشید):"</string>
+ <string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"ویژگی را برای استفاده با اشاره دسترس‌پذیری انتخاب کنید (با دو انگشت صفحه را از پایین تند به‌بالا بکشید):"</string>
+ <string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"ویزگی را برای استفاده با اشاره دسترس‌پذیری انتخاب کنید (با سه انگشت صفحه را از پایین تند به‌بالا بکشید):"</string>
<string name="accessibility_button_instructional_text" msgid="8853928358872550500">"برای جابه‌جایی بین ویژگی‌ها، دکمه دسترس‌پذیری را لمس کنید و نگه دارید."</string>
- <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"برای جابه‌جایی بین ویژگی‌ها، با دو انگشت صفحه را تند به بالا بکشید و نگه دارید."</string>
- <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"برای جابه‌جایی بین ویژگی‌ها، با سه انگشت صفحه را تند به بالا بکشید و نگه دارید."</string>
+ <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"برای جابه‌جایی بین ویژگی‌ها، با دو انگشت صفحه را تند به‌بالا بکشید و نگه دارید."</string>
+ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"برای جابه‌جایی بین ویژگی‌ها، با سه انگشت صفحه را تند به‌بالا بکشید و نگه دارید."</string>
<string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"درشت‌نمایی"</string>
<string name="user_switched" msgid="7249833311585228097">"کاربر کنونی <xliff:g id="NAME">%1$s</xliff:g>."</string>
<string name="user_switching_message" msgid="1912993630661332336">"در حالت تغییر به <xliff:g id="NAME">%1$s</xliff:g>…"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">‏برای %d ساعت</item>
<item quantity="other">‏برای %d ساعت</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"تا <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (زنگ بعدی)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"تا زمانی‌که آن را خاموش کنید"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 1eff983e803f..e7bcf4867203 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -304,7 +304,7 @@
<string name="permgrouplab_sms" msgid="795737735126084874">"Tekstiviestit"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"lähettää ja tarkastella tekstiviestejä"</string>
<string name="permgrouplab_storage" msgid="1938416135375282333">"Tiedostot ja media"</string>
- <string name="permgroupdesc_storage" msgid="6351503740613026600">"käyttää laitteellesi tallennettuja valokuvia, mediatiedostoja ja muita tiedostoja"</string>
+ <string name="permgroupdesc_storage" msgid="6351503740613026600">"käyttää laitteellesi tallennettuja kuvia, mediatiedostoja ja muita tiedostoja"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofoni"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"tallentaa ääntä"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Liikkuminen"</string>
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"muuta ääniasetuksia"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Antaa sovelluksen muokata yleisiä ääniasetuksia, kuten äänenvoimakkuutta ja käytettävää kaiutinta."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"tallentaa ääntä"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Tämä sovellus voi tallentaa mikrofonilla ääntä koska tahansa."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Tämä sovellus voi tallentaa mikrofonilla audiota, kun sovellusta käytetään."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"tallentaa audiota taustalla"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Tämä sovellus voi tallentaa mikrofonilla audiota koska tahansa."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"lähettää komentoja SIM-kortille"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Antaa sovelluksen lähettää komentoja SIM-kortille. Tämä ei ole turvallista."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"tunnistaa liikkumisen"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Sovellus voi tunnistaa liikkumisesi."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ota kuvia ja videoita"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Tämä sovellus voi ottaa kameralla kuvia ja videoita koska tahansa."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Tämä sovellus voi ottaa kameralla kuvia ja videoita, kun sovellusta käytetään."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ottaa kuvia ja videoita taustalla"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Tämä sovellus voi ottaa kameralla kuvia ja videoita koska tahansa."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Salli sovellukselle tai palvelulle pääsy järjestelmän kameroihin, jotta se voi ottaa kuvia ja nauhoittaa videoita"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Tämä oikeutettu tai järjestelmäsovellus voi ottaa järjestelmän kameralla kuvia ja videoita koska tahansa. Sovelluksella on oltava myös android.permission.CAMERA-lupa"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Salli sovelluksen tai palvelun vastaanottaa vastakutsuja kameralaitteiden avaamisesta tai sulkemisesta."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Nostetaanko äänenvoimakkuus suositellun tason yläpuolelle?\n\nPitkäkestoinen kova äänenvoimakkuus saattaa heikentää kuuloa."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Käytetäänkö esteettömyyden pikanäppäintä?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kun pikanäppäin on käytössä, voit käynnistää esteettömyystoiminnon pitämällä molempia äänenvoimakkuuspainikkeita painettuna kolmen sekunnin ajan."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Laitetaanko esteettömyysominaisuudet päälle?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Laitetaanko esteettömyysominaisuuksien pikavalinta päälle?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Molempien äänenvoimakkuuspainikkeiden painaminen muutaman sekunnin ajan laittaa esteettömyysominaisuudet päälle. Tämä voi muuttaa laitteesi toimintaa.\n\nNykyiset ominaisuudet:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVoit muuttaa valittuja ominaisuuksia kohdassa Asetukset &gt; Esteettömyys."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Laitetaanko <xliff:g id="SERVICE">%1$s</xliff:g> päälle?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Laitetaanko pikavalinta (<xliff:g id="SERVICE">%1$s</xliff:g>) päälle?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Molempien äänenvoimakkuuspainikkeiden pitkään painaminen laittaa päälle esteettömyysominaisuuden <xliff:g id="SERVICE">%1$s</xliff:g>. Tämä voi muuttaa laitteesi toimintaa.\n\nVoit muuttaa tätä pikanäppäintä kohdassa Asetukset &gt; Esteettömyys."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Laita päälle"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Älä laita päälle"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d tunnin ajan</item>
<item quantity="one">1 tunnin ajan</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> asti"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Kunnes kello on <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> asti (seuraava hälytys)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Kunnes laitat pois päältä"</string>
@@ -1914,7 +1919,7 @@
<string name="app_category_game" msgid="4534216074910244790">"Pelit"</string>
<string name="app_category_audio" msgid="8296029904794676222">"Musiikki ja ääni"</string>
<string name="app_category_video" msgid="2590183854839565814">"Elokuvat ja videot"</string>
- <string name="app_category_image" msgid="7307840291864213007">"Kuvat ja valokuvat"</string>
+ <string name="app_category_image" msgid="7307840291864213007">"Kuvat ja kuvat"</string>
<string name="app_category_social" msgid="2278269325488344054">"Some ja viestintä"</string>
<string name="app_category_news" msgid="1172762719574964544">"Uutiset ja lehdet"</string>
<string name="app_category_maps" msgid="6395725487922533156">"Kartat ja navigointi"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index c6f347b1833b..aad574946036 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Cette application peut enregistrer de l\'audio à l\'aide du microphone en tout temps."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut enregistrer de l\'audio à l\'aide du microphone lorsque vous utilisez l\'application."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"enregistrer de l\'audio en arrière-plan"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut enregistrer de l\'audio à l\'aide du microphone en tout temps."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permet à l\'application d\'envoyer des commandes à la carte SIM. Cette fonctionnalité est très dangereuse."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître les activités physiques"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Cette application peut reconnaître vos activités physiques."</string>
<string name="permlab_camera" msgid="6320282492904119413">"prendre des photos et filmer des vidéos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Cette application peut prendre des photos et enregistrer des vidéos à l\'aide de l\'appareil photo en tout temps."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Cette application peut prendre des photos et enregistrer des vidéos à l\'aide de l\'appareil photo lorsque vous utilisez l\'application."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"prendre des photos et enregistrer des vidéos en arrière-plan"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Cette application peut prendre des photos et enregistrer des vidéos à l\'aide de l\'appareil photo en tout temps."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Autoriser une application ou un service à accéder aux appareils photo système pour prendre des photos et filmer des vidéos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Cette application privilégiée ou système peut prendre des photos ou filmer des vidéos à l\'aide d\'un appareil photo système en tout temps. L\'application doit également posséder l\'autorisation android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Autoriser une application ou un service de recevoir des rappels relatifs à l\'ouverture ou à la fermeture des appareils photos."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au-dessus du niveau recommandé?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activer les fonctionnalités d\'accessibilité?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour les fonctionnalités d\'accessibilité?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez les fonctionnalités d\'accessibilité. Cela peut modifier le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, sélectionnez Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activer <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci pour <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous maintenez enfoncées les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut modifier le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, sélectionnez Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne pas activer"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Pendant %d h</item>
<item quantity="other">Pendant %d h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Jusqu\'à la désactivation"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 6307c354317b..d2bdbbb6e360 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -327,7 +327,7 @@
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Contrôle le niveau de zoom et le positionnement de l\'écran."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Effectuer des gestes"</string>
<string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permet d\'appuyer sur l\'écran, de le balayer, de le pincer et d\'effectuer d\'autres gestes."</string>
- <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestes avec l\'empreinte digitale"</string>
+ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestes d\'empreinte digitale"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Peut enregistrer des gestes effectués sur le lecteur d\'empreinte digitale de l\'appareil."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Prendre une capture d\'écran"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Peut prendre des captures d\'écran."</string>
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifier vos paramètres audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permet à l\'application de modifier les paramètres audio généraux, tels que le volume et la sortie audio utilisée."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"enregistrer des fichiers audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Cette application peut utiliser le micro pour enregistrer du contenu audio à tout moment."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Cette application peut utiliser le micro pour réaliser des enregistrements audio quand elle est en cours d\'utilisation."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"réaliser des enregistrements audio en arrière-plan"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Cette application peut utiliser le micro pour réaliser des enregistrements audio à tout moment."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"envoyer des commandes à la carte SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Autoriser l\'envoi de commandes à la carte SIM via l\'application. Cette fonctionnalité est très risquée."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconnaître l\'activité physique"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Cette application peut reconnaître votre activité physique."</string>
<string name="permlab_camera" msgid="6320282492904119413">"prendre des photos et enregistrer des vidéos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Cette application peut utiliser l\'appareil photo pour prendre des photos et enregistrer des vidéos à tout moment."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Cette application peut utiliser l\'appareil photo pour prendre des photos et enregistrer des vidéos quand elle est en cours d\'utilisation."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"prendre des photos et enregistrer des vidéos en arrière-plan"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Cette application peut utiliser l\'appareil photo pour prendre des photos et enregistrer des vidéos à tout moment."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Autoriser une application ou un service à accéder aux caméras système pour prendre des photos et enregistrer des vidéos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Cette application privilégiée ou système peut utiliser une caméra photo système pour prendre des photos et enregistrer des vidéos à tout moment. Pour cela, l\'application doit également disposer de l\'autorisation android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Autoriser une application ou un service à recevoir des rappels liés à l\'ouverture ou à la fermeture de caméras"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au dessus du niveau recommandé ?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour démarrer une fonctionnalité d\'accessibilité."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activer les fonctionnalités d\'accessibilité ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activer le raccourci pour accéder aux fonctionnalités d\'accessibilité ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez des fonctionnalités d\'accessibilité. Cela peut affecter le fonctionnement de votre appareil.\n\nFonctionnalités actuellement utilisées :\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPour les modifier, accédez à Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activer <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activer le raccourci <xliff:g id="SERVICE">%1$s</xliff:g> ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Si vous appuyez sur les deux touches de volume pendant quelques secondes, vous activez la fonctionnalité d\'accessibilité <xliff:g id="SERVICE">%1$s</xliff:g>. Cela peut affecter le fonctionnement de votre appareil.\n\nPour attribuer ce raccourci à une autre fonctionnalité, accédez à Paramètres &gt; Accessibilité."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activer"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne pas activer"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Pendant %d h</item>
<item quantity="other">Pendant %d h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarme suivante)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Jusqu\'à la désactivation"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 0f281469bbf1..79380d5398ca 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar a configuración de son"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite á aplicación modificar a configuración de audio global, como o volume e que altofalante se utiliza para a saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta aplicación pode utilizar o micrófono en calquera momento para gravar audio."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Mentres a empregas, esta aplicación pode utilizar o micrófono para gravar audio."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar audio en segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta aplicación pode utilizar o micrófono en calquera momento para gravar audio."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos á SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite á aplicación enviar comandos á SIM. Isto é moi perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recoñecer actividade física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta aplicación pode recoñecer a túa actividade física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"facer fotos e vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Esta aplicación pode utilizar a cámara en calquera momento para sacar fotos e gravar vídeos."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Mentres a empregas, esta aplicación pode utilizar a cámara para sacar fotos e gravar vídeos."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"sacar fotos e gravar vídeos en segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Esta aplicación pode utilizar a cámara en calquera momento para sacar fotos e gravar vídeos."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que unha aplicación ou un servizo acceda ás cámaras do sistema para sacar fotos e gravar vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta aplicación do sistema con privilexios pode utilizar unha cámara do sistema en calquera momento para tirar fotos e gravar vídeos. Require que a aplicación tamén teña o permiso android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que unha aplicación ou servizo reciba retrochamadas cando se abran ou se pechen dispositivos con cámara."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Queres subir o volume máis do nivel recomendado?\n\nA reprodución de son a un volume elevado durante moito tempo pode provocar danos nos oídos."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Queres utilizar o atallo de accesibilidade?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Cando o atallo está activado, podes premer os dous botóns de volume durante 3 segundos para iniciar unha función de accesibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Queres activar as funcións de accesibilidade?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Queres activar as funcións de accesibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ao manter as dúas teclas de volume premidas durante uns segundos actívanse as funcións de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nFuncións activadas actualmente:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPodes cambiar as funcións seleccionadas en Configuración &gt; Accesibilidade."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Queres activar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Queres activar o atallo a <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ao manter as dúas teclas de volume premidas durante uns segundos actívase <xliff:g id="SERVICE">%1$s</xliff:g>, unha función de accesibilidade. Esta acción pode cambiar o funcionamento do dispositivo.\n\nPodes cambiar o uso deste atallo para outra función en Configuración &gt; Accesibilidade."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Non activar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Durante %d h</item>
<item quantity="one">Durante unha h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Ata: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Ata as <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próxima alarma)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Ata a desactivación"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index f11a228ecae8..bf4d6c0b8a09 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"તમારી ઑડિઓ સેટિંગ્સ બદલો"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"એપ્લિકેશનને વૈશ્વિક ઑડિઓ સેટિંગ્સને સંશોધિત કરવાની મંજૂરી આપે છે, જેમ કે વૉલ્યૂમ અને આઉટપુટ માટે કયા સ્પીકરનો ઉપયોગ કરવો."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ઑડિઓ રેકોર્ડ કરવાની"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"આ ઍપ્લિકેશન, માઇક્રોફોનનો ઉપયોગ કરીને કોઈપણ સમયે ઑડિઓ રેકોર્ડ કરી શકે છે."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"આ ઍપ ઉપયોગમાં હોય ત્યારે તે માઇક્રોફોનનો ઉપયોગ કરીને ઑડિયો રેકોર્ડ કરી શકે છે."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"બૅકગ્રાઉન્ડમાં ઑડિયો રેકોર્ડ કરો"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"આ ઍપ, માઇક્રોફોનનો ઉપયોગ કરીને કોઈપણ સમયે ઑડિયો રેકોર્ડ કરી શકે છે."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"સિમ ને આદેશો મોકલો"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"એપ્લિકેશનને સિમ પરા આદેશો મોકલવાની મંજૂરી આપે છે. આ ખૂબ જ ખતરનાક છે."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"શારીરિક પ્રવૃત્તિને ઓળખો"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"આ ઍપ તમારી શારીરિક પ્રવૃત્તિને ઓળખી શકે છે."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ચિત્રો અને વિડિઓઝ લો"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"આ ઍપ્લિકેશન, કૅમેરાનો ઉપયોગ કરીને કોઈપણ સમયે ચિત્રો લઈ અને વિડિઓઝ રેકોર્ડ કરી શકે છે."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"આ ઍપ ઉપયોગમાં હોય ત્યારે તે કૅમેરાનો ઉપયોગ કરીને ફોટા લઈ શકે છે અને વીડિયો રેકોર્ડ કરી શકે છે."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"બૅકગ્રાઉન્ડમાં ફોટા અને વીડિયો લો"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"આ ઍપ, કૅમેરાનો ઉપયોગ કરીને કોઈપણ સમયે ફોટા લઈ શકે છે અને વીડિયો રેકોર્ડ કરી શકે છે."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ઍપ્લિકેશન અથવા સેવા ઍક્સેસને સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા અને વીડિયો લેવાની મંજૂરી આપો"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"આ વિશેષાધિકૃત અથવા સિસ્ટમ ઍપ કોઈપણ સમયે સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા લઈ અને વીડિયો રેકૉર્ડ કરી શકે છે. ઍપ દ્વારા આયોજિત કરવા માટે android.permission.CAMERAની પરવાનગી પણ જરૂરી છે"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"કૅમેરા ડિવાઇસ ચાલુ કે બંધ થવા વિશે કૉલબૅક પ્રાપ્ત કરવાની ઍપ્લિકેશન કે સેવાને મંજૂરી આપો."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ભલામણ કરેલ સ્તરની ઉપર વૉલ્યૂમ વધાર્યો?\n\nલાંબા સમય સુધી ઊંચા અવાજે સાંભળવું તમારી શ્રવણક્ષમતાને નુકસાન પહોંચાડી શકે છે."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ઍક્સેસિબિલિટી શૉર્ટકટનો ઉપયોગ કરીએ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"જ્યારે શૉર્ટકટ ચાલુ હોય, ત્યારે બન્ને વૉલ્યૂમ બટનને 3 સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા શરૂ થઈ જશે."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ઍક્સેસિબિલિટી સુવિધાઓ ચાલુ કરીએ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ઍક્સેસિબિલિટી સુવિધાઓ માટે શૉર્ટકટ ચાલુ કરીએ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધાઓ ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nવર્તમાન સુવિધાઓ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nતમે સેટિંગ &gt; ઍક્સેસિબિલિટીમાં જઈને પસંદ કરેલી સુવિધાઓને બદલી શકો છો."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>ને ચાલુ કરીએ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> શૉર્ટકટ ચાલુ કરીએ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"બન્ને વૉલ્યૂમ કીને થોડી સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા એવી <xliff:g id="SERVICE">%1$s</xliff:g> ચાલુ થઈ જાય છે. આનાથી તમારા ડિવાઇસની કામ કરવાની રીત બદલાઈ શકે છે.\n\nતમે સેટિંગ &gt; ઍક્સેસિબિલિટીમાં જઈને આ શૉર્ટકટને બીજી સુવિધામાં બદલી શકો છો."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ચાલુ કરો"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ચાલુ કરશો નહીં"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d કલાક માટે</item>
<item quantity="other">%d કલાક માટે</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> સુધી"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (આગલા એલાર્મ) સુધી"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"તમે બંધ ન કરો ત્યાં સુધી"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 7b0bd238d7f7..b867e9d04f7d 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"अपनी ऑडियो सेटिंग बदलें"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ऐप्स को वैश्विक ऑडियो सेटिंग, जैसे वॉल्‍यूम और कौन-सा स्पीकर आउटपुट के लिए उपयोग किया गया, संशोधित करने देता है."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडियो रिकॉर्ड करने"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"यह ऐप्लिकेशन किसी भी समय माइक्रोफ़ोन का उपयोग करके ऑडियो रिकॉर्ड कर सकता है."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"जब इस ऐप्लिकेशन का इस्तेमाल किया जा रहा हो, तब यह माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ऐप्लिकेशन बैकग्राउंड में ऑडियो रिकॉर्ड कर सकता है"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यह ऐप्लिकेशन जब चाहे, माइक्रोफ़ोन का इस्तेमाल करके ऑडियो रिकॉर्ड कर सकता है."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"सिम पर निर्देश भेजें"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ऐप को सिम पर निर्देश भेजने देती है. यह बहुत ही खतरनाक है."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शरीर की गतिविधि को पहचानना"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"यह ऐप्लिकेशन आपके शरीर की गतिविधि को पहचान सकता है."</string>
<string name="permlab_camera" msgid="6320282492904119413">"चित्र और वीडियो लें"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"यह ऐप्लिकेशन किसी भी समय कैमरे का उपयोग करके चित्र ले सकता है और वीडियो रिकॉर्ड कर सकता है."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"जब इस ऐप्लिकेशन का इस्तेमाल किया जा रहा हो, तब यह कैमरे का इस्तेमाल करके, तस्वीरें ले सकता है और वीडियो रिकॉर्ड कर सकता है."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ऐप्लिकेशन बैकग्राउंड में तस्वीरें ले सकता है और वीडियो रिकॉर्ड कर सकता है"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"यह ऐप्लिकेशन जब चाहे, कैमरे का इस्तेमाल करके तस्वीरें ले सकता है और वीडियो रिकॉर्ड कर सकता है."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"तस्वीरें और वीडियो लेने के लिए ऐप्लिकेशन या सेवा को सिस्टम के कैमरे का ऐक्सेस दें"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"यह खास सिस्टम ऐप्लिकेशन जब चाहे, तस्वीरें लेने और वीडियो रिकॉर्ड करने के लिए सिस्टम के कैमरे का इस्तेमाल कर सकता है. इसके लिए ऐप्लिकेशन को android.permission.CAMERA की अनुमति देना भी ज़रूरी है"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"डिवाइस का कैमरे चालू या बंद होने पर, किसी ऐप्लिकेशन या सेवा को कॉलबैक पाने की मंज़ूरी दें."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"वॉल्यूम को सुझाए गए स्तर से ऊपर बढ़ाएं?\n\nअत्यधिक वॉल्यूम पर ज़्यादा समय तक सुनने से आपकी सुनने की क्षमता को नुकसान हो सकता है."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"सुलभता शॉर्टकट का इस्तेमाल करना चाहते हैं?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट के चालू होने पर, दाेनाें वॉल्यूम बटन (आवाज़ कम या ज़्यादा करने वाले बटन) को तीन सेकंड तक दबाने से, सुलभता सुविधा शुरू हाे जाएगी."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"सुलभता सुविधाएं चालू करना चाहते हैं?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"क्या आप सुलभता सुविधाओं के लिए शॉर्टकट चालू करना चाहते हैं?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से सुलभता सुविधाएं चालू हो जाती हैं. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nमौजूदा सुविधाएं:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nआप सेटिंग और सुलभता में जाकर चुनी हुई सुविधाएं बदल सकते हैं."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> चालू करना चाहते हैं?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"क्या आप <xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट चालू करना चाहते हैं?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"आवाज़ कम और ज़्यादा करने वाले दोनों बटन को कुछ सेकंड तक दबाकर रखने से <xliff:g id="SERVICE">%1$s</xliff:g> चालू हो जाती है, जो एक सुलभता सुविधा है. ऐसा करने से आपके डिवाइस के काम करने के तरीके में बदलाव हो सकता है.\n\nआप सेटिंग और सुलभता में जाकर इस शॉर्टकट को किसी दूसरी सुविधा के लिए बदल सकते हैं."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"चालू करें"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"चालू न करें"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d घंटे के लिए</item>
<item quantity="other">%d घंटे के लिए</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> तक"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> तक"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अगले अलार्म) तक"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"जब तक आप बंद नहीं करते"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index eb41c9f8f6f8..dd2bdbeb74c3 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"promjena postavki zvuka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogućuje izmjenu globalnih postavki zvuka, primjerice glasnoće i zvučnika koji se upotrebljava za izlaz."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snimanje zvuka"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikacija u svakom trenutku može snimati zvuk mikrofonom."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikacija može snimati audiozapise pomoću mikrofona dok se upotrebljava."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snimati audiozapise u pozadini"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikacija može snimati audiozapise pomoću mikrofona u svakom trenutku."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"slati naredbe SIM-u"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Omogućuje aplikaciji slanje naredbi SIM-u. To je vrlo opasno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznati tjelesnu aktivnost"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ova aplikacija može prepoznati vašu tjelesnu aktivnost."</string>
<string name="permlab_camera" msgid="6320282492904119413">"snimi fotografije i videozapise"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Aplikacija u svakom trenutku može snimati fotografije i videozapise fotoaparatom."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Aplikacija može snimati fotografije i videozapise pomoću kamere dok se upotrebljava."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"snimati fotografije i videozapise u pozadini"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Aplikacija može snimati fotografije i videozapise pomoću kamere u svakom trenutku."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Dopustite aplikaciji ili usluzi da pristupa kamerama sustava radi snimanja fotografija i videozapisa"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ova povlaštena aplikacija ili aplikacija sustava u svakom trenutku može snimati fotografije i videozapise kamerom sustava. Aplikacija mora imati i dopuštenje android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Dopustite aplikaciji ili usluzi da prima povratne pozive o otvaranju ili zatvaranju fotoaparata."</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučene razine?\n\nDugotrajno slušanje glasne glazbe može vam oštetiti sluh."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li upotrebljavati prečac za pristupačnost?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad je taj prečac uključen, pritiskom na obje tipke za glasnoću na tri sekunde pokrenut će se značajka pristupačnosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite li uključiti značajke pristupačnosti?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite li uključiti prečac za značajke pristupačnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Značajke pristupačnosti uključuju se ako na nekoliko sekundi pritisnete obje tipke za glasnoću. Time se može promijeniti način na koji vaš uređaj radi.\n\nTrenutačne značajke:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nOdabrane značajke možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite li uključiti uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite li uključiti prečac za uslugu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ako na nekoliko sekundi pritisnete obje tipke za glasnoću, uključuje se značajka pristupačnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Time se može promijeniti način na koji vaš uređaj radi.\n\nZnačajku na koju se taj prečac odnosi možete promijeniti u odjeljku Postavke &gt; Pristupačnost."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Uključi"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nemoj uključiti"</string>
@@ -1860,6 +1864,7 @@
<item quantity="few">%d h</item>
<item quantity="other">%d h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sljedeći alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dok ne isključite"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3b44295755c1..9d1c2f491962 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"hangbeállítások módosítása"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lehetővé teszi az alkalmazás számára az általános hangbeállítások, például a hangerő és a használni kívánt kimeneti hangszóró módosítását."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"hanganyag rögzítése"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Az alkalmazás a mikrofon használatával bármikor készíthet hangfelvételt."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Az alkalmazás a mikrofon használatával akkor készíthet hangfelvételt, amikor az alkalmazás használatban van."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"hangfelvétel készítése a háttérben"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Az alkalmazás a mikrofon használatával bármikor készíthet hangfelvételt."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"parancsok küldése a SIM-kártyára"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Engedélyezi, hogy az alkalmazás parancsokat küldjön a SIM kártyára. Ez rendkívül veszélyes lehet."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"testmozgás felismerése"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Az alkalmazás képes felismerni a testmozgást."</string>
<string name="permlab_camera" msgid="6320282492904119413">"fotók és videók készítése"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Az alkalmazás a kamera használatával bármikor készíthet fényképeket és rögzíthet videókat."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Az alkalmazás a kamera használatával akkor készíthet fényképeket és videókat, amikor az alkalmazás használatban van."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"fényképek és videók készítése a háttérben"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Az alkalmazás a kamera használatával bármikor készíthet fényképeket és videókat."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"A rendszerkamerákhoz való hozzáférés, illetve képek és videók rögzítésének engedélyezése alkalmazás vagy szolgáltatás számára"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"A rendszerkamera használatával ez az előnyben részesített vagy rendszeralkalmazás bármikor készíthet fényképeket és videókat. Az alkalmazásnak az „android.permission.CAMERA” engedéllyel is rendelkeznie kell."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Visszahívás fogadásának engedélyezése alkalmazás vagy szolgáltatás számára, ha a kamerákat megnyitják vagy bezárják."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Az ajánlott szint fölé szeretné emelni a hangerőt?\n\nHa hosszú időn át teszi ki magát nagy hangerőnek, azzal károsíthatja a hallását."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Szeretné használni a Kisegítő lehetőségek billentyűparancsot?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ha a gyorsparancs aktív, akkor a két hangerőgomb három másodpercig tartó együttes lenyomásával kisegítő funkciót indíthat el."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bekapcsolja a kisegítő lehetőségeket?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bekapcsol gyorsparancsot a kisegítő lehetőségekhez?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"A kisegítő lehetőségek bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nJelenlegi funkciók:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nA kiválasztott funkciókat a Beállítások &gt; Kisegítő lehetőségek pontban módosíthatja."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Bekapcsolja a következőt: <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bekapcsolja a(z) <xliff:g id="SERVICE">%1$s</xliff:g> szolgáltatás gyorsparancsát?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"A(z) <xliff:g id="SERVICE">%1$s</xliff:g> kisegítő lehetőség bekapcsolásához tartsa nyomva néhány másodpercig mindkét hangerőgombot. Ez hatással lehet az eszköz működésére.\n\nEzt a gyorsparancsot a Beállítások &gt; Kisegítő lehetőségek pontban módosíthatja másik funkció használatára."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bekapcsolom"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nem kapcsolom be"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d órán keresztül</item>
<item quantity="one">1 órán keresztül</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Eddig: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ez a következő ébresztés)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Kikapcsolásig"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 4f66b6e8a947..4e2bbe2111b5 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -48,7 +48,7 @@
<string name="invalidPin" msgid="7542498253319440408">"Մուտքագրեք PIN, որը 4-ից 8 թիվ է:"</string>
<string name="invalidPuk" msgid="8831151490931907083">"Մուտքագրեք PUK, որն 8 կամ ավել թիվ ունի:"</string>
<string name="needPuk" msgid="7321876090152422918">"Ձեր SIM քարտը PUK-ով կողպված է: Մուտքագրեք PUK կոդը այն ապակողպելու համար:"</string>
- <string name="needPuk2" msgid="7032612093451537186">"Մուտքագրեք PUK2-ը` SIM քարտն արգելահանելու համար:"</string>
+ <string name="needPuk2" msgid="7032612093451537186">"Մուտքագրեք PUK2-ը՝ SIM քարտն արգելահանելու համար:"</string>
<string name="enablePin" msgid="2543771964137091212">"Ձախողվեց: Միացրեք SIM/RUIM կողպումը:"</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Մնաց <xliff:g id="NUMBER_1">%d</xliff:g> փորձ, որից հետո SIM քարտն արգելափակվելու է:</item>
@@ -411,7 +411,7 @@
<string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Թույլ է տալիս հավելվածին փոփոխել ձեր պլանշետի զանգերի մատյանը, այդ թվում` մուտքային և ելքային զանգերի մասին տվյալները: Վնասարար հավելվածները կարող են սա օգտագործել` ձեր զանգերի մատյանը ջնջելու կամ փոփոխելու համար:"</string>
<string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Թույլ է տալիս հավելվածին փոփոխել Android TV սարքի զանգերի մատյանը, այդ թվում՝ մուտքային և ելքային զանգերի մասին տվյալները: Վնասարար հավելվածները կարող են սա օգտագործել՝ ձեր զանգերի մատյանը ջնջելու կամ փոփոխելու համար:"</string>
<string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Թույլ է տալիս հավելվածին փոփոխել ձեր հեռախոսի զանգերի մատյանը, այդ թվում` մուտքային և ելքային զանգերի մասին տվյալները: Վնասարար հավելվածները կարող են սա օգտագործել` ձեր զանգերի մատյանը ջնջելու կամ փոփոխելու համար:"</string>
- <string name="permlab_bodySensors" msgid="3411035315357380862">"օգտագործել մարմնի սենսորները (օրինակ` սրտի կծկումների հաճախականության չափիչ)"</string>
+ <string name="permlab_bodySensors" msgid="3411035315357380862">"օգտագործել մարմնի սենսորները (օրինակ՝ սրտի կծկումների հաճախականության չափիչ)"</string>
<string name="permdesc_bodySensors" product="default" msgid="2365357960407973997">"Հավելվածին թույլ է տալիս մուտք ունենալ սենսորների տվյալներին, որոնք վերահսկում են ձեր ֆիզիկական վիճակը, օրինակ՝ ձեր սրտի զարկերը:"</string>
<string name="permlab_readCalendar" msgid="6408654259475396200">"Կարդալ օրացույցի միջոցառումները և տվյալները"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Այս հավելվածը կարող է կարդալ օրացույցի՝ ձեր պլանշետում պահված բոլոր միջոցառումները, ինչպես նաև հրապարակել կամ պահել ձեր օրացույցի տվյալները:"</string>
@@ -430,15 +430,19 @@
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"տեղադրության մասին տվյալների հասանելիություն ֆոնային ռեժիմում"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Այս հավելվածը ցանկացած ժամանակ կարող է տեսնել տեղադրության տվյալները, նույնիսկ երբ այն ակտիվ չէ։"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"փոխել ձեր աուդիո կարգավորումները"</string>
- <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ` ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string>
+ <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Թույլ է տալիս հավելվածին փոփոխել ձայնանյութի գլոբալ կարգավորումները, ինչպես օրինակ՝ ձայնը և թե որ խոսափողն է օգտագործված արտածման համար:"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ձայնագրել աուդիո ֆայլ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Այս հավելվածը ցանկացած պահի կարող է ձայնագրել խոսափողի օգնությամբ:"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Այս հավելվածը կարող է օգտագործել խոսափողը՝ ձայնագրություններ անելու համար, միայն երբ հավելվածն ակտիվ է։"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ձայնագրել ֆոնային ռեժիմում"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Այս հավելվածը կարող է ցանկացած ժամանակ օգտագործել խոսափողը՝ ձայնագրություններ անելու համար։"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ուղարկել հրամաններ SIM քարտին"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Թույլ է տալիս հավելվածին հրամաններ ուղարկել SIM-ին: Սա շատ վտանգավոր է:"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ֆիզիկական ակտիվության ճանաչում"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Հավելվածը կարող է ճանաչել ձեր ֆիզիկական ակտիվությունը:"</string>
<string name="permlab_camera" msgid="6320282492904119413">"լուսանկարել և տեսանկարել"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Այս հավելվածը կարող է ցանկացած պահի լուսանկարել և տեսագրել՝ օգտագործելով տեսախցիկը:"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Այս հավելվածը կարող է օգտագործել տեսախցիկը՝ լուսանկարելու և տեսագրելու համար, միայն երբ հավելվածն ակտիվ է։"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"լուսանկարել և տեսագրել ֆոնային ռեժիմում"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Այս հավելվածը կարող է ցանկացած ժամանակ օգտագործել տեսախցիկը՝ լուսանկարելու և տեսագրելու համար։"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Թույլատրել որևէ հավելվածի կամ ծառայության օգտագործել համակարգի տեսախցիկները՝ լուսանկարելու և տեսանկարելու համար"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Այս արտոնյալ կամ համակարգային հավելվածը կարող է ցանկացած պահի լուսանկարել և տեսագրել՝ օգտագործելով համակարգի տեսախցիկները։ Հավելվածին նաև անհրաժեշտ է android.permission.CAMERA թույլտվությունը։"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Թույլատրել հավելվածին կամ ծառայությանը հետզանգեր ստանալ՝ տեսախցիկների բացվելու և փակվելու դեպքում։"</string>
@@ -495,7 +499,7 @@
<string name="permlab_changeTetherState" msgid="9079611809931863861">"փոխել միացված կապը"</string>
<string name="permdesc_changeTetherState" msgid="3025129606422533085">"Թույլ է տալիս հավելվածին փոխել կապված ցանցի միացման կարգավիճակը:"</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"դիտել Wi-Fi կապերը"</string>
- <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Թույլ է տալիս հավելվածին տեսնել Wi-Fi ցանցի տեղեկություններ, ինչպես օրինակ` արդյոք Wi-Fi-ը միացված է, թե` ոչ, և միացված Wi-Fi սարքի անունը:"</string>
+ <string name="permdesc_accessWifiState" msgid="6913641669259483363">"Թույլ է տալիս հավելվածին տեսնել Wi-Fi ցանցի տեղեկություններ, ինչպես օրինակ՝ արդյոք Wi-Fi-ը միացված է, թե` ոչ, և միացված Wi-Fi սարքի անունը:"</string>
<string name="permlab_changeWifiState" msgid="7947824109713181554">"միանալ Wi-Fi-ին և անջատվել դրանից"</string>
<string name="permdesc_changeWifiState" msgid="7170350070554505384">"Թույլ է տալիս հավելվածին միանալ Wi-Fi մուտքի կետերին և անջատվել այդ կետերից, ինչպես նաև կատարել սարքի կարգավորման փոփոխություններ Wi-Fi ցանցերի համար:"</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"թույլատրել Բազմասփյուռ Wi-Fi-ի ընդունումը"</string>
@@ -615,7 +619,7 @@
</string-array>
<string name="face_icon_content_description" msgid="465030547475916280">"Դեմքի պատկերակ"</string>
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"կարդալ համաժամացման կարգավորումները"</string>
- <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Թույլ է տալիս հավելվածին կարդալ համաժամացման կարգավորումները հաշվի համար: Օրինակ` այն կարող է որոշել, արդյոք Մարդիկ հավելվածը համաժամացված է հաշվի հետ:"</string>
+ <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Թույլ է տալիս հավելվածին կարդալ համաժամացման կարգավորումները հաշվի համար: Օրինակ՝ այն կարող է որոշել, արդյոք Մարդիկ հավելվածը համաժամացված է հաշվի հետ:"</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"համաժամացումը փոխարկել միացվածի և անջատվածի"</string>
<string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Թույլ է տալիս հավելվածին փոփոխել համաժամացման կարգավորումները հաշվի համար: Օրինակ, այն կարող է օգտագործվել` միացնելու Մարդիկ հավելվածի համաժամացումը հաշվի հետ:"</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"կարդալ համաժամացման վիճակագրությունը"</string>
@@ -876,7 +880,7 @@
<string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Մոռացե՞լ եք սխեման:"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Հաշվի ապակողպում"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Չափից շատ սխեմայի փորձեր"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Ապակողպելու համար` մուտք գործեք ձեր Google հաշվով:"</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"Ապակողպելու համար՝ մուտք գործեք ձեր Google հաշվով:"</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Օգտանուն (էլփոստ)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Գաղտնաբառ"</string>
<string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Մուտք գործել"</string>
@@ -1407,7 +1411,7 @@
<string name="ime_action_done" msgid="6299921014822891569">"Պատրաստ է"</string>
<string name="ime_action_previous" msgid="6548799326860401611">"Նախորդ"</string>
<string name="ime_action_default" msgid="8265027027659800121">"Կատարել"</string>
- <string name="dial_number_using" msgid="6060769078933953531">"Հավաքել հեռախոսահամարը`\nօգտագործելով <xliff:g id="NUMBER">%s</xliff:g>-ը"</string>
+ <string name="dial_number_using" msgid="6060769078933953531">"Հավաքել հեռախոսահամարը՝\nօգտագործելով <xliff:g id="NUMBER">%s</xliff:g>-ը"</string>
<string name="create_contact_using" msgid="6200708808003692594">"Ստեղծել կոնտակտ`\nօգտագործելով <xliff:g id="NUMBER">%s</xliff:g>-ը"</string>
<string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Հետևյալ մեկ կամ մի քանի հավելվածներին թույլտվություն է անհրաժեշտ՝ այժմ և հետագայում ձեր հաշվի տվյալներն օգտագործելու համար։"</string>
<string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Թույլատրե՞լ"</string>
@@ -1474,7 +1478,7 @@
<string name="number_picker_increment_button" msgid="7621013714795186298">"Ավելացնել"</string>
<string name="number_picker_decrement_button" msgid="5116948444762708204">"Նվազեցնել"</string>
<string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> հպեք և պահեք:"</string>
- <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Սահեցրեք վերև` ավելացնելու համար, և ներքև` նվազեցնելու համար:"</string>
+ <string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"Սահեցրեք վերև՝ ավելացնելու համար, և ներքև՝ նվազեցնելու համար:"</string>
<string name="time_picker_increment_minute_button" msgid="7195870222945784300">"Աճեցնել րոպեն"</string>
<string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Նվազեցնել րոպեն"</string>
<string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Աճեցնել ժամը"</string>
@@ -1598,7 +1602,7 @@
<string name="kg_invalid_puk" msgid="4809502818518963344">"Վերամուտքագրեք ճիշտ PUK ծածկագիրը: Կրկնվող փորձերը ընդմիշտ կկասեցնեն SIM քարտը:"</string>
<string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN ծածկագրերը չեն համընկնում"</string>
<string name="kg_login_too_many_attempts" msgid="699292728290654121">"Չափից շատ սխեմայի փորձեր"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"Ապակողպելու համար` մուտք գործեք ձեր Google հաշվով:"</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"Ապակողպելու համար՝ մուտք գործեք ձեր Google հաշվով:"</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"Օգտանուն (էլփոստ)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"Գաղտնաբառը"</string>
<string name="kg_login_submit_button" msgid="893611277617096870">"Մուտք գործել"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ձայնը բարձրացնե՞լ խորհուրդ տրվող մակարդակից ավել:\n\nԵրկարատև բարձրաձայն լսելը կարող է վնասել ձեր լսողությունը:"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Օգտագործե՞լ Մատչելիության դյուրանցումը։"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Հատուկ գործառույթն օգտագործելու համար սեղմեք և 3 վայրկյան սեղմած պահեք ձայնի ուժգնության երկու կոճակները, երբ գործառույթը միացված է:"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Միացնե՞լ հատուկ գործառույթները"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Միացնե՞լ հատուկ գործառույթների դյուրանցումը"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք հատուկ գործառույթները։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԸնթացիկ գործառույթներ՝\n<xliff:g id="SERVICE">%1$s</xliff:g>\nԸնտրված գործառույթները փոխելու համար անցեք Կարգավորումներ &gt; Հատուկ գործառույթներ։"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Միացնե՞լ <xliff:g id="SERVICE">%1$s</xliff:g> ծառայությունը"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Միացնե՞լ <xliff:g id="SERVICE">%1$s</xliff:g>-ի դյուրանցումը"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ձայնի կարգավորման երկու կոճակները մի քանի վայրկյան սեղմած պահելով կմիացնեք <xliff:g id="SERVICE">%1$s</xliff:g> ծառայությունը, որը հատուկ գործառույթ է։ Դրա արդյունքում սարքի աշխատաեղանակը կարող է փոխվել։\n\nԱյս դյուրանցումը մեկ այլ գործառույթով փոխելու համար անցեք Կարգավորումներ &gt; Հատուկ գործառույթներ։"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Միացնել"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Չմիացնել"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d ժամով</item>
<item quantity="other">%d ժամով</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Մինչև <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Մինչև <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Մինչև ժ. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-ը (հաջորդ զարթուցիչը)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Մինչև չանջատեք"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index f6f50402b3ff..202664f7caab 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ubah setelan audio Anda"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Memungkinkan aplikasi mengubah setelan audio global, misalnya volume dan pengeras suara mana yang digunakan untuk keluaran."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"rekam audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Aplikasi ini dapat merekam audio menggunakan mikrofon kapan saja."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini dapat merekam audio menggunakan mikrofon saat aplikasi sedang digunakan."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"merekam audio di latar belakang"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Aplikasi ini dapat merekam audio menggunakan mikrofon kapan saja."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"kirimkan perintah ke SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Mengizinkan aplikasi mengirim perintah ke SIM. Ini sangat berbahaya."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"kenali aktivitas fisik"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Aplikasi ini dapat mengenali aktivitas fisik Anda."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ambil gambar dan video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Aplikasi ini dapat mengambil foto dan merekam video menggunakan kamera kapan saja."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Aplikasi ini dapat mengambil gambar dan merekam video menggunakan kamera saat aplikasi sedang digunakan."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"mengambil gambar dan merekam video di latar belakang"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Aplikasi ini dapat mengambil gambar dan merekam video menggunakan kamera kapan saja."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Izinkan akses aplikasi atau layanan ke kamera sistem untuk mengambil gambar dan video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Aplikasi sistem atau yang diberi hak istimewa ini dapat mengambil gambar dan merekam video menggunakan kamera sistem kapan saja. Mewajibkan aplikasi untuk memiliki izin android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Izinkan aplikasi atau layanan untuk menerima callback tentang perangkat kamera yang sedang dibuka atau ditutup."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Mengeraskan volume di atas tingkat yang disarankan?\n\nMendengarkan dengan volume keras dalam waktu yang lama dapat merusak pendengaran Anda."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Aksesibilitas?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Aktifkan fitur aksesibilitas?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Aktifkan pintasan untuk fitur aksesibilitas?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nFitur saat ini:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda dapat mengubah fitur yang dipilih di Setelan &gt; Aksesibilitas."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Aktifkan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Aktifkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Menahan kedua tombol volume selama beberapa detik akan mengaktifkan <xliff:g id="SERVICE">%1$s</xliff:g>, yang merupakan fitur aksesibilitas. Tindakan ini dapat mengubah cara kerja perangkat Anda.\n\nAnda dapat mengubah pintasan ini ke fitur lain di Setelan &gt; Aksesibilitas."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktifkan"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Jangan aktifkan"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Selama %d jam</item>
<item quantity="one">Selama 1 jam</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Sampai <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarm berikutnya)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Sampai Anda menonaktifkannya"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 899fa52fbb9f..efb23d4db012 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"breyta hljóðstillingum"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leyfir forriti að breyta altækum hljóðstillingum, s.s. hljóðstyrk og hvaða hátalari er notaður sem úttak."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"taka upp hljóð"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Þetta forrit getur tekið upp hljóð með hljóðnemanum hvenær sem er."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Þetta forrit getur tekið upp hljóð með hljóðnemanum meðan forritið er í notkun."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"taka upp hljóð í bakgrunni"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Þetta forrit getur tekið upp hljóð með hljóðnemanum hvenær sem er."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"senda skipanir til SIM-kortsins"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Leyfir forriti að senda SIM-kortinu skipanir. Þetta er mjög hættulegt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"greina hreyfingu"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Þetta forrit getur greint hreyfingu þína."</string>
<string name="permlab_camera" msgid="6320282492904119413">"taka myndir og myndskeið"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Þetta forrit getur tekið myndir og tekið upp myndskeið með myndavélinni hvenær sem er."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Þetta forrit getur tekið myndir og tekið upp myndskeið með myndavélinni þegar forritið er í notkun."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"taka myndir og taka upp myndskeið í bakgrunni"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Þetta forrit getur tekið myndir og tekið upp myndskeið með myndavélinni hvenær sem er."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Veittu forriti eða þjónustu aðgang að myndavélum kerfis til að taka myndir og myndskeið"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Þetta forgangs- eða kerfisforrit hefur heimild til að taka myndir og taka upp myndskeið með myndavél kerfisins hvenær sem er. Forritið þarf einnig að vera með heimildina android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Leyfa forriti eða þjónustu að taka við svörum um myndavélar sem verið er að opna eða loka."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Hækka hljóðstyrk umfram ráðlagðan styrk?\n\nEf hlustað er á háum hljóðstyrk í langan tíma kann það að skaða heyrnina."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Viltu nota aðgengisflýtileið?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Þegar flýtileiðin er virk er kveikt á aðgengiseiginleikanum með því að halda báðum hljóðstyrkshnöppunum inni í þrjár sekúndur."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Viltu kveikja á aðgengiseiginleikum?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Kveikja á flýtileið fyrir aðgangseiginleika?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Kveikt er á aðgengiseiginleikum þegar báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur. Þetta getur breytt því hvernig tækið virkar.\n\nNúverandi eiginleikar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÞú getur breytt völdum eiginleikum í Stillingar &gt; Aðgengi."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Viltu kveikja á <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Kveikja á flýtileið <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ef báðum hljóðstyrkstökkunum er haldið inni í nokkrar sekúndur er kveikt á aðgengiseiginleikanum <xliff:g id="SERVICE">%1$s</xliff:g>. Þetta getur breytt því hvernig tækið virkar.\n\nÞú getur breytt þessari flýtileið í annan eiginleika í Stillingar &gt; Aðgengi."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Kveikja"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ekki kveikja"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Í %d klst.</item>
<item quantity="other">Í %d klst.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Þangað til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (næsta viðvörun)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Þar til þú slekkur"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 14bfa03bb706..ec446d7e2f57 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modifica impostazioni audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Consente all\'applicazione di modificare le impostazioni audio globali, come il volume e quale altoparlante viene utilizzato per l\'uscita."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"registrazione audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Questa app può registrare audio tramite il microfono in qualsiasi momento."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Questa app può registrare audio tramite il microfono mentre l\'app è in uso."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Registrazione di audio in background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Questa app può registrare audio tramite il microfono in qualsiasi momento."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"invio di comandi alla SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Consente all\'app di inviare comandi alla SIM. Questo è molto pericoloso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"riconoscimento dell\'attività fisica"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Questa app può riconoscere la tua attività fisica."</string>
<string name="permlab_camera" msgid="6320282492904119413">"acquisizione di foto e video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Questa app può scattare foto e registrare video tramite la fotocamera in qualsiasi momento."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Questa app può scattare foto e registrare video tramite la fotocamera mentre l\'app è in uso."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Acquisizione di foto e video in background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Questa app può scattare foto e registrare video tramite la fotocamera in qualsiasi momento."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Consenti a un\'applicazione o a un servizio di accedere alle videocamere del sistema per fare foto e video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Questa app di sistema o con privilegi può scattare foto e registrare video tramite una videocamera di sistema in qualsiasi momento. Richiede che anche l\'app disponga dell\'autorizzazione android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Consenti a un\'applicazione o a un servizio di ricevere callback relativi all\'apertura o alla chiusura di videocamere."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vuoi aumentare il volume oltre il livello consigliato?\n\nL\'ascolto ad alto volume per lunghi periodi di tempo potrebbe danneggiare l\'udito."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usare la scorciatoia Accessibilità?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando la scorciatoia è attiva, puoi premere entrambi i pulsanti del volume per tre secondi per avviare una funzione di accessibilità."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Attivare le funzioni di accessibilità?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vuoi attivare la scorciatoia per le funzioni di accessibilità?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Se tieni premuti entrambi i tasti del volume per qualche secondo, vengono attivate le funzioni di accessibilità. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nFunzioni correnti:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuoi modificare le funzioni selezionate in Impostazioni &gt; Accessibilità."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Attivare <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vuoi attivare la scorciatoia per <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Se tieni premuti entrambi i tasti del volume per qualche secondo verrà attivata la funzione di accessibilità <xliff:g id="SERVICE">%1$s</xliff:g>. Questa operazione potrebbe modificare il funzionamento del dispositivo.\n\nPuoi associare questa scorciatoia a un\'altra funzionalità in Impostazioni &gt; Accessibilità."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Attiva"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Non attivare"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Per %d ore</item>
<item quantity="one">Per 1 ora</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Fino a: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Fino a <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Fino a <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (prossima sveglia)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Fino alla disattivazione"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 2b6d22681f52..874d5770c32e 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"שנה את הגדרות האודיו שלך"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"מאפשר לאפליקציה לשנות הגדרות אודיו גלובליות כמו עוצמת קול ובחירת הרמקול המשמש לפלט."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"הקלט אודיו"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"אפליקציה זו יכולה להשתמש במיקרופון כדי להקליט אודיו בכל עת."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו כאשר היא בשימוש."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"הקלטת אודיו ברקע"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"האפליקציה הזו יכולה להשתמש במיקרופון כדי להקליט אודיו בכל זמן."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"‏שליחת פקודות אל ה-SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"‏מאפשרת ליישום לשלוח פקודות ל-SIM. זוהי הרשאה מסוכנת מאוד."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"זיהוי הפעילות הגופנית"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"האפליקציה מזהה את הפעילות הגופנית שלך."</string>
<string name="permlab_camera" msgid="6320282492904119413">"צלם תמונות וסרטונים"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"אפליקציה זו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים בכל עת."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"האפליקציה הזו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים כאשר היא בשימוש."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"צילום תמונות וסרטונים ברקע"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"האפליקציה הזו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים בכל זמן."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"הרשאת גישה לאפליקציה או לשירות למצלמות המערכת כדי לצלם תמונות וסרטונים"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"‏אפליקציה זו בעלת ההרשאות, או אפליקציית המערכת הזו, יכולה לצלם תמונות ולהקליט סרטונים באמצעות מצלמת מערכת בכל זמן. בנוסף, לאפליקציה נדרשת ההרשאה android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"‏אפליקציה או שירות יוכלו לקבל קריאות חוזרות (callback) כשמכשירי מצלמה ייפתחו או ייסגרו."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"האם להעלות את עוצמת הקול מעל לרמה המומלצת?\n\nהאזנה בעוצמת קול גבוהה למשכי זמן ממושכים עלולה לפגוע בשמיעה."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"להשתמש בקיצור הדרך לתכונת הנגישות?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת הקול למשך שלוש שניות מפעילה את תכונת הנגישות."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"להפעיל את תכונות הנגישות?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"האם להפעיל את מקש הקיצור לתכונות הנגישות?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"‏לחיצה ארוכה על שני לחצני עוצמת הקול למשך מספר שניות מפעילה את תכונות הנגישות. בעקבות זאת, ייתכן שאופן הפעולה של המכשיר ישתנה.\n\nהתכונות הנוכחיות:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nניתן לשנות תכונות נבחרות ב\'הגדרות\' &gt; \'נגישות\'."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"להפעיל את <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"האם להפעיל את מקש הקיצור של <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"‏ניתן ללחוץ על שני מקשי עוצמת הקול למשך מספר שניות כדי להפעיל את <xliff:g id="SERVICE">%1$s</xliff:g>, תכונת נגישות. בעקבות זאת, ייתכן שאופן הפעולה של המכשיר ישתנה.\n\nאפשר לשנות את מקשי הקיצור האלה לתכונה נוספת ב\'הגדרות\' &gt; \'נגישות\'."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"אני רוצה להפעיל"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"לא להפעיל"</string>
@@ -1891,6 +1895,7 @@
<item quantity="other">‏למשך %d שעות</item>
<item quantity="one">למשך שעה אחת</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"עד <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ההתראה הבאה)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"עד הכיבוי"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index fdb505b696dc..32a4dedd6290 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"音声設定の変更"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"音声全般の設定(音量、出力に使用するスピーカーなど)の変更をアプリに許可します。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"録音"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"このアプリは、いつでもマイクを使用して録音できます。"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"このアプリは、ユーザーがアプリを使用している場合にマイクを使用して音声を録音できます。"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"バックグラウンドでの音声の録音"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"このアプリは、いつでもマイクを使用して音声を録音できます。"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMへのコマンド送信"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMにコマンドを送信することをアプリに許可します。この許可は非常に危険です。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"身体活動の認識"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"このアプリで身体活動が認識されるようにします。"</string>
<string name="permlab_camera" msgid="6320282492904119413">"写真と動画の撮影"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"このアプリは、いつでもカメラを使用して写真や動画を撮影できます。"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"このアプリは、ユーザーがアプリを使用している場合にカメラを使用して写真や動画を撮影できます。"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"バックグラウンドでの写真と動画の撮影"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"このアプリは、いつでもカメラを使用して写真や動画を撮影できます。"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"写真と動画を撮影するには、システムカメラへのアクセスをアプリまたはサービスに許可してください"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"権限を付与されたこのアプリまたはシステムアプリは、いつでもシステムカメラを使用して写真と動画を撮影できます。アプリには android.permission.CAMERA 権限も必要です"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"カメラデバイスが起動または終了したときにコールバックを受け取ることを、アプリまたはサービスに許可してください。"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"推奨レベルを超えるまで音量を上げますか?\n\n大音量で長時間聞き続けると、聴力を損なう恐れがあります。"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ユーザー補助機能のショートカットの使用"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ショートカットが ON の場合、両方の音量ボタンを 3 秒ほど長押しするとユーザー補助機能が起動します。"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ユーザー補助機能を ON にしますか?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ユーザー補助機能のショートカットを ON にしますか?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\n現在の機能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n選択した機能は [設定] &gt; [ユーザー補助] で変更できます。"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> を ON にしますか?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> のショートカットを ON にしますか?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"音量大と音量小の両方のボタンを数秒ほど長押しすると、ユーザー補助機能の <xliff:g id="SERVICE">%1$s</xliff:g> が ON になります。この機能が ON になると、デバイスの動作が変わることがあります。\n\nこのショートカットは [設定] &gt; [ユーザー補助] で別の機能に変更できます。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ON にする"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ON にしない"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d時間</item>
<item quantity="one">1時間</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>まで"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>まで"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(次のアラーム)まで"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"自分が OFF にするまで"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 3d0d88b9e823..349bd167ff56 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"თქვენი აუდიო პარამეტრების შეცვლა"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"აპს შეეძლება აუდიოს გლობალური პარამეტრების შეცვლა. მაგ.: ხმის სიმაღლე და რომელი დინამიკი გამოიყენება სიგნალის გამოსტანად."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"აუდიოს ჩაწერა"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ამ აპს ნებისმიერ დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ამ აპს გამოყენების დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ფონურად ჩაწერს აუდიოს"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ამ აპს ნებისმიერ დროს შეუძლია მიკროფონით აუდიოს ჩაწერა."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ბრძანებების SIM-ზე გაგზავნა"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"აპისთვის ნების დართვა გაუგზავნოს ბრძანებები SIM-ბარათს. ეს ძალიან საშიშია."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ფიზიკური აქტივობის ამოცნობა"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ამ აპს შეუძლია თქვენი ფიზიკური აქტივობის ამოცნობა."</string>
<string name="permlab_camera" msgid="6320282492904119413">"სურათებისა და ვიდეოების გადაღება"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ამ აპს ნებისმიერ დროს შეუძლია კამერით სურათების გადაღება და ვიდეოების ჩაწერა."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ამ აპს გამოყენების დროს შეუძლია კამერით სურათების გადაღება და ვიდეოების ჩაწერა."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ფონურად გადაიღებს სურათებს და ჩაწერს ვიდეოს"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ამ აპს ნებისმიერ დროს შეუძლია კამერით სურათების გადაღება და ვიდეოების ჩაწერა."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ნება დაერთოს აპლიკაციას ან სერვისს, ჰქონდეს წვდომა სისტემის კამერებზე სურათების და ვიდეოების გადასაღებად"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ამ პრივილეგირებულ ან სისტემის აპს შეუძლია ფოტოების გადაღება და ვიდეოების ჩაწერა ნებისმიერ დროს სისტემის კამერის გამოყენებით. საჭიროა, რომ აპს ჰქოდეს android.permission.CAMERA ნებართვაც"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ნება დაერთოს აპლიკაციას ან სერვისს, მიიღოს გადმორეკვები კამერის მოწყობილობის გახსნის ან დახურვისას."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"გსურთ ხმის რეკომენდებულ დონეზე მაღლა აწევა?\n\nხანგრძლივად ხმამაღლა მოსმენით შესაძლოა სმენადობა დაიზიანოთ."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"გსურთ მარტივი წვდომის მალსახმობის გამოყენება?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"თუ მალსახმობი ჩართულია, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"გსურთ, ჩართოთ მარტივი წვდომის ფუნქციები?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ჩაირთოს მარტივი წვდომის ფუნქციების მალსახმობი?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ხმის ორივე ღილაკზე ხანგრძლივად დაჭერა რამდენიმე წამის განმავლობაში ჩართავს მარტივი წვდომის ფუნქციებს. ამ ქმედებამ შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამჟამინდელი ფუნქციები:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nარჩეული ფუნქციების შეცვლა შესაძლებელია აქ: პარამეტრები &gt; მარტივი წვდომა."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ჩაირთოს <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ჩაირთოს <xliff:g id="SERVICE">%1$s</xliff:g>-ის მალსახმობი?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ხმის ორივე ღილაკზე რამდენიმე წამის განმავლობაში დაჭერით ჩაირთვება <xliff:g id="SERVICE">%1$s</xliff:g>, რომელიც მარტივი წვდომის ფუნქციაა. ამან შეიძლება შეცვალოს თქვენი მოწყობილობის მუშაობის პრინციპი.\n\nამ მალსახმობის შეცვლა სხვა ფუნქციით შეგიძლიათ აქ: პარამეტრები &gt; აპები."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ჩართვა"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"არ ჩაირთოს"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d სთ.</item>
<item quantity="one">1 სთ.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>-მდე (შემდეგი მაღვიძარა)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"გამორთვამდე"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 9798cc9db1b8..4d4eef0378dd 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио параметрлерін өзгерту"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Қолданбаға дыбыс қаттылығы және аудио шығыс үндеткішін таңдау сияқты жаһандық аудио параметрлерін өзгерту мүмкіндігін береді."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жазу"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Бұл қолданба кез келген уақытта микрофон арқылы аудиомазмұн жаза алады."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бұл қолданба жұмыс барысында микрофон арқылы аудиомазмұн жаза алады."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондық режимде аудиомазмұн жазу"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бұл қолданба кез келген уақытта микрофон арқылы аудиомазмұн жаза алады."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM картасына пәрмендер жіберу"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Қолданбаға SIM картасына пәрмен жіберу мүмкіндігін береді. Бұл өте қауіпті."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"физикалық әрекетті тану"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Бұл қолданба физикалық әрекетті тани алады."</string>
<string name="permlab_camera" msgid="6320282492904119413">"фотосурет жасау және бейне жазу"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Бұл қолданба кез келген уақытта камерамен суретке түсіруі және бейнелерді жазуы мүмкін."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Бұл қолданба жұмыс барысында камерамен суретке түсіріп, бейнелер жаза алады."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Фондық режимде сурет пен бейне түсіру"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Бұл қолданба кез келген уақытта камерамен суретке түсіріп, бейнелер жаза алады."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Суретке немесе бейнеге түсіру үшін қолданбаға немесе қызметке жүйелік камераларды пайдалануға рұқсат беру"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Осы айрықша немесе жүйе қолданбасы кез келген уақытта жүйелік камера арқылы суретке не бейнеге түсіре алады. Қолданбаға android.permission.CAMERA рұқсаты қажет болады."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Қолданбаға не қызметке ашылып не жабылып жатқан камера құрылғылары туралы кері шақыру алуға рұқсат ету"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Түймелер тіркесімі қосулы кезде, екі дыбыс түймесін 3 секунд басып тұрсаңыз, \"Арнайы мүмкіндіктер\" функциясы іске қосылады."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Арнайы мүмкіндіктер іске қосылсын ба?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Арнайы мүмкіндіктердің жылдам пәрмені іске қосылсын ба?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, арнайы мүмкіндіктер іске қосылады. Бұл – құрылғының жұмысына әсер етуі мүмкін.\n\nҚазіргі функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТаңдалған функцияларды \"Параметрлер &gt; Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> қосылсын ба?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> жылдам пәрмені іске қосылсын ба?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дыбыс деңгейі пернелерін бірнеше секунд басып тұрсаңыз, <xliff:g id="SERVICE">%1$s</xliff:g> арнайы қызметі іске қосылады. Бұл – құрылғының жүмысына әсер етуі мүмкін.\n\nБұл таңбашаны басқа функцияға \"Параметрлер &gt; Арнайы мүмкіндіктер\" бөлімінен өзгерте аласыз."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Қосылсын"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Қосылмасын"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d сағат</item>
<item quantity="one">1 сағат</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> дейін (келесі дабыл)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Өшірілгенге дейін"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index a0b8faa12f78..3ebd7364b2c9 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ប្ដូរ​ការ​កំណត់​អូឌីយូ​របស់​អ្នក"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ឲ្យ​កម្មវិធី​កែ​ការ​កំណត់​សំឡេង​សកល ដូច​ជា​កម្រិត​សំឡេង និង​អូប៉ាល័រ​ដែល​បាន​ប្រើ​សម្រាប់​លទ្ធផល។"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ថត​សំឡេង"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"កម្មវិធី​នេះ​អាច​ថត​សំឡេង​ដោយ​ប្រើ​មីក្រូហ្វូន​បាន​គ្រប់​ពេល។"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"កម្មវិធី​នេះ​អាច​ថតសំឡេងដោយប្រើមីក្រូហ្វូន នៅពេលកំពុងប្រើប្រាស់កម្មវិធី។"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ថតសំឡេងនៅផ្ទៃខាងក្រោយ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"កម្មវិធី​នេះ​អាច​ថត​សំឡេង​ដោយ​ប្រើ​មីក្រូហ្វូន​បាន​គ្រប់​ពេល។"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ផ្ញើពាក្យបញ្ជាទៅស៊ីមកាត"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ឲ្យ​កម្មវិធី​ផ្ញើ​ពាក្យ​បញ្ជា​ទៅ​ស៊ីម​កាត។ វា​គ្រោះ​ថ្នាក់​ណាស់។"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ស្គាល់​សកម្មភាព​រាងកាយ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"កម្មវិធីនេះ​អាចស្គាល់សកម្មភាព​រាងកាយ​របស់អ្នក។"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ថត​រូប និងវីដេអូ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"កម្មវិធី​នេះ​អាច​ថត​រូប​ និង​ថត​វីដេអូ​ ដោយ​ប្រើ​កាមេរ៉ា​បាន​គ្រប់​ពេល​។"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"កម្មវិធី​នេះ​អាច​ថត​រូប​ និង​វីដេអូ​ដោយ​ប្រើ​កាមេរ៉ា នៅពេលកំពុងប្រើប្រាស់កម្មវិធី។"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ថតរូបភាព និងវីដេអូនៅផ្ទៃខាងក្រោយ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"កម្មវិធី​នេះ​អាច​ថត​រូប​ និង​វីដេអូ​ដោយ​ប្រើ​កាមេរ៉ា​បាន​គ្រប់​ពេល​។"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"អនុញ្ញាតឱ្យកម្មវិធី ឬសេវាកម្ម​ចូលប្រើកាមេរ៉ា​ប្រព័ន្ធ ដើម្បីថតរូប និង​ថតវីដេអូ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"កម្មវិធីប្រព័ន្ធ ឬកម្មវិធីដែលមានសិទ្ធិអនុញ្ញាត​នេះអាចថត​រូប និង​ថតវីដេអូ ដោយប្រើ​កាមេរ៉ា​ប្រព័ន្ធបាន​គ្រប់ពេល។ តម្រូវឱ្យមាន​ការអនុញ្ញាត android.permission.CAMERA ដើម្បីឱ្យ​កម្មវិធីអាចធ្វើ​សកម្មភាព​បានផងដែរ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"អនុញ្ញាតឱ្យកម្មវិធី ឬសេវាកម្ម​ទទួលការហៅត្រឡប់វិញអំពី​កាមេរ៉ាដែលកំពុងបិទ ឬបើក។"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"បង្កើន​កម្រិត​សំឡេង​លើស​ពី​កម្រិត​បាន​ផ្ដល់​យោបល់?\n\nការ​ស្ដាប់​នៅ​កម្រិត​សំឡេង​ខ្លាំង​យូរ​អាច​ធ្វើឲ្យ​ខូច​ត្រចៀក។"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ប្រើប្រាស់​ផ្លូវកាត់​ភាព​ងាយស្រួល?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"នៅពេលបើក​ផ្លូវកាត់ ការចុច​ប៊ូតុង​កម្រិតសំឡេង​ទាំងពីរ​រយៈពេល 3 វិនាទី​នឹង​ចាប់ផ្តើម​មុខងារ​ភាពងាយប្រើ។"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"បើក​មុខងារ​ភាពងាយប្រើ​ឬ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"បើក​ផ្លូវកាត់​សម្រាប់មុខងារ​ភាពងាយស្រួលឬ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ការសង្កត់​គ្រាប់ចុច​កម្រិតសំឡេង​ទាំងពីរ​ឱ្យជាប់​រយៈពេល​ពីរបីវិនាទី​នឹងបើក​មុខងារ​ភាពងាយប្រើ។ ការធ្វើ​បែបនេះ​អាចផ្លាស់ប្ដូរ​របៀបដែល​ឧបករណ៍​របស់អ្នក​ដំណើរការ។\n\nមុខងារ​បច្ចុប្បន្ន៖\n<xliff:g id="SERVICE">%1$s</xliff:g>\nអ្នកអាច​ប្ដូរ​មុខងារ​ដែលបាន​ជ្រើសរើស​នៅក្នុង​ការកំណត់ &gt; ភាពងាយស្រួល។"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"បើក <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"បើក​ផ្លូវកាត់ <xliff:g id="SERVICE">%1$s</xliff:g> ឬ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ការសង្កត់​គ្រាប់ចុច​កម្រិតសំឡេង​ទាំងពីរ​ឱ្យជាប់​រយៈពេល​ពីរបីវិនាទី​នឹងបើក <xliff:g id="SERVICE">%1$s</xliff:g> ដែលជា​មុខងារ​ភាពងាយប្រើ។ ការធ្វើ​បែបនេះ​អាចផ្លាស់ប្ដូរ​របៀបដែល​ឧបករណ៍​របស់អ្នក​ដំណើរការ។\n\nអ្នកអាច​ប្ដូរផ្លូវកាត់​នេះទៅ​មុខងារ​ផ្សេងទៀត​នៅក្នុង​ការកំណត់ &gt; ភាពងាយស្រួល។"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"បើក"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"កុំបើក"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">អស់រយៈពេល %d ម៉ោង</item>
<item quantity="one">អស់រយៈពេល 1 ម៉ោង</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"រហូត​ដល់​ម៉ោង <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"រហូត​ដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"រហូតដល់ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ម៉ោងរោទិ៍បន្ទាប់)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"រហូតទាល់តែ​អ្នកបិទ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index c7786aebe9e4..0cf6f494677b 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ನಿಮ್ಮ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ವಾಲ್ಯೂಮ್ ರೀತಿಯ ಮತ್ತು ಔಟ್‍‍ಪುಟ್‍‍ಗಾಗಿ ಯಾವ ಸ್ಪೀಕರ್ ಬಳಸಬೇಕು ಎಂಬ ರೀತಿಯ ಜಾಗತಿಕ ಆಡಿಯೊ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೈಕ್ರೋಫೋನ್ ಬಳಸುವ ಮೂಲಕ ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ ಈ ಆ್ಯಪ್ ಮೈಕ್ರೊಫೋನ್ ಬಳಸಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಆಡಿಯೊವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ಈ ಆ್ಯಪ್ ಮೈಕ್ರೋಫೋನ್ ಬಳಸುವ ಮೂಲಕ ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಆಡಿಯೊ ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ಸಿಮ್‌ಗೆ ಆಜ್ಞೆಗಳನ್ನು ಕಳುಹಿಸಿ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ಸಿಮ್‌ ಗೆ ಆದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ತುಂಬಾ ಅಪಾಯಕಾರಿ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಿ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ದೈಹಿಕ ಚಟುವಟಿಕೆಯನ್ನು ಗುರುತಿಸಬಹುದು."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ಚಿತ್ರಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಯಾವ ಸಮಯದಲ್ಲಾದರೂ ಕ್ಯಾಮರಾ ಬಳಸಿಕೊಂಡು ಚಿತ್ರಗಳು ಮತ್ತು ವಿಡಿಯೋಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ, ಈ ಆ್ಯಪ್ ಕ್ಯಾಮರಾ ಬಳಸಿ ಚಿತ್ರಗಳನ್ನು ತೆಗೆಯಬಹುದು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಚಿತ್ರಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಿ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ಈ ಆ್ಯಪ್ ಯಾವ ಸಮಯದಲ್ಲಾದರೂ ಕ್ಯಾಮರಾ ಬಳಸಿಕೊಂಡು ಚಿತ್ರಗಳು ಮತ್ತು ವಿಡಿಯೋಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ಫೋಟೋಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಲು ಸಿಸ್ಟಂ ಕ್ಯಾಮರಾಗಳಿಗೆ ಅಪ್ಲಿಕೇಶನ್ ಅಥವಾ ಸೇವಾ ಪ್ರವೇಶವನ್ನು ಅನುಮತಿಸಿ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ಈ ವಿಶೇಷ ಅಥವಾ ಸಿಸ್ಟಂ ಆ್ಯಪ್, ಯಾವುದೇ ಸಮಯದಲ್ಲಾದರೂ ಸಿಸ್ಟಂ ಕ್ಯಾಮರಾವನ್ನು ಬಳಸಿಕೊಂಡು ಫೋಟೋಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ವೀಡಿಯೋಗಳನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬಹುದು. ಆ್ಯಪ್‌ಗೆ android.permission.CAMERA ಅನುಮತಿಯ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ಕ್ಯಾಮರಾ ಸಾಧನಗಳನ್ನು ತೆರೆಯುತ್ತಿರುವ ಅಥವಾ ಮುಚ್ಚುತ್ತಿರುವ ಕುರಿತು ಕಾಲ್‌ಬ್ಯಾಕ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಆ್ಯಪ್‌ ಅಥವಾ ಸೇವೆಗೆ ಅನುಮತಿಸಿ."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ವಾಲ್ಯೂಮ್‌ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾದ ಮಟ್ಟಕ್ಕಿಂತಲೂ ಹೆಚ್ಚು ಮಾಡುವುದೇ?\n\nದೀರ್ಘ ಅವಧಿಯವರೆಗೆ ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್‌ನಲ್ಲಿ ಆಲಿಸುವುದರಿಂದ ನಿಮ್ಮ ಆಲಿಸುವಿಕೆ ಸಾಮರ್ಥ್ಯಕ್ಕೆ ಹಾನಿಯುಂಟು ಮಾಡಬಹುದು."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ಪ್ರವೇಶಿಸುವಿಕೆ ಶಾರ್ಟ್‌ಕಟ್ ಬಳಸುವುದೇ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್‌ಗಳನ್ನು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಿದರೆ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವೊಂದು ಪ್ರಾರಂಭವಾಗುತ್ತದೆ."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಬೇಕೇ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳಿಗಾಗಿ ಶಾರ್ಟ್‌ಕಟ್ ಆನ್ ಮಾಡಬೇಕೇ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳು ಆನ್ ಆಗುತ್ತವೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\n ಪ್ರಸ್ತುತ ವೈಶಿಷ್ಟ್ಯಗಳು:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿ ಆಯ್ದ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನೀವು ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ಅನ್ನು ಆನ್‌ ಮಾಡುವುದೇ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ಶಾರ್ಟ್‌ಕಟ್ <xliff:g id="SERVICE">%1$s</xliff:g>ಆನ್‌ ಮಾಡಬೇಕೇ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ಎರಡೂ ವಾಲ್ಯೂಮ್ ಕೀಗಳನ್ನು ಕೆಲವು ಸೆಕೆಂಡುಗಳ ಕಾಲ ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳುವುದರಿಂದ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯವಾದ <xliff:g id="SERVICE">%1$s</xliff:g> ಆನ್ ಆಗುತ್ತದೆ. ಇದು ನಿಮ್ಮ ಸಾಧನವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಬಹುದು.\n\nನೀವು ಈ ಶಾರ್ಟ್‌ಕಟ್ ಅನ್ನು ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಮತ್ತು ಅಕ್ಸೆಸಿಬಿಲಿಟಿಯಲ್ಲಿನ ಮತ್ತೊಂದು ವೈಶಿಷ್ಟ್ಯಕ್ಕೆ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ಆನ್ ಮಾಡಿ"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ಆನ್ ಮಾಡಬೇಡಿ"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d ಗಂಟೆಗೆ</item>
<item quantity="other">%d ಗಂಟೆಗೆ</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ವರೆಗೆ (ಮುಂದಿನ ಅಲಾರಮ್)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ನೀವು ಆಫ್ ಮಾಡುವವರೆಗೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 78c3286eecf3..3dfdf3d4ebd1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"오디오 설정 변경"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"오디오 녹음"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"이 앱은 언제든지 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"앱을 사용하는 동안 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"백그라운드에서 오디오 녹음"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"언제든지 앱에서 마이크를 사용하여 오디오를 녹음할 수 있습니다."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM 카드로 명령 전송"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"앱이 SIM에 명령어를 전송할 수 있도록 허용합니다. 이 기능은 매우 신중히 허용해야 합니다."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"신체 활동 확인"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"이 앱에서 내 신체 활동을 확인할 수 있습니다."</string>
<string name="permlab_camera" msgid="6320282492904119413">"사진과 동영상 찍기"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"이 앱은 언제든지 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"앱을 사용하는 동안 앱에서 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"백그라운드에서 사진 촬영 및 동영상 녹화"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"언제든지 앱에서 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"사진 및 동영상 촬영을 위해 애플리케이션 또는 서비스에서 시스템 카메라에 액세스하도록 허용"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"이 권한이 있는 시스템 앱은 언제든지 시스템 카메라를 사용하여 사진을 촬영하고 동영상을 녹화할 수 있습니다. 또한 앱에 android.permission.CAMERA 권한이 필요합니다."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"애플리케이션 또는 서비스에서 카메라 기기 열림 또는 닫힘에 대한 콜백을 수신하도록 허용"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"권장 수준 이상으로 볼륨을 높이시겠습니까?\n\n높은 볼륨으로 장시간 청취하면 청력에 손상이 올 수 있습니다."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"접근성 단축키를 사용하시겠습니까?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"단축키가 사용 설정된 경우 볼륨 버튼 두 개를 동시에 3초간 누르면 접근성 기능이 시작됩니다."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"접근성 기능을 사용하시겠습니까?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"접근성 기능 바로가기를 사용 설정하시겠습니까?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"볼륨 키 2개를 몇 초 동안 길게 누르면 접근성 기능이 사용 설정됩니다. 이때 기기 작동 방식이 달라질 수 있습니다.\n\n현재 기능:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n설정 &gt; 접근성에서 선택한 기능을 변경할 수 있습니다."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> 바로가기를 사용 설정하시겠습니까?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"볼륨 키 2개를 몇 초 동안 길게 누르면 <xliff:g id="SERVICE">%1$s</xliff:g> 접근성 기능이 사용 설정됩니다. 이렇게 되면 기기 작동 방식이 달라질 수 있습니다.\n\n설정 &gt; 접근성에서 이 단축키를 다른 기능으로 변경할 수 있습니다."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"사용"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"사용 안 함"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d시간 동안</item>
<item quantity="one">1시간 동안</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>까지"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>까지"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(다음 알람)까지"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"사용 중지할 때까지"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 162b4d3fdb26..012acf53a3a4 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"аудио жөндөөлөрүңүздү өзгөртүңүз"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Колдонмого үн деңгээли жана кайсы динамик аркылуу үн чыгарылышы керек сыяктуу түзмөктүн аудио тууралоолорун өзгөртүүгө уруксат берет."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио жаздыруу"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Бул колдонмо каалаган убакта микрофон менен аудио файлдарды жаздыра алат."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Бул колдонмо иштеп жатканда микрофон менен аудио файлдарды жаздыра алат."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Фондо аудио жаздыруу"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Бул колдонмо каалаган убакта микрофон менен аудио файлдарды жаздыра алат."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-картага буйруктарды жөнөтүү"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Колдонмого SIM-картага буйруктарды жөнөтүү мүмкүнчүлүгүн берет. Бул абдан кооптуу."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"кыймыл-аракетти аныктоо"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Бул колдонмо кыймыл-аракетиңизди аныктап турат."</string>
<string name="permlab_camera" msgid="6320282492904119413">"сүрөт жана видео тартуу"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Бул колдонмо каалаган убакта камера менен сүрөт же видеолорду тарта алат."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Бул колдонмо иштеп жатканда камера менен сүрөт же видеолорду тарта алат."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Фондо сүрөткө жана видеого тартат"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Бул колдонмо каалаган убакта камера менен сүрөт же видеолорду тарта алат."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Сүрөткө тартып, видеолорду жаздыруу үчүн бул колдонмого же кызматка тутумдун камерасын колдонууга уруксат берүү"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Бул артыкчылыктуу тутум колдонмосу тутумдун камерасын каалаган убакта колдонуп, сүрөткө тартып, видео жаздыра алат. Ошондой эле колдонмого android.permission.CAMERA уруксатын берүү керек"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Колдонмого же кызматка камера ачылып же жабылып жатканда чалууларды кабыл алууга уруксат берүү."</string>
@@ -1202,7 +1206,7 @@
<string name="android_upgrading_apk" msgid="1339564803894466737">"<xliff:g id="NUMBER_1">%2$d</xliff:g> ичинен <xliff:g id="NUMBER_0">%1$d</xliff:g> колдонмо ыңгайлаштырылууда."</string>
<string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> даярдалууда."</string>
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Колдонмолорду иштетип баштоо"</string>
- <string name="android_upgrading_complete" msgid="409800058018374746">"Жүктөө аякталууда."</string>
+ <string name="android_upgrading_complete" msgid="409800058018374746">"Жүктөлүүдө"</string>
<string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> иштеп жатат"</string>
<string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Оюнга кайтуу үчүн таптаңыз"</string>
<string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Оюн тандоо"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Сунушталган деңгээлден да катуулатып уккуңуз келеби?\n\nМузыканы узакка чейин катуу уксаңыз, угууңуз начарлап кетиши мүмкүн."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең 3 секунддай коё бербей басып туруңуз."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Атайын мүмкүнчүлүктөрдү иштетесизби?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Атайын мүмкүнчүлүктөрдүн ыкчам баскычын иштетесизби?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Атайын мүмкүнчүлүктөр функциясын иштетүү үчүн, үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nУчурдагы функциялар:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТандалган функцияларды өзгөртүү үчүн, Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> күйгүзүлсүнбү?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ыкчам баскычын иштетесизби?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> кызматын иштетүү үчүн, үндү чоңойтуп/кичирейтүү баскычтарын бир нече секунд коё бербей басып туруңуз. Ушуну менен, түзмөгүңүз бир аз башкача иштеп калышы мүмкүн.\n\nБаскычтардын ушул айкалышын башка функцияга дайындоо үчүн, Жөндөөлөр &gt; Атайын мүмкүнчүлүктөр бөлүмүнө өтүңүз."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ооба"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Жок"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d саатка</item>
<item quantity="one">1 саатка</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> чейин (кийинки ойготкуч)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Бул функция өчүрүлгөнгө чейин"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 44511398c694..315ee322fa7c 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ປ່ຽນການຕັ້ງຄ່າສຽງຂອງທ່ານ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ອະນຸຍາດໃຫ້ແອັບຯແກ້ໄຂການຕັ້ງຄ່າສຽງສ່ວນກາງ ເຊັ່ນ: ລະດັບສຽງ ແລະລຳໂພງໃດທີ່ຖືກໃຊ້ສົ່ງສຽງອອກ."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ບັນທຶກສຽງ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"This app can record audio using the microphone at any time."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນໃນຂະນະທີ່ກຳລັງໃຊ້ແອັບຢູ່ໄດ້."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ບັນທຶກສຽງໃນພື້ນຫຼັງ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ແອັບນີ້ສາມາດບັນທຶກສຽງດ້ວຍໄມໂຄຣໂຟນຕອນໃດກໍໄດ້."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ສົ່ງ​ຄຳ​ສັ່ງ​ຫາ SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ອະນຸຍາດໃຫ້ແອັບຯສົ່ງຄຳສັ່ງຫາ SIM. ສິ່ງນີ້ອັນຕະລາຍຫຼາຍ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ຈຳແນກກິດຈະກຳທາງກາຍ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ແອັບນີ້ສາມາດຈັດລະບຽບການເຄື່ອນໄຫວທາງກາຍຂອງທ່ານໄດ້."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ຖ່າຍຮູບ ແລະວິດີໂອ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"This app can take pictures and record videos using the camera at any time."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ແອັບນີ້ສາມາດຖ່າຍຮູບ ແລະ ບັນທຶກວິດີໂອໂດຍໃຊ້ກ້ອງຖ່າຍຮູບໃນເວລາທີ່ກຳລັງໃຊ້ແອັບຢູ່ໄດ້."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ຖ່າຍຮູບ ແລະ ວິດີໂອໃນພື້ນຫຼັງ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ແອັບນີ້ສາມາດຖ່າຍຮູບ ແລະ ບັນທຶກວິດີໂອໂດຍໃຊ້ກ້ອງຖ່າຍຮູບຕອນໃດກໍໄດ້."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນ ຫຼື ບໍລິການເຂົ້າເຖິງກ້ອງຂອງລະບົບໄດ້ເພື່ອຖ່າຍຮູບ ແລະ ວິດີໂອ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ສິດ ຫຼື ແອັບລະບົບນີ້ສາມາດຖ່າຍຮູບ ແລະ ບັນທຶກວິດີໂອໂດຍໃຊ້ກ້ອງຂອງລະບົບຕອນໃດກໍໄດ້. ຕ້ອງໃຊ້ສິດອະນຸຍາດ android.permission.CAMERA ໃຫ້ແອັບຖືນຳ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນ ຫຼື ບໍລິການຮັບການເອີ້ນກັບກ່ຽວກັບອຸປະກອນກ້ອງຖືກເປີດ ຫຼື ປິດໄດ້."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ເພີ່ມ​ລະ​ດັບ​ສຽງ​ໃຫ້​ເກີນກວ່າ​ລະ​ດັບ​ທີ່​ແນະ​ນຳ​ບໍ?\n\n​ການ​ຮັບ​ຟັງ​ສຽງ​ໃນ​ລະ​ດັບ​ທີ່​ສູງ​ເປັນ​ໄລ​ຍະ​ເວ​ລາ​ດົນ​​ອາດ​ເຮັດ​ໃຫ້​ການ​ຟັງ​ຂອງ​ທ່ານ​ມີ​ບັນ​ຫາ​ໄດ້."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ໃຊ້ປຸ່ມລັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ເມື່ອເປີດໃຊ້ທາງລັດແລ້ວ, ການກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ 3 ວິນາທີຈະເປັນການເລີ່ມຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ເປີດໃຊ້ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ເປີດໃຊ້ທາງລັດສຳລັບຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nຄຸນສົມບັດປັດຈຸບັນ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nທ່ານສາມາດປ່ຽນຄຸນສົມບັດທີ່ເລືອກໄດ້ໃນການຕັ້ງຄ່າ &gt; ການຊ່ວຍເຂົ້າເຖິງ."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ເປີດໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ເປີດໃຊ້ທາງລັດ <xliff:g id="SERVICE">%1$s</xliff:g> ບໍ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ກົດປຸ່ມລະດັບສຽງທັງສອງຄ້າງໄວ້ສອງສາມວິນາທີເພື່ອເປີດໃຊ້ <xliff:g id="SERVICE">%1$s</xliff:g>, ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ນີ້ອາດປ່ຽນວິທີການເຮັດວຽກຂອງອຸປະກອນທ່ານ.\n\nທ່ານສາມາດປ່ຽນທາງລັດນີ້ເປັນຄຸນສົມບັດອື່ນໄດ້ໃນການຕັ້ງຄ່າ &gt; ການຊ່ວຍເຂົ້າເຖິງ."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ເປີດໃຊ້"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ບໍ່ເປີດ"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">ເປັນ​ເວ​ລາ %d ຊມ</item>
<item quantity="one">ເປັນ​ເວ​ລາ 1 ຊມ</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"ຈົນຮອດ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"ຈົນ​ຮອດ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"ຈົນ​ກ​່​ວາ <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ສັນ​ຍານ​ເຕືອນ​ຕໍ່ໄປ​)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ຈົນກວ່າທ່ານຈະປິດ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index fa0797846de4..1d6b2c7dbb45 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"keisti garso nustatymus"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Leidžiama programai keisti visuotinius garso nustatymus, pvz., garsumą ir tai, kuris garsiakalbis naudojamas išvesčiai."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"įrašyti garsą"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ši programa gali bet kada įrašyti garsą naudodama mikrofoną."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ši programa gali įrašyti garsą naudodama mikrofoną, kol programa naudojama."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"įrašyti garsą fone"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ši programa gali bet kada įrašyti garsą naudodama mikrofoną."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"siųsti komandas į SIM kortelę"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Programai leidžiama siųsti komandas į SIM kortelę. Tai labai pavojinga."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"atpažinti fizinę veiklą"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ši programa gali atpažinti jūsų fizinę veiklą."</string>
<string name="permlab_camera" msgid="6320282492904119413">"fotografuoti ir filmuoti"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ši programa gali bet kada fotografuoti ir įrašyti vaizdo įrašų naudodama fotoaparatą."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ši programa gali fotografuoti ir įrašyti vaizdo įrašų naudodama fotoaparatą, kol programa naudojama."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"fotografuoti ir įrašyti vaizdo įrašų fone"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ši programa gali bet kada fotografuoti ir įrašyti vaizdo įrašų naudodama fotoaparatą."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Suteikti programai arba paslaugai prieigą prie sistemos fotoaparatų, kad būtų galima daryti nuotraukas ir įrašyti vaizdo įrašus"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ši privilegijuota arba sistemos programa gali daryti nuotraukas ir įrašyti vaizdo įrašus naudodama sistemos fotoaparatą bet kuriuo metu. Programai taip pat būtinas leidimas „android.permission.CAMERA“"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Leisti programai ar paslaugai sulaukti atgalinio skambinimo, kai atidaromas ar uždaromas fotoaparatas."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Padidinti garsą daugiau nei rekomenduojamas lygis?\n\nIlgai klausydami dideliu garsu galite pažeisti klausą."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Naudoti spartųjį pritaikymo neįgaliesiems klavišą?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kai spartusis klavišas įjungtas, paspaudus abu garsumo mygtukus ir palaikius 3 sekundes bus įjungta pritaikymo neįgaliesiems funkcija."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Įjungti pritaikymo neįgaliesiems funkcijas?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Įjungti pritaikymo neįgaliesiems funkcijų spartųjį klavišą?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiamos pritaikymo neįgaliesiems funkcijos. Tai gali pakeisti įrenginio veikimą.\n\nDabartinės funkcijos:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPasirinktas funkcijas galite pakeisti skiltyje „Nustatymai“ &gt; „Pritaikomumas“."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Įjungti „<xliff:g id="SERVICE">%1$s</xliff:g>“?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Įjungti „<xliff:g id="SERVICE">%1$s</xliff:g>“ spartųjį klavišą?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Paspaudus abu garsumo klavišus ir palaikius kelias sekundes įjungiama pritaikymo neįgaliesiems funkcija „<xliff:g id="SERVICE">%1$s</xliff:g>“. Tai gali pakeisti įrenginio veikimą.\n\nGalite pakeisti šį spartųjį klavišą į kitą funkciją skiltyje „Nustatymai“ &gt; „Pritaikomumas“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Įjungti"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Neįjungti"</string>
@@ -1891,6 +1895,7 @@
<item quantity="many">%d val.</item>
<item quantity="other">%d val.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Iki <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kitas signalas)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Kol išjungsite"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index e8107fb0c744..ba790a0714c0 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"mainīt audio iestatījumus"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ļauj lietotnei mainīt globālos audio iestatījumus, piemēram, skaļumu un izejai izmantoto skaļruni."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ierakstīt audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Šī lietotne jebkurā brīdī var ierakstīt audio, izmantojot mikrofonu."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Šī lietotne var ierakstīt audio, izmantojot mikrofonu, kamēr lietotne tiek izmantota."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ierakstīt audio fonā"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Šī lietotne var jebkurā brīdī ierakstīt audio, izmantojot mikrofonu."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"Sūtīt komandas SIM kartei"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ļauj lietotnei sūtīt komandas uz SIM karti. Tas ir ļoti bīstami!"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"noteikt fiziskās aktivitātes"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Šī lietotne var noteikt jūsu fiziskās aktivitātes."</string>
<string name="permlab_camera" msgid="6320282492904119413">"uzņemt attēlus un videoklipus"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Šī lietotne jebkurā brīdī var uzņemt attēlus un ierakstīt videoklipus, izmantojot kameru."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Šī lietotne var uzņemt attēlus un ierakstīt videoklipus, izmantojot kameru, kamēr lietotne tiek izmantota."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"uzņemt attēlus un videoklipus fonā"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Šī lietotne var jebkurā brīdī uzņemt attēlus un ierakstīt videoklipus, izmantojot kameru."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Atļauja lietojumprogrammai vai pakalpojumam piekļūt sistēmas kamerām, lai uzņemtu attēlus un videoklipus"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Šī privileģētā vai sistēmas lietotne var jebkurā brīdī uzņemt attēlus un ierakstīt videoklipus, izmantojot sistēmas kameru. Lietotnei nepieciešama arī atļauja android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Atļaut lietojumprogrammai vai pakalpojumam saņemt atzvanus par kameras ierīču atvēršanu vai aizvēršanu"</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vai palielināt skaļumu virs ieteicamā līmeņa?\n\nIlgstoši klausoties skaņu lielā skaļumā, var tikt bojāta dzirde."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vai izmantot pieejamības saīsni?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kad īsinājumtaustiņš ir ieslēgts, nospiežot abas skaļuma pogas un 3 sekundes turot tās, tiks aktivizēta pieejamības funkcija."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vai ieslēgt pieejamības funkcijas?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vai ieslēgt pieejamības funkciju saīsni?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgtas pieejamības funkcijas. Tas var mainīt ierīces darbību.\n\nPašreizējās funkcijas:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAtlasītās funkcijas varat mainīt šeit: Iestatījumi &gt; Pieejamība."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vai ieslēgt pakalpojumu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vai ieslēgt <xliff:g id="SERVICE">%1$s</xliff:g> saīsni?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Turot nospiestus abus skaļuma taustiņus dažas sekundes, tiek ieslēgta pieejamības funkcija <xliff:g id="SERVICE">%1$s</xliff:g>. Tas var mainīt ierīces darbību.\n\nŠo saīsni uz citu funkciju varat mainīt šeit: Iestatījumi &gt; Pieejamība."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ieslēgt"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Neieslēgt"</string>
@@ -1860,6 +1864,7 @@
<item quantity="one">%d h</item>
<item quantity="other">%d h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Līdz: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Līdz <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Līdz plkst. <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nākamais signāls)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Līdz brīdim, kad izslēgsiet"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index df7781e089dd..c882cb1378e7 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"менува аудио поставки"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Овозможува апликацијата да ги менува глобалните аудио поставки, како што се јачината на звукот и кој звучник се користи за излез."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"снимај аудио"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Апликацијава може да снима аудио со микрофонот во секое време."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Апликацијава може да снима аудио со микрофонот додека се користи апликацијата."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"снима аудио во заднината"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Апликацијава може да снима аудио со микрофонот во секое време."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"испраќање наредби до SIM-картичката"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Овозможува апликацијата да испраќа наредби до SIM картичката. Ова е многу опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавајте ја физичката активност"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Апликацијава може да ја препознава вашата физичка активност."</string>
<string name="permlab_camera" msgid="6320282492904119413">"снимај слики и видеа"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Апликацијава може да фотографира и да снима видеа со камерата во секое време."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Апликацијава може да фотографира и снима видеа со камерата додека се користи апликацијата."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"снима слики и видеа во заднината"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Апликацијава може да фотографира и да снима видеа со камерата во секое време."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволете апликацијата или услугата да пристапува до системските камери за да фотографира и да снима видеа"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Оваа привилегирана или системска апликација може да фотографира и да снима видеа со системската камера во секое време. Потребно е апликацијата да ја има и дозволата android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволете апликацијатa или услугата да прима повратни повици за отворањето или затворањето на уредите со камера."</string>
@@ -988,7 +992,7 @@
<string name="searchview_description_search" msgid="1045552007537359343">"Пребарај"</string>
<string name="searchview_description_query" msgid="7430242366971716338">"Пребарај барање"</string>
<string name="searchview_description_clear" msgid="1989371719192982900">"Исчисти барање"</string>
- <string name="searchview_description_submit" msgid="6771060386117334686">"Поднеси барање"</string>
+ <string name="searchview_description_submit" msgid="6771060386117334686">"Испрати барање"</string>
<string name="searchview_description_voice" msgid="42360159504884679">"Гласовно пребарување"</string>
<string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"Овозможи „Истражувај со допир“?"</string>
<string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> сака да овозможи „Истражувај со допир“. Кога е вклучено „Истражувај со допир“, може да се слушнат или да се видат описи на она што е под вашиот прст или да се прават движења за комуницирање со таблетот."</string>
@@ -1439,7 +1443,7 @@
<string name="upload_file" msgid="8651942222301634271">"Избери датотека"</string>
<string name="no_file_chosen" msgid="4146295695162318057">"Не е избрана датотека"</string>
<string name="reset" msgid="3865826612628171429">"Ресетирај"</string>
- <string name="submit" msgid="862795280643405865">"Поднеси"</string>
+ <string name="submit" msgid="862795280643405865">"Испрати"</string>
<string name="car_mode_disable_notification_title" msgid="8450693275833142896">"Апликацијата за возење работи"</string>
<string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Допрете за да излезете од апликацијата за возење."</string>
<string name="back_button_label" msgid="4078224038025043387">"Назад"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да го зголемиме звукот над препорачаното ниво?\n\nСлушањето звуци со голема јачина подолги периоди може да ви го оштети сетилото за слух."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Да се користи кратенка за „Пристапност“?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Кога е вклучена кратенката, ако ги притиснете двете копчиња за јачина на звук во времетраење од 3 секунди, ќе се стартува функција за пристапност."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Да се вклучат функциите за пристапност?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Да се вклучи кратенка за функциите за пристапност?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучат функциите за пристапност. Ова може да го промени начинот на функционирање на уредот.\n\nТековни функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМоже да ги промените избраните функции во „Поставки &gt; Пристапност“."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Да се вклучи <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Да се вклучи кратенка за <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако ги задржите притиснати двете копчиња за јачина на звук неколку секунди, ќе се вклучи функцијата за пристапност <xliff:g id="SERVICE">%1$s</xliff:g>. Ова може да го промени начинот на функционирање на уредот.\n\nМоже да ја измените кратенкава да биде за друга функција во „Поставки &gt; Пристапност“."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Вклучи"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не вклучувај"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">За %d ч.</item>
<item quantity="other">За %d ч.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следниот аларм)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Додека не го исклучите"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 12c0cb4025ee..ffe073c2d31d 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -327,7 +327,7 @@
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"ഡിസ്പ്ലേയുടെ സൂം നിലയും പൊസിഷനിംഗും നിയന്ത്രിക്കുക."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"ജെസ്‌റ്ററുകൾ നിർവഹിക്കുക"</string>
<string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"ടാപ്പുചെയ്യാനോ സ്വൈപ്പുചെയ്യാനോ പിഞ്ചുചെയ്യാനോ മറ്റ് ജെസ്‌റ്ററുകൾ നിർവഹിക്കാനോ കഴിയും."</string>
- <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ഫിംഗർപ്രിന്റ് ജെസ്‌റ്ററുകൾ"</string>
+ <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"ഫിംഗർപ്രിന്റ് ജെസ്‌ച്ചറുകൾ"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"ഉപകരണത്തിന്റെ ഫിംഗർപ്രിന്റ് സെൻസറിൽ ചെയ്‌ത ജെസ്‌റ്ററുകൾ ക്യാപ്‌ചർ ചെയ്യാനാകും."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"സ്ക്രീന്‍ഷോട്ട് എടുക്കുക"</string>
<string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ഡിസ്‌പ്ലേയുടെ സ്‌ക്രീൻഷോട്ട് എടുക്കാൻ കഴിയും."</string>
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"നിങ്ങളുടെ ഓഡിയോ ക്രമീകരണങ്ങൾ മാറ്റുക"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"വോളിയവും ഔട്ട്പുട്ടിനായി ഉപയോഗിച്ച സ്‌പീക്കറും പോലുള്ള ആഗോള ഓഡിയോ ക്രമീകരണങ്ങൾ പരിഷ്‌ക്കരിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ഈ ആപ്പിന് ഏത് സമയത്തും മൈക്രോഫോൺ ഉപയോഗിച്ചുകൊണ്ട് ഓഡിയോ റെക്കോർഡുചെയ്യാൻ കഴിയും."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ആപ്പ് ഉപയോഗത്തിലായിരിക്കുമ്പോൾ മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ ഈ ആപ്പിന് കഴിയും."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"പശ്ചാത്തലത്തിൽ ഓഡിയോ റെക്കോർഡ് ചെയ്യുക"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ഈ ആപ്പിന് ഏത് സമയത്തും മൈക്രോഫോൺ ഉപയോഗിച്ച് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ കഴിയും."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM-ലേക്ക് കമാൻഡുകൾ അയയ്ക്കുക"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"സിമ്മിലേക്ക് കമാൻഡുകൾ അയയ്‌ക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഇത് വളരെ അപകടകരമാണ്."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ശാരീരിക പ്രവർത്തനം തിരിച്ചറിയുക"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"നിങ്ങളുടെ ശാരീരിക പ്രവർത്തനം ഈ ആപ്പിന് തിരിച്ചറിയാനാവും."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കുക"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ഏതുസമയത്തും ക്യാമറ ഉപയോഗിച്ചുകൊണ്ട് ചിത്രങ്ങൾ എടുക്കാനും വീഡിയോകൾ റെക്കോർഡുചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ആപ്പ് ഉപയോഗത്തിലായിരിക്കുമ്പോൾ ക്യാമറ ഉപയോഗിച്ച് ചിത്രങ്ങളെടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"പശ്ചാത്തലത്തിൽ ചിത്രങ്ങളും വീഡിയോകളും എടുക്കുക"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ഏതുസമയത്തും ക്യാമറ ഉപയോഗിച്ച് ചിത്രങ്ങൾ എടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കാൻ, സിസ്‌റ്റം ക്യാമറ ആക്‌സസ് ചെയ്യുന്നതിന് ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"സിസ്‌റ്റം ക്യാമറ ഉപയോഗിച്ച് ഏത് സമയത്തും ചിത്രങ്ങളെടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ വിശേഷാധികാര അല്ലെങ്കിൽ സിസ്‌റ്റം ആപ്പിന് കഴിയും. ആപ്പിലും android.permission.CAMERA അനുമതി ഉണ്ടായിരിക്കണം"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ക്യാമറയുള്ള ഉപകരണങ്ങൾ ഓണാക്കുന്നതിനെയോ അടയ്ക്കുന്നതിനെയോ കുറിച്ചുള്ള കോൾബാക്കുകൾ സ്വീകരിക്കാൻ ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"മുകളിൽക്കൊടുത്തിരിക്കുന്ന ശുപാർശചെയ്‌ത ലെവലിലേക്ക് വോളിയം വർദ്ധിപ്പിക്കണോ?\n\nഉയർന്ന വോളിയത്തിൽ ദീർഘനേരം കേൾക്കുന്നത് നിങ്ങളുടെ ശ്രവണ ശേഷിയെ ദോഷകരമായി ബാധിക്കാം."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ഉപയോഗസഹായി കുറുക്കുവഴി ഉപയോഗിക്കണോ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"കുറുക്കുവഴി ഓണായിരിക്കുമ്പോൾ, രണ്ട് വോളിയം ബട്ടണുകളും 3 സെക്കൻഡ് നേരത്തേക്ക് അമർത്തുന്നത് ഉപയോഗസഹായി ഫീച്ചർ ആരംഭിക്കും."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ഉപയോഗസഹായി ഫീച്ചറുകൾ ഓണാക്കണോ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ഉപയോഗസഹായി ഫീച്ചറുകൾക്കുള്ള കുറുക്കുവഴി ഓണാക്കണോ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത്, ഉപയോഗസഹായി ഫീച്ചറുകൾ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന രീതിയെ ഇത് മാറ്റിയേക്കാം.\n\nനിലവിലുള്ള ഫീച്ചറുകൾ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nതിരഞ്ഞെടുത്ത ഫീച്ചറുകൾ ക്രമീകരണം &gt; ഉപയോഗസഹായി എന്നതിൽ മാറ്റാനാവും."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ഓണാക്കണോ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> കുറുക്കുവഴി ഓണാക്കണോ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"രണ്ട് വോളിയം കീകളും അൽപ്പ നേരത്തേക്ക് അമർത്തിപ്പിടിക്കുന്നത് ഉപയോഗസഹായി ഫീച്ചറായ <xliff:g id="SERVICE">%1$s</xliff:g> എന്നതിനെ ഓണാക്കുന്നു. നിങ്ങളുടെ ഉപകരണം പ്രവർത്തിക്കുന്ന വിധം ഇത് മാറ്റിയേക്കാം.\n\nക്രമീകരണം &gt; ഉപയോഗസഹായി എന്നതിലെ മറ്റൊരു ഫീച്ചറിലേക്ക് നിങ്ങൾക്ക് ഈ കുറുക്കുവഴി മാറ്റാനാവും."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ഓണാക്കുക"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ഓണാക്കരുത്"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d മണിക്കൂറത്തേക്ക്</item>
<item quantity="one">ഒരു മണിക്കൂറത്തേക്ക്</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> വരെ (അടുത്ത അലാറം)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"നിങ്ങൾ ഓഫാക്കുന്നത് വരെ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c041dced482..2183d010283a 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Аудио тохиргоо солих"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Апп нь дууны хэмжээ, спикерын гаралтад ашиглагдах глобал аудио тохиргоог өөрчлөх боломжтой."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"аудио бичих"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Энэ апп ямар ч үед микрофон ашиглан аудио бичих боломжтой."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Энэ аппыг ашиглаж байх үед энэ нь микрофон ашиглан аудио бичих боломжтой."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ард видео бичих"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Энэ апп ямар ч үед микрофон ашиглан аудио бичих боломжтой."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM картад тушаал илгээх"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Апп-д SIM рүү комманд илгээхийг зөвшөөрнө. Энэ маш аюултай."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"биеийн дасгал хөдөлгөөн таних"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Энэ апп таны биеийн дасгал хөдөлгөөнийг таних боломжтой."</string>
<string name="permlab_camera" msgid="6320282492904119413">"зураг авах болон видео бичих"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Энэ апп ямар ч үед камер ашиглан зураг авж, видео хийх боломжтой."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Энэ аппыг ашиглаж байх үед энэ нь камер ашиглан зураг авж, видео бичих боломжтой."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ард зураг авж, видео хийх"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Энэ апп ямар ч үед камер ашиглан зураг авж, видео бичих боломжтой."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Видео болон зураг авахын тулд апп эсвэл үйлчилгээнд хандахыг системийн камерт зөвшөөрөх"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Энэ хамгаалагдсан эсвэл системийн апп нь системийн камер ашиглан ямар ч үед зураг авч, видео бичих боломжтой. Мөн түүнчлэн, апп нь android.permission.CAMERA-н зөвшөөрөлтэй байх шаардлагатай"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Аппликэйшн эсвэл үйлчилгээнд камерын төхөөрөмжүүдийг нээж эсвэл хааж байгаа тухай залгасан дуудлага хүлээн авахыг зөвшөөрөх."</string>
@@ -953,18 +957,18 @@
<string name="autofill_parish" msgid="6847960518334530198">"Мөргөлч"</string>
<string name="autofill_area" msgid="8289022370678448983">"Хэсэг"</string>
<string name="autofill_emirate" msgid="2544082046790551168">"Эмират"</string>
- <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"өөрийн Вэб хавчуурга болон түүхийг унших"</string>
+ <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"өөрийн Веб хавчуурга болон түүхийг унших"</string>
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Апп нь Хөтчийн зочилж байсан бүх URL-н түүх болон Хөтчийн бүх хавчуургыг унших боломжтой. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадавхтай аппликейшнүүдэд ашиглагдахгүй байх боломжтой."</string>
- <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"вэб хавчуурга болон түүхийг бичих"</string>
+ <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"веб хавчуурга болон түүхийг бичих"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Апп нь таны таблет дээр хадгалагдсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөх боломжтой. Энэ нь апп-д Хөтчийн датаг арилгах эсвэл өөрчлөх боломжийг олгоно. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадвартай аппликейшнд ажиллахгүй байх боломжтой."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Аппад таны Android TV төхөөрөмжид хадгалсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөхийг зөвшөөрнө. Энэ нь аппад Хөтчийн өгөгдлийг устгах эсвэл өөрчлөхийг зөвшөөрч болзошгүй. Санамж: энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вэб хөтчийн чадамжтай бусад аппад хэрэгжихгүй байж болзошгүй."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Аппад таны Android TV төхөөрөмжид хадгалсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөхийг зөвшөөрнө. Энэ нь аппад Хөтчийн өгөгдлийг устгах эсвэл өөрчлөхийг зөвшөөрч болзошгүй. Санамж: энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл веб хөтчийн чадамжтай бусад аппад хэрэгжихгүй байж болзошгүй."</string>
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Апп нь таны утсан дээр хадгалагдсан Хөтчийн түүх эсвэл хавчуургыг өөрчлөх боломжтой. Энэ нь апп-д Хөтчийн датаг арилгах эсвэл өөрчлөх боломжийг олгоно. Анхаар: Энэ зөвшөөрөл нь гуравдагч талын хөтөч эсвэл вебээр хөтөчлөх чадвартай аппликейшнд ажиллахгүй байх боломжтой."</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"сэрүүлэг тохируулах"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"Апп нь суулгагдсан сэрүүлэгний апп дээр сэрүүлэг тохируулах боломжтой. Зарим сэрүүлэгний апп нь энэ функцийг дэмжихгүй байж болзошгүй."</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"дуут шуудан нэмэх"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"Таны дуут шуудангийн ирсэн мэйлд зурвас нэмэхийг апп-д зөвшөөрөх."</string>
<string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"Хөтчийн геобайршлын зөвшөөрлийг өөрчлөх"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын вэб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Апп нь Хөтчийн гео байршлын зөвшөөрлийг өөрчлөх боломжтой. Хортой апп нь энийг ашиглан дурын веб хуудасруу байршлын мэдээллийг илгээх боломжтой."</string>
<string name="save_password_message" msgid="2146409467245462965">"Та хөтчид энэ нууц үгийг сануулах уу?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"Одоо биш"</string>
<string name="save_password_remember" msgid="6490888932657708341">"Санах"</string>
@@ -1455,7 +1459,7 @@
<string name="progress_erasing" msgid="6891435992721028004">"Хуваалцсан хадгалах санг устгаж байна…"</string>
<string name="share" msgid="4157615043345227321">"Хуваалцах"</string>
<string name="find" msgid="5015737188624767706">"Олох"</string>
- <string name="websearch" msgid="5624340204512793290">"Вэб хайлт"</string>
+ <string name="websearch" msgid="5624340204512793290">"Веб хайлт"</string>
<string name="find_next" msgid="5341217051549648153">"Дараагийнхыг хайх"</string>
<string name="find_previous" msgid="4405898398141275532">"Өмнөхөөс олох"</string>
<string name="gpsNotifTicker" msgid="3207361857637620780">"<xliff:g id="NAME">%s</xliff:g>-н байршлын хүсэлт"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дууг санал болгосноос чанга болгож өсгөх үү?\n\nУрт хугацаанд чанга хөгжим сонсох нь таны сонсголыг муутгаж болно."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Хүртээмжийн товчлолыг ашиглах уу?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Товчлол асаалттай үед дууны түвшний хоёр товчлуурыг хамтад нь 3 секунд дарснаар хандалтын онцлогийг эхлүүлнэ."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Хандалтын онцлогуудыг асаах уу?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Хандалтын онцлогуудын товчлолыг асаах уу?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарснаар хандалтын онцлогууд асна. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nОдоогийн онцлогууд:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nТа сонгосон онцлогуудыг Тохиргоо &gt; Хандалт хэсэгт өөрчлөх боломжтой."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g>-г асаах уу?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g>-н товчлолыг асаах уу?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Дууны түвшний түлхүүрийг хэдэн секундийн турш зэрэг дарах нь хандалтын онцлог болох <xliff:g id="SERVICE">%1$s</xliff:g>-г асаадаг. Энэ нь таны төхөөрөмжийн ажиллах зарчмыг өөрчилж болзошгүй.\n\nТа Тохиргоо &gt; Хандалт хэсэгт энэ товчлолыг өөр онцлогт оноож өөрчлөх боломжтой."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Асаах"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Бүү асаа"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d цагийн турш</item>
<item quantity="one">1 цагийн турш:</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> хүртэл (дараагийн сэрүүлэг)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Таныг унтраах хүртэл"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index da6f2b71c65a..3d2fbcb07af9 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"आपल्या ऑडिओ सेटिंग्ज बदला"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"व्हॉल्यूम आणि आउटपुटसाठी कोणता स्पीकर वापरला आहे यासारख्या समग्र ऑडिओ सेटिंग्ज सुधारित करण्यासाठी अ‍ॅप ला अनुमती देते."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ऑडिओ रेकॉर्ड"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"हा अ‍ॅप कोणत्याही वेळी मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकता."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ॲप वापरात असताना, हे ॲप मायक्रोफोन वापरून ऑडिओ रेकॉर्ड करू शकते."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"बॅकग्राउंडमध्ये ऑडिओ रेकॉर्ड करा"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"हे ॲप मायक्रोफोन वापरून ऑडिओ कधीही रेकॉर्ड करू शकते."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"सिम वर कमांड पाठवा"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"अ‍ॅप ला सिम वर कमांड पाठविण्‍याची अनुमती देते. हे खूप धोकादायक असते."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक ॲक्टिव्हिटी ओळखा"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"हे अ‍ॅप तुमच्या शारीरिक ॲक्टिव्हिटी ओळखू शकते."</string>
<string name="permlab_camera" msgid="6320282492904119413">"चित्रे आणि व्हिडिओ घ्या"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"हा अ‍ॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्ह‍िडिओ रेकॉर्ड करू शकतो."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ॲप वापरात असताना, हे ॲप कॅमेरा वापरून फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"बॅकग्राउंडमध्ये फोटो काढा आणि व्हिडिओ रेकॉर्ड करा"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"हे ॲप कॅमेरा वापरून कधीही फोटो काढू शकते आणि व्हिडिओ रेकॉर्ड करू शकते."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"फोटो आणि व्हिडिओ काढण्यासाठी ॲप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे ॲक्सेस करण्याची अनुमती द्या"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"हे विशेषाधिकृत किंवा सिस्टम ॲप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. ॲपकडे android.permission.CAMERA परवानगी असण्याचीदेखील आवश्यकता आहे"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"एखाद्या अ‍ॅप्लिकेशन किंवा सेवेला कॅमेरा डिव्हाइस सुरू किंवा बंद केल्याची कॉलबॅक मिळवण्याची अनुमती द्या."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"शिफारस केलेल्‍या पातळीच्या वर आवाज वाढवायचा?\n\nउच्च आवाजात दीर्घ काळ ऐकण्‍याने आपल्‍या श्रवणशक्तीची हानी होऊ शकते."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"प्रवेशयोग्यता शॉर्टकट वापरायचा?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"शॉर्टकट सुरू असताना, दोन्ही व्‍हॉल्‍यूम बटणे तीन सेकंदांसाठी दाबून ठेवल्याने अ‍ॅक्सेसिबिलिटी वैशिष्ट्य सुरू होईल."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"अ‍ॅक्सेसिबिलिटी वैशिष्ट्ये सुरू करायची आहेत का?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"अ‍ॅक्सेसिबिलिटी वैशिष्ट्यांसाठी शॉर्टकट सुरू करायचा आहे का?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने अ‍ॅक्सेसिबिलिटी वैशिष्ट्ये सुरू होतात. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nसध्याची वैशिष्ट्ये:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतुम्ही हा शॉर्टकट सेटिंग्ज &gt; अ‍ॅक्सेसिबिलिटी मध्ये बदलू शकता."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> सुरू करायची आहे का?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> शॉर्टकट सुरू करायचा आहे का?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"दोन्ही व्हॉल्यूम की काही सेकंद धरून ठेवल्याने <xliff:g id="SERVICE">%1$s</xliff:g>, एक अ‍ॅक्सेसिबिलिटी वैशिष्ट्य सुरू होते. यामुळे तुमचे डिव्हाइस कसे काम करते हे पूर्णपणे बदलते.\n\nतुम्ही हा शॉर्टकट सेटिंग्ज &gt; अ‍ॅक्सेसिबिलिटी मध्ये बदलू शकता."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"सुरू करा"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"सुरू करू नका"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d तासासाठी</item>
<item quantity="one">1 तासासाठी</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>पर्यंत"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> पर्यंत (पुढील अलार्म)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"तुम्ही बंद करेपर्यंत"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 2e78b45cbb67..5c35ba13f599 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"tukar tetapan audio anda"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Membenarkan apl untuk mengubah suai tetapan audio global seperti kelantangan dan pembesar suara mana digunakan untuk output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"rakam audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Apl ini boleh merakam audio menggunakan mikrofon pada bila-bila masa."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Aplikasi ini boleh merakam audio menggunakan mikrofon semasa apl sedang digunakan."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rakam audio di latar"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Apl ini boleh merakam audio menggunakan mikrofon pada bila-bila masa."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"hantar perintah ke SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Membenarkan apl menghantar arahan kepada SIM. Ini amat berbahaya."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"camkan aktiviti fizikal"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Apl ini dapat mengecam aktiviti fizikal anda."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ambil gambar dan video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Apl ini boleh mengambil gambar dan merakam video menggunakan kamera pada bila-bila masa."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Apl ini boleh mengambil gambar dan merakam video menggunakan kamera semasa apl sedang digunakan."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ambil gambar dan video di latar"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Apl ini boleh mengambil gambar dan merakam video menggunakan kamera pada bila-bila masa."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Benarkan aplikasi atau perkhidmatan mengakses kamera sistem untuk mengambil gambar dan video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Apl terlindung atau apl sistem ini boleh mengambil gambar dan merakam video menggunakan kamera sistem pada bila-bila masa. Apl juga perlu mempunyai kebenaran android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Benarkan aplikasi atau perkhidmatan menerima panggilan balik tentang peranti kamera yang dibuka atau ditutup."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Naikkan kelantangan melebihi paras yang disyokorkan?\n\nMendengar pada kelantangan yang tinggi untuk tempoh yang lama boleh merosakkan pendengaran anda."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Kebolehaksesan?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Apabila pintasan dihidupkan, tindakan menekan kedua-dua butang kelantangan selama 3 saat akan memulakan ciri kebolehaksesan."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Hidupkan ciri kebolehaksesan?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Hidupkan pintasan untuk ciri kebolehaksesan?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan ciri kebolehaksesan. Hal ini mungkin mengubah cara peranti anda berfungsi.\n\nCiri semasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nAnda boleh menukar ciri yang dipilih dalam Tetapan &gt; Kebolehaksesan."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Hidupkan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Hidupkan pintasan <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Tindakan menahan kedua-dua kekunci kelantangan selama beberapa saat akan menghidupkan <xliff:g id="SERVICE">%1$s</xliff:g>, iaitu satu ciri kebolehaksesan. Ini mungkin mengubah cara peranti anda berfungsi.\n\nAnda boleh menukar pintasan ini kepada ciri lain dalam Tetapan &gt; Kebolehaksesan."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Hidupkan"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Jangan hidupkan"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Selama %d jam</item>
<item quantity="one">Selama 1 jam</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Hingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Sehingga <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (penggera akan datang)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Sehingga anda matikan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 1ebafa47f76e..38f1aa8f8f6b 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"သင့်အသံအပြင်အဆင်အားပြောင်းခြင်း"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"အပလီကေးရှင်းအား အသံအတိုးအကျယ်နှင့် အထွက်ကို မည်သည့်စပီကာကို သုံးရန်စသည်ဖြင့် စက်တစ်ခုလုံးနှင့်ဆိုင်သော အသံဆိုင်ရာ ဆက်တင်များ ပြင်ဆင်ခွင့် ပြုရန်"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"အသံဖမ်းခြင်း"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ဤအက်ပ်သည် မိုက်ခရိုဖုန်းကို အသုံးပြုပြီး အချိန်မရွေး အသံသွင်းနိုင်ပါသည်။"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ဤအက်ပ်ကို အသုံးပြုနေစဉ် ၎င်းက မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အသံဖမ်းနိုင်သည်။"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"နောက်ခံတွင် အသံဖမ်းပါ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ဤအက်ပ်သည် မိုက်ခရိုဖုန်းကို အသုံးပြု၍ အချိန်မရွေး အသံဖမ်းနိုင်သည်။"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM ထံသို့ ညွှန်ကြားချက်များကို ပို့ပါ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"အက်ပ်အား ဆင်းမ်ကဒ်ဆီသို့ အမိန့်များ ပေးပို့ခွင့် ပြုခြင်း။ ဤခွင့်ပြုမှုမှာ အန္တရာယ်အလွန် ရှိပါသည်။"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ကိုယ်လက်လှုပ်ရှားမှုကို မှတ်သားပါ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ဤအက်ပ်က သင်၏ကိုယ်လက်လှုပ်ရှားမှုကို မှတ်သားနိုင်ပါသည်။"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ဓါတ်ပုံနှင့်ဗွီဒီယိုရိုက်ခြင်း"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ဤအက်ပ်သည် ကင်မရာကို အသုံးပြု၍ ဓာတ်ပုံနှင့် ဗီဒီယိုများကို အချိန်မရွေး ရိုက်ကူးနိုင်ပါသည်။"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ဤအက်ပ်ကို အသုံးပြုနေစဉ် ၎င်းက ကင်မရာကို အသုံးပြု၍ ဓာတ်ပုံနှင့် ဗီဒီယိုများကို ရိုက်ကူးနိုင်သည်။"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ဓာတ်ပုံနှင့် ဗီဒီယိုများကို နောက်ခံတွင် ရိုက်ကူးပါ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ဤအက်ပ်သည် ကင်မရာကို အသုံးပြု၍ ဓာတ်ပုံနှင့် ဗီဒီယိုများကို အချိန်မရွေး ရိုက်ကူးနိုင်သည်။"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ဓာတ်ပုံနှင့် ဗီဒီယိုများရိုက်ရန်အတွက် စနစ်ကင်မရာများကို အက်ပ် သို့မဟုတ် ဝန်‌ဆောင်မှုအား အသုံးပြုခွင့်ပေးခြင်း"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ဤခွင့်ပြုထားသည့် သို့မဟုတ် စနစ်အက်ပ်က စနစ်ကင်မရာအသုံးပြုပြီး ဓာတ်ပုံနှင့် ဗီဒီယိုများကို အချိန်မရွေး ရိုက်ကူးနိုင်သည်။ အက်ပ်ကလည်း android.permission.CAMERA ခွင့်ပြုချက် ရှိရပါမည်"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ကင်မရာစက်များ ပွင့်နေခြင်း သို့မဟုတ် ပိတ်နေခြင်းနှင့် ပတ်သက်ပြီး ပြန်လည်ခေါ်ဆိုမှုများ ရယူရန် အပလီကေးရှင်း သို့မဟုတ် ဝန်ဆောင်မှုကို ခွင့်ပြုခြင်း။"</string>
@@ -1096,7 +1100,7 @@
<string name="cut" msgid="2561199725874745819">"ဖြတ်ရန်"</string>
<string name="copy" msgid="5472512047143665218">"ကူးရန်"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"ကလစ်ဘုတ်သို့ မိတ္တူကူးခြင်း မအောင်မြင်ပါ"</string>
- <string name="paste" msgid="461843306215520225">"Paste"</string>
+ <string name="paste" msgid="461843306215520225">"ကူးထည့်ရန်"</string>
<string name="paste_as_plain_text" msgid="7664800665823182587">"စာသားအတိုင်း ကူးထည့်ပါ"</string>
<string name="replace" msgid="7842675434546657444">"အစားထိုခြင်း"</string>
<string name="delete" msgid="1514113991712129054">"ဖျက်ရန်"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"အသံကို အကြံပြုထားသည့် ပမာဏထက် မြှင့်ပေးရမလား?\n\nအသံကို မြင့်သည့် အဆင့်မှာ ကြာရှည်စွာ နားထောင်ခြင်းက သင်၏ နားကို ထိခိုက်စေနိုင်သည်။"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"အများသုံးစွဲနိုင်မှု ဖြတ်လမ်းလင့်ခ်ကို အသုံးပြုလိုပါသလား။"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ဖြတ်လမ်းလင့်ခ်ကို ဖွင့်ထားစဉ် အသံထိန်းခလုတ် နှစ်ခုစလုံးကို ၃ စက္ကန့်ခန့် ဖိထားခြင်းဖြင့် အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုကို ဖွင့်နိုင်သည်။"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်မလား။"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများအတွက် ဖြတ်လမ်းကို ဖွင့်မလား။"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nလက်ရှိ ဝန်ဆောင်မှုများ-\n<xliff:g id="SERVICE">%1$s</xliff:g>\n\'ဆက်တင်များ &gt; အများသုံးစွဲနိုင်မှု\' တွင် ရွေးထားသည့် ဝန်ဆောင်မှုများကို ပြောင်းနိုင်သည်။"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ဖွင့်မလား။"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ဖြတ်လမ်းကို ဖွင့်မလား။"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"အသံခလုတ်နှစ်ခုလုံးကို စက္ကန့်အနည်းငယ် ဖိထားခြင်းက အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုဖြစ်သော <xliff:g id="SERVICE">%1$s</xliff:g> ကို ဖွင့်ပေးသည်။ ဤလုပ်ဆောင်ချက်က သင့်စက်အလုပ်လုပ်ပုံကို ပြောင်းလဲနိုင်သည်။\n\nဤဖြတ်လမ်းလင့်ခ်ကို \'ဆက်တင်များ &gt; အများသုံးစွဲနိုင်မှု\' တွင် နောက်ဝန်ဆောင်မှုတစ်ခုသို့ ပြောင်းနိုင်သည်။"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ဖွင့်ရန်"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"မဖွင့်ပါနှင့်"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d နာရီ အတွက်</item>
<item quantity="one">၁ နာရီအတွက်</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> အထိ"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>အထိ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> အထိ (လာမည့် နှိုးစက်)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"သင်ပိတ်လိုက်သည် အထိ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index ac21aec0080e..5b0a4ad1c124 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"endre lydinnstillinger"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lar appen endre globale lydinnstillinger slik som volum og hvilken høyttaler som brukes for lydavspilling."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ta opp lyd"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Denne appen kan når som helst ta opp lyd med mikrofonen."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Denne appen kan ta opp lyd med mikrofonen mens den er i bruk."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ta opp lyd i bakgrunnen"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Denne appen kan når som helst ta opp lyd med mikrofonen."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"sende kommandoer til SIM-kortet"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lar appen sende kommandoer til SIM-kortet. Dette er veldig farlig."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"gjenkjenn fysisk aktivitet"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Denne appen kan gjenkjenne den fysiske aktiviteten din."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ta bilder og videoer"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Denne appen kan når som helst ta bilder og spille inn videoer ved hjelp av kameraet."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Denne appen kan ta bilder og spille inn videoer med kameraet mens den er i bruk."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ta bilder og spille inn videoer i bakgrunnen"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Denne appen kan når som helst ta bilder og spille inn videoer med kameraet."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Gi en app eller tjeneste tilgang til systemkameraene for å ta bilder og spille inn videoer"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Denne privilegerte appen eller systemappen kan når som helst ta bilder og spille inn videoer med et systemkamera. Dette krever at appen også har tillatelsen android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillat at en app eller tjeneste mottar tilbakekallinger om kameraenheter som åpnes eller lukkes."</string>
@@ -1094,7 +1098,7 @@
<string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="1532369154488982046">"Merk alt"</string>
<string name="cut" msgid="2561199725874745819">"Klipp ut"</string>
- <string name="copy" msgid="5472512047143665218">"Kopier"</string>
+ <string name="copy" msgid="5472512047143665218">"Kopiér"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Kunne ikke kopiere til utklippstavlen"</string>
<string name="paste" msgid="461843306215520225">"Lim inn"</string>
<string name="paste_as_plain_text" msgid="7664800665823182587">"Lim inn som ren tekst"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du øke volumet til over anbefalt nivå?\n\nHvis du hører på et høyt volum over lengre perioder, kan det skade hørselen din."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruke tilgjengelighetssnarveien?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Når snarveien er på, starter en tilgjengelighetsfunksjon når du trykker inn begge volumknappene i tre sekunder."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vil du slå på tilgjengelighetsfunksjoner?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vil du slå på snarveien for tilgjengelighetsfunksjoner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hvis du holder inne volumtastene i noen sekunder, slås tilgjengelighetsfunksjoner på. Dette kan endre hvordan enheten din fungerer.\n\nNåværende funksjoner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan endre valgte funksjoner i Innstillinger &gt; Tilgjengelighet."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vil du slå på <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vil du slå på <xliff:g id="SERVICE">%1$s</xliff:g>-snarveien?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hvis du holder inne begge volumtastene i noen sekunder, slår du på <xliff:g id="SERVICE">%1$s</xliff:g>, en tilgjengelighetsfunksjon. Dette kan endre hvordan enheten din fungerer.\n\nDu kan endre denne snarveien til en annen funksjon i Innstillinger &gt; Tilgjengelighet."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Slå på"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ikke slå på"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">I %d timer</item>
<item quantity="one">I én time</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Til <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (neste alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Til du slår av"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index ee0362e4a184..092686b1c242 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"तपाईँका अडियो सेटिङहरू परिवर्तन गर्नुहोस्"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"एपलाई ग्लोबल अडियो सेटिङहरू परिमार्जन गर्न अनुमति दिन्छ, जस्तै भोल्युम र आउटपुटको लागि कुन स्पिकर प्रयोग गर्ने।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"अडियो रेकर्ड गर्नुहोस्"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"यस अनुप्रयोगले जुनसुकै समय माइक्रोफोनको प्रयोग गरी अडियो रेकर्ड गर्न सक्छ।"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"यो एप प्रयोग भइरहेका बेला यसले माइक्रोफोन प्रयोग गरेर अडियो रेकर्ड गर्न सक्छ।"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ब्याकग्राउन्डमा अडियो रेकर्ड गर्नुहोस्"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"यो एपले जुनसुकै बेला माइक्रोफोन प्रयोग गरी अडियो रेकर्ड गर्न सक्छ।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM मा आदेशहरू पठाउन दिनुहोस्"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM लाई आदेश पठाउन एपलाई अनुमति दिन्छ। यो निकै खतरनाक हुन्छ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"शारीरिक गतिविधि पहिचान गर्नुहोस्‌"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"यो अनुप्रयोगले तपाईंको शारीरिक गतिविधिको पहिचान गर्न सक्छ।"</string>
<string name="permlab_camera" msgid="6320282492904119413">"फोटोहरू र भिडियोहरू लिनुहोस्।"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"यो एप प्रयोग भइरहेका बेला यसले क्यामेरा प्रयोग गरेर फोटो खिच्न तथा भिडियोहरू रेकर्ड गर्न सक्छ।"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ब्याकग्राउन्डमा फोटो खिच्नुहोस् तथा भिडियो रेकर्ड गर्नुहोस्"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"यो एपले जुनसुकै बेला क्यामेराको प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"एप वा सेवालाई फोटो र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"प्रणालीको यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै बेला प्रणालीको क्यामेरा प्रयोग गरी फोटो खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA प्रयोग गर्ने अनुमति हुनु पर्छ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"कुनै एप वा सेवालाई खोलिँदै वा बन्द गरिँदै गरेका क्यामेरा यन्त्रहरूका बारेमा कलब्याक प्राप्त गर्ने अनुमति दिनुहोस्।"</string>
@@ -1385,7 +1389,7 @@
<string name="ext_media_status_unmountable" msgid="7043574843541087748">"बिग्रेको"</string>
<string name="ext_media_status_unsupported" msgid="5460509911660539317">"असमर्थित"</string>
<string name="ext_media_status_ejecting" msgid="7532403368044013797">"निकाल्दै..."</string>
- <string name="ext_media_status_formatting" msgid="774148701503179906">"फरम्याट गर्दै…"</string>
+ <string name="ext_media_status_formatting" msgid="774148701503179906">"फर्म्याट गर्दै…"</string>
<string name="ext_media_status_missing" msgid="6520746443048867314">"सम्मिलित छैन"</string>
<string name="activity_list_empty" msgid="4219430010716034252">"कुनै मिल्ने गतिविधि पाइएन।"</string>
<string name="permlab_route_media_output" msgid="8048124531439513118">"मिडिया निकास दिशानिर्देश गराउनुहोस्"</string>
@@ -1473,7 +1477,7 @@
<string name="add_account_button_label" msgid="322390749416414097">"खाता थप्नुहोस्"</string>
<string name="number_picker_increment_button" msgid="7621013714795186298">"बढाउनुहोस्"</string>
<string name="number_picker_decrement_button" msgid="5116948444762708204">"घटाउनुहोस्"</string>
- <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> छोइराख्नुहोस्।"</string>
+ <string name="number_picker_increment_scroll_mode" msgid="8403893549806805985">"<xliff:g id="VALUE">%s</xliff:g> टच एण्ड होल्ड गर्नुहोस्।"</string>
<string name="number_picker_increment_scroll_action" msgid="8310191318914268271">"बढाउन माथि र घटाउन तल सार्नुहोस्।"</string>
<string name="time_picker_increment_minute_button" msgid="7195870222945784300">"मिनेट बढाउनुहोस्"</string>
<string name="time_picker_decrement_minute_button" msgid="230925389943411490">"मिनेट घटाउनुहोस्"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"सिफारिस तहभन्दा आवाज ठुलो गर्नुहुन्छ?\n\nलामो समय सम्म उच्च आवाजमा सुन्दा तपाईँको सुन्ने शक्तिलाई हानी गर्न सक्छ।"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"पहुँच सम्बन्धी सर्टकट प्रयोग गर्ने हो?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"यो सर्टकट सक्रिय हुँदा, ३ सेकेन्डसम्म दुवै भोल्युम बटन थिच्नुले पहुँचसम्बन्धी कुनै सुविधा सुरु गर्ने छ।"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"पहुँचसम्बन्धी सुविधाहरू सक्रिय गर्ने हो?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"सर्वसुलभता कायम गर्ने सुविधाहरू प्रयोग गर्न सर्टकट अन गर्ने हो?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"केही सेकेन्डसम्म दुवै भोल्युम बटन थिचिराख्नुभयो भने पहुँचसम्बन्धी सुविधाहरू सक्रिय हुन्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nहालका सुविधाहरू:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nतपाईं सेटिङ &gt; पहुँचमा गएर चयन गरिएका सुविधाहरू परिवर्तन गर्न सक्नुहुन्छ।"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> सक्रिय गर्ने हो?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> प्रयोग गर्न सर्टकट अन गर्ने हो?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"केही सेकेन्डसम्म दुवै भोल्युम बटन थिचिराख्नुले <xliff:g id="SERVICE">%1$s</xliff:g> नामक पहुँचसम्बन्धी सुविधा सक्रिय गर्छ। यसले तपाईंको यन्त्रले काम गर्ने तरिका परिवर्तन गर्न सक्छ।\n\nतपाईं सेटिङ &gt; पहुँचमा गई यो सर्टकटमार्फत अर्को सुविधा खुल्ने बनाउन सक्नुहुन्छ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"सक्रिय गरियोस्"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"सक्रिय नगरियोस्"</string>
@@ -1656,9 +1660,9 @@
<string name="accessibility_button_prompt_text" msgid="8343213623338605305">"तपाईंले पहुँचको बटन ट्याप गर्दा प्रयोग गर्न चाहनुभएको सुविधा छनौट गर्नुहोस्:"</string>
<string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"तपाईंले पहुँचको इसारामार्फत प्रयोग गर्न चाहनुभएको सुविधा छनौट गर्नुहोस् (दुईवटा औँलाले स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्):"</string>
<string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"तपाईंले पहुँचको इसारामार्फत प्रयोग गर्न चाहनुभएको सुविधा छनौट गर्नुहोस् (तीनवटा औँलाले स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्):"</string>
- <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"एउटा सुविधाबाट अर्को सुविधामा जान पहुँच बटन छोइराख्नुहोस्।"</string>
- <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"एउटा सुविधाबाट अर्को सुविधामा जान दुईवटा औँलाले माथितिर स्वाइप गरी स्क्रिनमा छोइराख्नुहोस्।"</string>
- <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"एउटा सुविधाबाट अर्को सुविधामा जान तीनवटा औँलाले माथितिर स्वाइप गरी स्क्रिनमा छोइराख्नुहोस्।"</string>
+ <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"एउटा सुविधाबाट अर्को सुविधामा जान पहुँच बटन टच एण्ड होल्ड गर्नुहोस्।"</string>
+ <string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"एउटा सुविधाबाट अर्को सुविधामा जान दुईवटा औँलाले माथितिर स्वाइप गरी स्क्रिनमा टच एण्ड होल्ड गर्नुहोस्।"</string>
+ <string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"एउटा सुविधाबाट अर्को सुविधामा जान तीनवटा औँलाले माथितिर स्वाइप गरी स्क्रिनमा टच एण्ड होल्ड गर्नुहोस्।"</string>
<string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"म्याग्निफिकेसन"</string>
<string name="user_switched" msgid="7249833311585228097">"अहिलेको प्रयोगकर्ता <xliff:g id="NAME">%1$s</xliff:g>।"</string>
<string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> मा स्विच गर्दै..."</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d घन्टाका लागि</item>
<item quantity="one">१ घन्टाको लागि</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> सम्म"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (अर्को अलार्म) सम्म"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"तपाईंले निष्क्रिय नपार्नुभएसम्म"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 14cfee028fdc..17e988e4a344 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"je audio-instellingen wijzigen"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Hiermee kan de app algemene audio-instellingen wijzigen zoals het volume en welke luidspreker wordt gebruikt voor de uitvoer."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"audio opnemen"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Deze app kan op elk moment audio opnemen met de microfoon."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Deze app kan audio opnemen met de microfoon als de app wordt gebruikt."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"audio opnemen op de achtergrond"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Deze app kan altijd audio opnemen met de microfoon."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"opdrachten verzenden naar de simkaart"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Hiermee kan de app opdrachten verzenden naar de simkaart. Dit is erg gevaarlijk."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fysieke activiteit herkennen"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Deze app kan je fysieke activiteit herkennen."</string>
<string name="permlab_camera" msgid="6320282492904119413">"foto\'s en video\'s maken"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Deze app kan op elk moment foto\'s maken en video\'s opnemen met de camera."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Deze app kan foto\'s maken en video\'s opnemen met de camera als de app wordt gebruikt."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"foto\'s maken en video\'s opnemen op de achtergrond"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Deze app kan altijd foto\'s maken en video\'s opnemen met de camera."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Een app of service toegang tot systeemcamera\'s geven om foto\'s en video\'s te maken"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Deze gemachtigde app of systeem-app kan op elk gewenst moment foto\'s maken en video\'s opnemen met een systeemcamera. De app moet ook het recht android.permission.CAMERA hebben."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Een app of service toestaan callbacks te ontvangen over camera-apparaten die worden geopend of gesloten."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Volume verhogen tot boven het aanbevolen niveau?\n\nAls je langere tijd op hoog volume naar muziek luistert, raakt je gehoor mogelijk beschadigd."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Snelkoppeling toegankelijkheid gebruiken?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Als de snelkoppeling is ingeschakeld, kun je drie seconden op beide volumeknoppen drukken om een toegankelijkheidsfunctie te starten."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Toegankelijkheidsfuncties inschakelen?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Snelkoppeling voor toegankelijkheidsfuncties inschakelen?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, schakel je de toegankelijkheidsfuncties in. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nHuidige functies:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nJe kunt de geselecteerde functies wijzigen via Instellingen &gt; Toegankelijkheid."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> inschakelen?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Snelkoppeling voor <xliff:g id="SERVICE">%1$s</xliff:g> inschakelen?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Als je beide volumetoetsen een paar seconden ingedrukt houdt, wordt de toegankelijkheidsfunctie <xliff:g id="SERVICE">%1$s</xliff:g> ingeschakeld. Hierdoor kan de manier veranderen waarop je apparaat werkt.\n\nJe kunt deze sneltoets op een andere functie instellen via Instellingen &gt; Toegankelijkheid."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Inschakelen"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Niet inschakelen"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Gedurende %d u</item>
<item quantity="one">Gedurende 1 u</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Tot <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (volgende wekker)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Totdat je uitschakelt"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 1cb01ead277b..0f10cde4b9cb 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ଆପଣଙ୍କ ଅଡିଓ ସେଟିଙ୍ଗକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ଆପ୍‌କୁ ଗ୍ଲୋବାଲ୍ ଅଡିଓ ସେଟିଙ୍ଗ, ଯେପରିକି ଭଲ୍ୟୁମ୍‌କୁ ସଂଶୋଧିତ କରିବାକୁ ଏବଂ ଆଉଟପୁଟ୍ ପାଇଁ ସ୍ପିକର୍‌ ବ୍ୟବହାର କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ଏହି ଆପ୍‍ ଯେକୌଣସି ସମୟରେ ମାଇକ୍ରୋଫୋନ୍‍ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ଆପକୁ ବ୍ୟବହାର କରାଯାଉଥିବା ସମୟରେ ଏହା ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ପୃଷ୍ଠପଟରେ ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ଏହି ଆପ୍ ଯେ କୌଣସି ସମୟରେ ମାଇକ୍ରୋଫୋନକୁ ବ୍ୟବହାର କରି ଅଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMକୁ କମାଣ୍ଡ ପଠାନ୍ତୁ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIMକୁ କମାଣ୍ଡ ପଠାଇବା ପାଇଁ ଆପ୍‍କୁ ଅନୁମତି ଦେଇଥାଏ। ଏହା ବହୁତ ବିପଦପୂର୍ଣ୍ଣ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ଶାରୀରିକ ଗତିବିଧି ଚିହ୍ନଟକରେ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ଏହି ଆପ୍‍ଣ ଆପଣଙ୍କ ଶାରୀରିକ ଗତିବିଧିକୁ ଚିହ୍ନଟ କରିପାରେ"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ଫଟୋ ଓ ଭିଡିଓଗୁଡ଼ିକୁ ନିଅନ୍ତୁ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ଏହି ଆପ୍‍ ଯେକୌଣସି ସମୟରେ କ୍ୟାମେରା ବ୍ୟବହାର କରି ଫଟୋ ଉଠାଇପାରେ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରେ।"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ଆପକୁ ବ୍ୟବହାର କରାଯାଉଥିବା ସମୟରେ ଏହା କ୍ୟାମେରାକୁ ବ୍ୟବହାର କରି ଛବି ନେଇପାରିବ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ପୃଷ୍ଠପଟରେ ଛବି ନିଅନ୍ତୁ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରନ୍ତୁ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"କ୍ୟାମେରାକୁ ବ୍ୟବହାର କରି ଯେ କୌଣସି ସମୟରେ ଏହି ଆପ୍ ଛବି ନେଇପାରିବ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରିବ।"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ଛବି ଏବଂ ଭିଡିଓଗୁଡ଼ିକୁ ନେବା ପାଇଁ ସିଷ୍ଟମ୍ କ୍ୟାମେରା‌ଗୁଡ଼ିକୁ କୌଣସି ଆପ୍ଲିକେସନ୍ କିମ୍ବା ସେବା ଆକ୍ସେସ୍ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ବିଶେଷ ଅଧିକାର ଥିବା ଏହି ଆପ୍ କିମ୍ବା ସିଷ୍ଟମ୍ ଆପ୍ ଯେ କୌଣସି ସମୟରେ ଏକ ସିଷ୍ଟମ୍ କ୍ୟାମେରା ବ୍ୟବହାର କରି ଛବି ଉଠାଇପାରିବ ଏବଂ ଭିଡିଓ ରେକର୍ଡ କରିପାରିବ। ଆପରେ ମଧ୍ୟ android.permission.CAMERA ଅନୁମତି ଆବଶ୍ୟକ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"କ୍ୟାମେରା ଡିଭାଇସଗୁଡ଼ିକ ଖୋଲିବା କିମ୍ବା ବନ୍ଦ କରିବା ବିଷୟରେ କଲବ୍ୟାକଗୁଡ଼ିକ ପାଇବାକୁ ଏକ ଆପ୍ଲିକେସନ୍ କିମ୍ବା ସେବାକୁ ଅନୁମତି ଦିଅନ୍ତୁ।"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ମାତ୍ରା ବଢ଼ାଇ ସୁପାରିଶ ସ୍ତର ବଢ଼ାଉଛନ୍ତି? \n\n ଲମ୍ବା ସମୟ ପର୍ଯ୍ୟନ୍ତ ଉଚ୍ଚ ଶବ୍ଦରେ ଶୁଣିଲେ ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତି ଖରାପ ହୋଇପାରେ।"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ଆକ୍ସେସବିଲିଟି ଶର୍ଟକଟ୍‍ ବ୍ୟବହାର କରିବେ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ସର୍ଟକଟ୍ ଚାଲୁ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍ ବଟନ୍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଏକ ଆକ୍ସେସବିଲିଟି ଫିଚର୍ ଆରମ୍ଭ ହେବ।"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକୁ ଚାଲୁ କରିବେ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ପାଇଁ ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଆକ୍ସେସିବିଲିଟୀ ଫିଚରଗୁଡ଼ିକ ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nବର୍ତ୍ତମାନର ଫିଚରଗୁଡ଼ିକ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n ଆପଣ ସେଟିଂସ୍ &amp;gt ଆକ୍ସେସିବିଲିଟୀରେ ଚୟନିତ ଫିଚରଗୁଡ଼ିକୁ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ଚାଲୁ କରିବେ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> ସର୍ଟକଟ୍ ଚାଲୁ କରିବେ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"କିଛି ସେକେଣ୍ଡ ପାଇଁ ଉଭୟ ଭଲ୍ୟୁମ୍ କୀ’କୁ ଧରି ରଖିବା ଫଳରେ ଏକ ଆକ୍ସେସିବିଲିଟୀ ଫିଚର୍ <xliff:g id="SERVICE">%1$s</xliff:g> ଚାଲୁ ହୁଏ। ଏହା ଆପଣଙ୍କ ଡିଭାଇସ୍ କିପରି କାମ କରେ ତାହା ପରିବର୍ତ୍ତନ କରିପାରେ।\n\nଆପଣ ସେଟିଂସ୍ &amp;gt ଆକ୍ସେସିବିଲିଟୀରେ ଏହି ସର୍ଚକଟକୁ ଅନ୍ୟ ଏକ ଫିଚରରେ ପରିବର୍ତ୍ତନ କରିପାରିବେ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ଚାଲୁ କରନ୍ତୁ ନାହିଁ"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d ଘଣ୍ଟା ପାଇଁ</item>
<item quantity="one">1 ଘଣ୍ଟା ପାଇଁ</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ପର୍ଯ୍ୟନ୍ତ"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ପର୍ଯ୍ୟନ୍ତ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ପରବର୍ତ୍ତୀ ଆଲାର୍ମ) ପର୍ଯ୍ୟନ୍ତ"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ବନ୍ଦ ନକରିବା ପର୍ଯ୍ୟନ୍ତ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b5560f186e3a..11bfd78a2ca7 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ਆਪਣੀਆਂ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਬਦਲੋ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ਐਪ ਨੂੰ ਗਲੋਬਲ ਆਡੀਓ ਸੈਟਿੰਗਾਂ ਸੰਸ਼ੋਧਿਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ ਜਿਵੇਂ ਅਵਾਜ਼ ਅਤੇ ਆਊਟਪੁਟ ਲਈ ਕਿਹੜਾ ਸਪੀਕਰ ਵਰਤਿਆ ਜਾਂਦਾ ਹੈ।"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">" ਆਡੀਓ ਰਿਕਾਰਡ ਕਰਨ"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ਇਹ ਐਪ ਕਿਸੇ ਵੀ ਸਮੇਂ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਆਡੀਓ ਫ਼ਾਈਲ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ਇਹ ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਵੇਲੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"ਇਹ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਨੂੰ ਵਰਤ ਕੇ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜੋ"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"ਐਪ ਨੂੰ SIM ਨੂੰ ਕਮਾਂਡਾਂ ਭੇਜਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਬਹੁਤ ਘਾਤਕ ਹੈ।"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣਨਾ"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ਇਹ ਐਪ ਤੁਹਾਡੀ ਸਰੀਰਕ ਸਰਗਰਮੀ ਨੂੰ ਪਛਾਣ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ਤਸਵੀਰਾਂ ਅਤੇ ਵੀਡੀਓ ਬਣਾਓ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ਇਹ ਐਪ ਕਿਸੇ ਵੀ ਸਮੇਂ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ਇਹ ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਵੇਲੇ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਤਸਵੀਰਾਂ ਖਿੱਚੋ ਅਤੇ ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰੋ"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"ਇਹ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ।"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ਸਿਸਟਮ ਕੈਮਰੇ ਨੂੰ ਤਸਵੀਰਾਂ ਅਤੇ ਵੀਡੀਓ ਬਣਾਉਣ ਲਈ ਐਪਲੀਕੇਸ਼ਨ ਜਾਂ ਸੇਵਾ ਤੱਕ ਪਹੁੰਚ ਦਿਓ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ਇਹ ਵਿਸ਼ੇਸ਼ ਅਧਿਕ੍ਰਿਤ ਜਾਂ ਸਿਸਟਮ ਐਪ ਕਿਸੇ ਵੇਲੇ ਵੀ ਸਿਸਟਮ ਕੈਮਰੇ ਨੂੰ ਵਰਤ ਕੇ ਤਸਵੀਰਾਂ ਖਿੱਚ ਸਕਦੀ ਹੈ ਅਤੇ ਵੀਡੀਓ ਫ਼ਾਈਲਾਂ ਰਿਕਾਰਡ ਕਰ ਸਕਦੀ ਹੈ। ਐਪ ਨੂੰ ਵੀ android.permission.CAMERA ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ਐਪਲੀਕੇਸ਼ਨ ਜਾਂ ਸੇਵਾ ਨੂੰ ਕੈਮਰਾ ਡੀਵਾਈਸਾਂ ਦੇ ਚਾਲੂ ਜਾਂ ਬੰਦ ਕੀਤੇ ਜਾਣ ਬਾਰੇ ਕਾਲਬੈਕ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿਓ।"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ਕੀ ਵੌਲਿਊਮ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੇ ਪੱਧਰ ਤੋਂ ਵਧਾਉਣੀ ਹੈ?\n\nਲੰਮੇ ਸਮੇਂ ਤੱਕ ਉੱਚ ਵੌਲਿਊਮ ਤੇ ਸੁਣਨ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ।"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਸ਼ਾਰਟਕੱਟ ਵਰਤਣਾ ਹੈ?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਲਈ ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ, ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਮੌਜੂਦਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਚੁਣੀਆਂ ਗਈਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"ਕੀ <xliff:g id="SERVICE">%1$s</xliff:g> ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"ਕੁਝ ਸਕਿੰਟਾਂ ਲਈ ਦੋਵੇਂ ਅਵਾਜ਼ੀ ਕੁੰਜੀਆਂ ਨੂੰ ਦਬਾਈ ਰੱਖਣਾ <xliff:g id="SERVICE">%1$s</xliff:g>, ਇੱਕ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਚਾਲੂ ਕਰ ਦਿੰਦਾ ਹੈ। ਇਹ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੰਮ ਕਰਨ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦਾ ਹੈ।\n\nਸੈਟਿੰਗਾਂ ਅਤੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਤੁਸੀਂ ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਕਿਸੇ ਹੋਰ ਵਿਸ਼ੇਸ਼ਤਾ ਵਿੱਚ ਬਦਲ ਸਕਦੇ ਹੋ।"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ਚਾਲੂ ਕਰੋ"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ਚਾਲੂ ਨਾ ਕਰੋ"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">%d ਘੰਟਿਆਂ ਲਈ</item>
<item quantity="other">%d ਘੰਟਿਆਂ ਲਈ</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> ਤੱਕ (ਅਗਲਾ ਅਲਾਰਮ)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਬੰਦ ਨਹੀਂ ਕਰਦੇ ਹੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index aeebf8845567..d62651326645 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"zmienianie ustawień audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pozwala aplikacji na modyfikowanie globalnych ustawień dźwięku, takich jak głośność oraz urządzenie wyjściowe."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nagrywanie dźwięku"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ta aplikacja może w dowolnym momencie nagrać dźwięk przez mikrofon."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacja może nagrywać dźwięk przy użyciu mikrofonu, gdy jest używana."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nagrywanie dźwięku w tle"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacja może w dowolnym momencie nagrywać dźwięk przy użyciu mikrofonu."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"wysyłanie poleceń do karty SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Pozwala aplikacji na wysyłanie poleceń do karty SIM. To bardzo niebezpieczne."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznawanie aktywności fizycznej"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ta aplikacja może rozpoznawać Twoją aktywność fizyczną."</string>
<string name="permlab_camera" msgid="6320282492904119413">"wykonywanie zdjęć i filmów wideo"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ta aplikacja może w dowolnym momencie robić zdjęcia i nagrywać filmy przy użyciu aparatu."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ta aplikacja może robić zdjęcia i nagrywać filmy przy użyciu aparatu, gdy jest używana."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"robienie zdjęć i nagrywanie filmów w tle"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ta aplikacja może w dowolnym momencie robić zdjęcia i nagrywać filmy przy użyciu aparatu."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Zezwól na dostęp aplikacji lub usługi do aparatów systemu i robienie zdjęć oraz nagrywanie filmów"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ta aplikacja systemowa z podwyższonymi uprawnieniami może w dowolnym momencie robić zdjęcia i nagrywać filmy przy użyciu aparatu systemowego. Wymaga przyznania uprawnień android.permission.CAMERA również aplikacji."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Zezwól na dostęp aplikacji lub usługi na otrzymywanie wywoływania zwrotnego o urządzeniach z aparatem, kiedy są one uruchamiane lub zamykane."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zwiększyć głośność ponad zalecany poziom?\n\nSłuchanie głośno przez długi czas może uszkodzić Twój słuch."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Użyć skrótu do ułatwień dostępu?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Gdy skrót jest włączony, jednoczesne naciskanie przez trzy sekundy obu przycisków głośności uruchamia funkcję ułatwień dostępu."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Włączyć ułatwienia dostępu?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Włączyć skrót ułatwień dostępu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza ułatwienia dostępu. Może to zmienić sposób działania urządzenia.\n\nBieżące funkcje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\naby zmienić wybrane funkcje, kliknij Ustawienia &gt; Ułatwienia dostępu."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Włączyć usługę <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Włączyć skrót <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Przytrzymanie obu klawiszy głośności przez kilka sekund włącza usługę <xliff:g id="SERVICE">%1$s</xliff:g>, stanowiącą ułatwienie dostępu. Może to zmienić sposób działania urządzenia.\n\nAby zmienić ten skrót i wskazać inną funkcję, kliknij Ustawienia &gt; Ułatwienia dostępu."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Włącz"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nie włączaj"</string>
@@ -1891,6 +1895,7 @@
<item quantity="other">%d godz.</item>
<item quantity="one">1 godz.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (następny alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dopóki nie wyłączysz"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index aedc0471ac6f..450fddb025e2 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Este app pode reconhecer sua atividade física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e gravar vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Enquanto está sendo usado, este app pode tirar fotos e gravar vídeos usando a câmera."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"tirar fotos e gravar vídeos em segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que um aplicativo ou serviço acesse as câmeras do sistema para tirar fotos e gravar vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esse app do sistema ou com privilégios pode tirar fotos e gravar vídeos a qualquer momento usando a câmera do sistema. É necessário que o app tenha também a permissão android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que um aplicativo ou serviço receba callbacks sobre dispositivos de câmera sendo abertos ou fechados."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ativar os recursos de acessibilidade?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ativar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Por %d horas</item>
<item quantity="other">Por %d horas</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Até você desativar"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 883e4bf09251..6862001b874e 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas definições de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que a app modifique definições de áudio globais, tais como o volume e qual o altifalante utilizado para a saída de som."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Esta app pode gravar áudio através do microfone em qualquer altura."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app pode gravar áudio através do microfone enquanto estiver a ser utilizada."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Esta app pode gravar áudio através do microfone em qualquer altura."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que a app envie comandos para o SIM. Esta ação é muito perigosa."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer a atividade física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Esta app consegue reconhecer a sua atividade física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Esta app pode tirar fotos e gravar vídeos através da câmara em qualquer altura."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Esta app pode tirar fotos e gravar vídeos através da câmara enquanto estiver a ser utilizada."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"tirar fotos e gravar vídeos em segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Esta app pode tirar fotos e gravar vídeos através da câmara em qualquer altura."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que uma app ou um serviço aceda às câmaras do sistema para tirar fotos e vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esta app do sistema ou privilegiada pode tirar fotos e gravar vídeos através de uma câmara do sistema em qualquer altura. Também necessita da autorização android.permission.CAMERA para a app."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que uma app ou um serviço receba chamadas de retorno sobre dispositivos de câmara que estão a ser abertos ou fechados"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir com um volume elevado durante longos períodos poderá ser prejudicial para a sua audição."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Pretende utilizar o atalho de acessibilidade?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Pretende ativar as funcionalidades de acessibilidade?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Pretende ativar o atalho das funcionalidades de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter premidas ambas as teclas de volume durante alguns segundos ativa as funcionalidades de acessibilidade. Estas podem alterar a forma como o seu dispositivo funciona.\n\nFuncionalidades atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\npode alterar as funcionalidades selecionadas em Definições &gt; Acessibilidade."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Pretende ativar o serviço <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Pretende ativar o atalho do serviço <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter premidas ambas as teclas de volume durante alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, uma funcionalidade de acessibilidade. Esta pode alterar a forma como o seu dispositivo funciona.\n\nPode alterar este atalho para outra funcionalidade em Definições &gt; Acessibilidade."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Durante %d h</item>
<item quantity="one">Durante 1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Até desativar"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index aedc0471ac6f..450fddb025e2 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"alterar as suas configurações de áudio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que o app modifique configurações de áudio globais como volume e alto-falantes de saída."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"gravar áudio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Enquanto está sendo usado, este app pode gravar áudio usando o microfone."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"gravar áudio em segundo plano"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Este app pode gravar áudio usando o microfone a qualquer momento."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"enviar comandos para o chip"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite que o app envie comandos ao chip. Muito perigoso."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"reconhecer atividade física"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Este app pode reconhecer sua atividade física."</string>
<string name="permlab_camera" msgid="6320282492904119413">"tirar fotos e gravar vídeos"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Enquanto está sendo usado, este app pode tirar fotos e gravar vídeos usando a câmera."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"tirar fotos e gravar vídeos em segundo plano"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Este app pode tirar fotos e gravar vídeos usando a câmera a qualquer momento."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permitir que um aplicativo ou serviço acesse as câmeras do sistema para tirar fotos e gravar vídeos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Esse app do sistema ou com privilégios pode tirar fotos e gravar vídeos a qualquer momento usando a câmera do sistema. É necessário que o app tenha também a permissão android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permitir que um aplicativo ou serviço receba callbacks sobre dispositivos de câmera sendo abertos ou fechados."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Quando o atalho estiver ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ativar os recursos de acessibilidade?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ativar atalho para recursos de acessibilidade?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Manter as duas teclas de volume pressionadas por alguns segundos ativa os recursos de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nRecursos atuais:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nÉ possível mudar os recursos selecionados em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ativar <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ativar atalho para <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Manter as duas teclas de volume pressionadas por alguns segundos ativa o serviço <xliff:g id="SERVICE">%1$s</xliff:g>, um recurso de acessibilidade. Isso pode mudar a forma como seu dispositivo funciona.\n\nÉ possível trocar o uso desse atalho para outro recurso em \"Config. &gt; Acessibilidade\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Ativar"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Não ativar"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Por %d horas</item>
<item quantity="other">Por %d horas</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Até às <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Até <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (próximo alarme)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Até você desativar"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index ebc5827c2a7a..f9e608fd0b8e 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"modificare setări audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite aplicației să modifice setările audio globale, cum ar fi volumul și difuzorul care este utilizat pentru ieșire."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"înregistreze sunet"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Această aplicație poate înregistra conținut audio folosind microfonul în orice moment."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Această aplicație poate să înregistreze conținut audio folosind microfonul când este în uz."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"să înregistreze conținut audio în fundal"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Această aplicație poate înregistra conținut audio folosind microfonul oricând."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"să trimită comenzi către SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Permite aplicației să trimită comenzi pe cardul SIM. Această permisiune este foarte periculoasă."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"recunoașterea activității fizice"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Această aplicație vă poate recunoaște activitatea fizică."</string>
<string name="permlab_camera" msgid="6320282492904119413">"realizarea de fotografii și videoclipuri"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Această aplicație poate să facă fotografii și să înregistreze videoclipuri folosind camera foto în orice moment."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Această aplicație poate să fotografieze și să înregistreze videoclipuri folosind camera foto când este în uz."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"să fotografieze și să înregistreze videoclipuri în fundal"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Această aplicație poate să fotografieze și să înregistreze videoclipuri folosind camera foto oricând."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Permiteți unei aplicații sau unui serviciu accesul la camerele de sistem, ca să fotografieze și să înregistreze videoclipuri"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Această aplicație de sistem privilegiată poate să fotografieze și să înregistreze videoclipuri folosind o cameră de sistem în orice moment. Necesită și permisiunea android.permission.CAMERA pentru aplicație"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Permiteți unei aplicații sau unui serviciu să primească apeluri inverse atunci când sunt deschise sau închise dispozitive cu cameră."</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Atunci când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de trei secunde, veți lansa o funcție de accesibilitate."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Activați funcțiile de accesibilitate?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Activați comanda rapidă pentru funcțiile de accesibilitate?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Dacă apăsați ambele taste de volum câteva secunde, activați funcțiile de accesibilitate. Acest lucru poate schimba funcționarea dispozitivului.\n\nFuncțiile actuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuteți schimba funcțiile selectate din Setări &gt; Accesibilitate."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Activați <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Activați comanda rapidă <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Dacă apăsați ambele taste de volum câteva secunde, activați funcția de accesibilitate <xliff:g id="SERVICE">%1$s</xliff:g>. Acest lucru poate schimba funcționarea dispozitivului.\n\nPuteți alege altă funcție pentru această comandă în Setări &gt; Accesibilitate."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Activați"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nu activați"</string>
@@ -1860,6 +1864,7 @@
<item quantity="other">Pentru %d h</item>
<item quantity="one">Pentru 1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Până <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Până la <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (următoarea alarmă)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Până când dezactivați"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index f3d13ceec060..be24770dcc2d 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"Изменение настроек аудио"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Приложение сможет изменять системные настройки звука, например уровень громкости и активный динамик."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"Запись аудио"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Приложение может в любое время записывать аудио с помощью микрофона."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Когда приложение используется, оно может записывать аудио с помощью микрофона."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"Записывать аудио в фоновом режиме"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Приложение может в любое время записывать аудио с помощью микрофона."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"Отправка команд SIM-карте"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Приложение сможет отправлять команды SIM-карте (данное разрешение представляет большую угрозу)."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"распознавать физическую активность"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Приложение может распознавать физическую активность."</string>
<string name="permlab_camera" msgid="6320282492904119413">"Фото- и видеосъемка"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Приложение может в любое время делать фотографии и снимать видео с помощью камеры."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Когда приложение используется, оно может делать фотографии и снимать видео с помощью камеры."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"Делать фотографии и снимать видео в фоновом режиме"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Приложение может в любое время делать фотографии и снимать видео с помощью камеры."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Доступ приложения или сервиса к системным настройкам камеры, позволяющим снимать фото и видео"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Это привилегированное или системное приложение может в любое время делать фотографии и записывать видео с помощью камеры. Для этого приложению также требуется разрешение android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Разрешить приложению или сервису получать обратные вызовы при открытии и закрытии камер"</string>
@@ -1315,7 +1319,7 @@
<string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Отмена"</string>
<string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Запомнить выбор"</string>
<string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Это можно изменить позже в разделе настроек \"Приложения\"."</string>
- <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Всегда разрешать"</string>
+ <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Разрешать всегда"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Не разрешать"</string>
<string name="sim_removed_title" msgid="5387212933992546283">"SIM-карта удалена"</string>
<string name="sim_removed_message" msgid="9051174064474904617">"Пока вы не вставите действующую SIM-карту, мобильная сеть будет недоступна."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Установить громкость выше рекомендуемого уровня?\n\nВоздействие громкого звука в течение долгого времени может привести к повреждению слуха."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Использовать быстрое включение?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Чтобы использовать функцию специальных возможностей, когда она включена, нажмите и удерживайте обе кнопки регулировки громкости в течение трех секунд."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Включить специальные возможности?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Использовать быстрое включение?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Чтобы включить специальные возможности, нажмите обе кнопки регулировки громкости и удерживайте несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nТекущие функции:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nЧтобы изменить выбранные функции, перейдите в настройки и нажмите \"Специальные возможности\"."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Включить функцию \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Использовать быстрое включение сервиса \"<xliff:g id="SERVICE">%1$s</xliff:g>\"?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Чтобы включить функцию \"<xliff:g id="SERVICE">%1$s</xliff:g>\", нажмите обе кнопки регулировки громкости на несколько секунд. Обратите внимание, что в работе устройства могут произойти изменения.\n\nЧтобы назначить это сочетание клавиш другой функции, перейдите в настройки и выберите \"Специальные возможности\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Включить"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не включать"</string>
@@ -1891,6 +1895,7 @@
<item quantity="many">На %d часов</item>
<item quantity="other">На %d часа</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (будильник)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Пока вы не отключите"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index cc6783a4fb3d..c23e4757805b 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ඔබගේ ශ්‍රව්‍ය සැකසීම් වෙනස් කරන්න"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ශබ්දය ආදී ගෝලීය ශබ්ද සැකසීම් වෙනස් කිරීමට සහ ප්‍රතිදානය සඳහා භාවිත කරන්නේ කුමන නාදකය දැයි තේරීමට යෙදුමට අවසර දෙන්න."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ශබ්ද පටිගත කරන්න"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"මෙම යෙදුමට ඕනෑම වේලාවක මයික්‍රෆෝනය භාවිතයෙන් ශ්‍රව්‍ය පටිගත කිරීමට හැකිය."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"මෙම යෙදුමට එය භාවිතයෙහි ඇති අතරතුර මයික්‍රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"පසුබිමෙහි ඕඩියෝ පටිගත කරන්න"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"මෙම යෙදුමට ඕනෑම වේලාවක මයික්‍රෆෝනය භාවිත කර ඕඩියෝ පටිගත කිරීමට හැකිය."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM වෙත විධාන යැවීම"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"SIM වෙත විධාන ගෙන යාමට යෙදුමට අවසර දෙයි. මෙය ඉතා භයානක වේ."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"ශාරීරික ක්‍රියාකාරකම හඳුනා ගන්න"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"මෙම යෙදුමට ඔබේ ශාරීරික ක්‍රියාකාරකම හඳුනා ගැනීමට නොහැකිය"</string>
<string name="permlab_camera" msgid="6320282492904119413">"පින්තූර සහ වීඩියෝ ගන්න"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"මෙම යෙදුමට ඕනෑම වේලාවක කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"මෙම යෙදුමට එය භාවිතයෙහි ඇති අතරතුර කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"පසුබිමෙහි පින්තූර සහ වීඩියෝ ගන්න"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"මෙම යෙදුමට ඕනෑම වේලාවක කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"පින්තූර සහ වීඩියෝ ගැනීමට පද්ධති කැමරාවලට යෙදුමකට හෝ සේවාවකට ප්‍රවේශය ඉඩ දෙන්න"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"මෙම වරප්‍රසාද ලත් හෝ පද්ධති යෙදුමට ඕනෑම වේලාවක පද්ධති කැමරාව භාවිත කර පින්තූර ගැනීමට සහ වීඩියෝ පටිගත කිරීමට හැකිය. යෙදුම විසින් රඳවා තබා ගැනීමට android.permission.CAMERA ප්‍රවේශයද අවශ්‍ය වේ"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"විවෘත වෙමින් හෝ වැසෙමින් පවතින කැමරා උපාංග පිළිබඳ පසු ඇමතුම් ලබා ගැනීමට යෙදුමකට හෝ සේවාවකට ඉඩ දෙන්න."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"නිර්දේශිතයි මට්ටමට වඩා ශබ්දය වැඩිද?\n\nදිගු කාලයක් සඳහා ඉහළ ශබ්දයක් ඇසීමෙන් ඇතැම් විට ඔබගේ ඇසීමට හානි විය හැක."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ප්‍රවේශ්‍යතා කෙටිමඟ භාවිතා කරන්නද?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"කෙටිමග ක්‍රියාත්මක විට, හඬ පරිමා බොත්තම් දෙකම තත්පර 3ක් තිස්සේ එබීමෙන් ප්‍රවේශ්‍යතා විශේෂාංගය ආරම්භ වනු ඇත."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ප්‍රවේශ්‍යතා විශේෂාංග ක්‍රියාත්මක කරන්නද?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ප්‍රවේශ්‍යතා විශේෂාංග සඳහා කෙටි මග ක්‍රියාත්මක කරන්නද?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්‍රවේශ්‍යතා විශේෂාංග ක්‍රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්‍රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nවත්මන් විශේෂාංග:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nඔබට තේරූ විශේෂාංග සැකසීම් &gt; ප්‍රවේශ්‍යතාව හි වෙනස් කළ හැකිය."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ක්‍රියාත්මක කරන්නද?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> කෙටි මග ක්‍රියාත්මක කරන්නද?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"හඬ පරිමා යතුරු දෙකම තත්පර කීපයකට පහළට අල්ලාගෙන සිටීම ප්‍රවේශ්‍යතා විශේෂාංගයක් වන <xliff:g id="SERVICE">%1$s</xliff:g> ක්‍රියාත්මක කරයි. මෙය ඔබේ උපාංගය ක්‍රියා කරන ආකාරය වෙනස් කළ හැකිය.\n\nඔබට මෙම කෙටිමග සැකසීම් &gt; ප්‍රවේශ්‍යතාව හි තවත් විශේෂාංගයකට වෙනස් කළ හැකිය."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ක්‍රියාත්මක කරන්න"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ක්‍රියාත්මක නොකරන්න"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">පැය %d ක් සඳහා</item>
<item quantity="other">පැය %d ක් සඳහා</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> දක්වා"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක්"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> තෙක් (ඊළඟ එලාමය)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ඔබ ක්‍රියාවිරහිත කරන තුරු"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index b6125657f408..980a9451bff3 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"meniť nastavenia zvuku"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Umožňuje aplikácii upraviť globálne nastavenia zvuku, ako je hlasitosť, alebo určiť, z ktorého reproduktora bude zvuk vychádzať."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"nahrávať zvuk"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Táto aplikácia môže kedykoľvek nahrávať zvuk pomocou mikrofónu."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Táto aplikácia môže nahrávať zvuk pomocou mikrofónu, keď ju používate."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"nahrávanie zvuku na pozadí"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Táto aplikácia môže kedykoľvek nahrávať zvuk pomocou mikrofónu."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"posielanie príkazov do SIM karty"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Umožňuje aplikácii odosielať príkazy na SIM kartu. Toto je veľmi nebezpečné povolenie."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"rozpoznávanie fyzickej aktivity"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Táto aplikácia dokáže rozpoznať vašu fyzickú aktivitu."</string>
<string name="permlab_camera" msgid="6320282492904119413">"fotiť a nakrúcať videá"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Táto aplikácia môže kedykoľvek fotografovať a zaznamenávať videá pomocou fotoaparátu."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Táto aplikácia môže fotiť a nahrávať videá pomocou fotoaparátu, keď ju používate."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"fotenie a nahrávanie videí na pozadí"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Táto aplikácia môže kedykoľvek fotiť a nahrávať videá pomocou fotoaparátu."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Povoľte aplikácii alebo službe prístup k fotoaparátom systému na snímanie fotiek a videí"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Táto oprávnená alebo systémová aplikácia môže kedykoľvek fotiť a nahrávať videá fotoaparátom systému. Aplikácia musí mať tiež povolenie android.permission.CAMERA."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Povoliť aplikácii alebo službe prijímať spätné volanie, keď sú zariadenia s kamerou otvorené alebo zatvorené."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšiť hlasitosť nad odporúčanú úroveň?\n\nDlhodobé počúvanie pri vysokej hlasitosti môže poškodiť váš sluch."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použiť skratku dostupnosti?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Keď je skratka zapnutá, stlačením obidvoch tlačidiel hlasitosti na tri sekundy spustíte funkciu dostupnosti."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Chcete zapnúť funkcie dostupnosti?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Chcete zapnúť skratku pre funkcie dostupnosti?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Pridržaním oboch tlačidiel hlasitosti na niekoľko sekúnd zapnete funkcie dostupnosti. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nAktuálne funkcie:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nVybrané funkcie môžete zmeniť v časti Nastavenia &gt; Dostupnosť."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Chcete zapnúť <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Chcete zapnúť skratku na službu <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Pridržaním oboch klávesov hlasitosti na niekoľko sekúnd zapnete funkciu dostupnosti <xliff:g id="SERVICE">%1$s</xliff:g>. Môže sa tým zmeniť spôsob fungovania vášho zariadenia.\n\nTúto skratku môžete zmeniť na inú funkciu v časti Nastavenia &gt; Dostupnosť."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Zapnúť"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Nezapínať"</string>
@@ -1891,6 +1895,7 @@
<item quantity="other">Na %d h</item>
<item quantity="one">Na 1 h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (ďalší budík)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dokým funkciu nevypnete"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3ef51a7e6dab..573a082e0439 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"spreminjanje nastavitev zvoka"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Aplikaciji omogoča spreminjanje splošnih zvočnih nastavitev, na primer glasnost in kateri zvočnik se uporablja."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"snemanje zvoka"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ta aplikacija lahko poljubno uporablja mikrofon za snemanje zvoka."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ta aplikacija lahko uporablja mikrofon za snemanje zvoka med uporabo aplikacije."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"snemanje zvoka v ozadju"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ta aplikacija lahko poljubno uporablja mikrofon za snemanje zvoka."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"pošiljanje ukazov na kartico SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Aplikaciji dovoli pošiljanje ukazov kartici SIM. To je lahko zelo nevarno."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"prepoznavanje telesne dejavnosti"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ta aplikacija lahko prepoznava vašo telesno dejavnost."</string>
<string name="permlab_camera" msgid="6320282492904119413">"fotografiranje in snemanje videoposnetkov"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ta aplikacija lahko poljubno uporablja fotoaparat za snemanje fotografij ali videoposnetkov."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ta aplikacija lahko uporablja fotoaparat za snemanje fotografij in videoposnetkov med uporabo aplikacije."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"snemanje fotografij in videoposnetkov v ozadju"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ta aplikacija lahko poljubno uporablja fotoaparat za snemanje fotografij in videoposnetkov."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Aplikaciji ali storitvi dovoli dostop do vgrajenih fotoaparatov za snemanje fotografij in videoposnetkov"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ta prednostna ali sistemska aplikacija lahko z vgrajenim fotoaparatom kadar koli snema fotografije in videoposnetke. Aplikacija mora imeti omogočeno tudi dovoljenje android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Aplikaciji ali storitvi dovoli prejemanje povratnih klicev o odpiranju ali zapiranju naprav s fotoaparati."</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ali želite povečati glasnost nad priporočeno raven?\n\nDolgotrajno poslušanje pri veliki glasnosti lahko poškoduje sluh."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite uporabljati bližnjico funkcij za ljudi s posebnimi potrebami?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Ko je bližnjica vklopljena, pritisnite gumba za glasnost in ju pridržite tri sekunde, če želite zagnati funkcijo za ljudi s posebnimi potrebami."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Želite vklopiti funkcije za ljudi s posebnimi potrebami?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Želite vklopiti bližnjico za funkcije za ljudi s posebnimi potrebami?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili funkcije za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTrenutne funkcije:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nIzbrane funkcije lahko spremenite v meniju »Nastavitve« &gt; »Funkcije za ljudi s posebnimi potrebami«."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Želite vklopiti storitev <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Želite vklopiti bližnjico za <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Če za nekaj sekund pridržite obe tipki za glasnost, boste vklopili storitev <xliff:g id="SERVICE">%1$s</xliff:g>, ki je funkcija za ljudi s posebnimi potrebami. To lahko spremeni način delovanja naprave.\n\nTo bližnjico lahko v meniju »Nastavitve« &gt; »Funkcije za ljudi s posebnimi potrebami« spremenite, da bo uporabljena za drugo funkcijo."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vklopi"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ne vklopi"</string>
@@ -1891,6 +1895,7 @@
<item quantity="few">%d h</item>
<item quantity="other">%d h</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Do <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (naslednji alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Dokler ne izklopite"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 37a995a5a799..f6f3594d0195 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ndrysho cilësimet e audios"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Lejon aplikacionin të modifikojë cilësimet globale të audios siç është volumi dhe se cili altoparlant përdoret për daljen."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"regjistro audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ky aplikacion mund të regjistrojë audio me mikrofonin në çdo kohë."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ky aplikacion mund të regjistrojë audion duke përdorur mikrofonin kur aplikacioni është në përdorim."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"të regjistrojë audion në sfond"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ky aplikacion mund të regjistrojë audion me mikrofonin në çdo kohë."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"dërgo komanda te karta SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Lejon aplikacionin t\'i dërgojë komanda kartës SIM. Kjo është shumë e rrezikshme."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"njih aktivitetin fizik"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ky aplikacion mund të njohë aktivitetin tënd fizik."</string>
<string name="permlab_camera" msgid="6320282492904119413">"bëj fotografi dhe video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ky aplikacion mund të nxjerrë fotografi dhe të regjistrojë video me kamerën tënde në çdo kohë."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ky aplikacion mund të nxjerrë fotografi dhe të regjistrojë video duke përdorur kamerën kur aplikacioni është në përdorim."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"të nxjerrë fotografi dhe video në sfond"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ky aplikacion mund të nxjerrë fotografi dhe të regjistrojë video me kamerën tënde në çdo kohë."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Lejo një aplikacion ose shërbim të ketë qasje në kamerat e sistemit për të shkrepur fotografi dhe regjistruar video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ky aplikacion sistemi ose i privilegjuar mund të nxjerrë fotografi dhe të regjistrojë video duke përdorur një kamerë në çdo moment. Kërkon që autorizimi i android.permission.CAMERA të mbahet edhe nga aplikacioni"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Lejo që një aplikacion ose shërbim të marrë telefonata mbrapsht për pajisjet e kamerës që hapen ose mbyllen."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Të ngrihet volumi mbi nivelin e rekomanduar?\n\nDëgjimi me volum të lartë për periudha të gjata mund të dëmtojë dëgjimin."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Të përdoret shkurtorja e qasshmërisë?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kur shkurtorja është e aktivizuar, shtypja e të dy butonave për 3 sekonda do të nisë një funksion qasshmërie."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Të aktivizohen veçoritë e qasshmërisë?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Të aktivizohet shkurtorja për veçoritë e qasshmërisë?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon veçoritë e qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nVeçoritë aktuale:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nKe ndryshuar veçoritë e zgjedhura te Cilësimet &gt; Qasshmëria."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Të aktivizohet <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Të aktivizohet shkurtorja për <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mbajtja shtypur e dy tasteve të volumit për pak sekonda aktivizon <xliff:g id="SERVICE">%1$s</xliff:g>, një veçori të qasshmërisë. Kjo mund të ndryshojë mënyrën se si funksionon pajisja jote.\n\nMund të ndryshosh këtë shkurtore te një veçori tjetër te Cilësimet &gt; Qasshmëria."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivizo"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Mos e aktivizo"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Për %d orë</item>
<item quantity="one">Për 1 orë</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Deri në <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (alarmi tjetër)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Derisa ta çaktivizosh"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index a3eb3e412c55..366d29b15c93 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -243,7 +243,7 @@
<string name="global_action_power_off" msgid="4404936470711393203">"Искључи"</string>
<string name="global_action_power_options" msgid="1185286119330160073">"Напајање"</string>
<string name="global_action_restart" msgid="4678451019561687074">"Рестартуј"</string>
- <string name="global_action_emergency" msgid="1387617624177105088">"Хитни позив"</string>
+ <string name="global_action_emergency" msgid="1387617624177105088">"Хитан позив"</string>
<string name="global_action_bug_report" msgid="5127867163044170003">"Извештај о грешци"</string>
<string name="global_action_logout" msgid="6093581310002476511">"Заврши сесију"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Снимак екрана"</string>
@@ -435,13 +435,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"промена аудио подешавања"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозвољава апликацији да мења глобална аудио подешавања као што су јачина звука и избор звучника који се користи као излаз."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"снимање аудио записа"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ова апликација може да снима звук помоћу микрофона док се апликација користи."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"да снима звук у позадини"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ова апликација може да снима звук помоћу микрофона у било ком тренутку."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"слање команди на SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Омогућава апликацији да шаље команде SIM картици. То је веома опасно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"препознавање физичких активности"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ова апликација може да препозна физичке активности."</string>
<string name="permlab_camera" msgid="6320282492904119413">"снимање фотографија и видео снимака"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ова апликација може да снима фотографије и видео снимке помоћу камере у било ком тренутку."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ова апликација може да снима слике и видео снимке помоћу камере док се апликација користи."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"да снима слике и видео снимке у позадини"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ова апликација може да снима фотографије и видео снимке помоћу камере у било ком тренутку."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволите некој апликацији или услузи да приступа камерама система да би снимала слике и видео снимке"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ова привилегована системска апликација може да снима слике и видео снимке помоћу камере система у било ком тренутку. Апликација треба да има и дозволу android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволите апликацији или услузи да добија повратне позиве о отварању или затварању уређаја са камером."</string>
@@ -835,7 +839,7 @@
<string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Притисните „Мени“ да бисте откључали телефон или упутите хитан позив."</string>
<string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Притисните „Мени“ за откључавање."</string>
<string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Унесите шаблон за откључавање"</string>
- <string name="lockscreen_emergency_call" msgid="7549683825868928636">"Хитни позив"</string>
+ <string name="lockscreen_emergency_call" msgid="7549683825868928636">"Хитан позив"</string>
<string name="lockscreen_return_to_call" msgid="3156883574692006382">"Назад на позив"</string>
<string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Тачно!"</string>
<string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Пробајте поново"</string>
@@ -1644,10 +1648,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Желите ли да користите пречицу за приступачност?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Желите ли да укључите функције приступачности?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Желите да укључите пречицу за функције приступачности?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ако задржите оба тастера за јачину звука пар секунди, укључиће се функције приступачности. То може да промени начин рада уређаја.\n\nПостојеће функције:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nМожете да промените изабране функције у одељку Подешавања &gt; Приступачност."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Желите ли да укључите услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Желите да укључите пречицу за услугу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ако задржите оба тастера за јачину звука пар секунди, укључује се <xliff:g id="SERVICE">%1$s</xliff:g>, функција приступачности. То може да промени начин рада уређаја.\n\nМожете да промените функцију на коју се односи ова пречица у одељку Подешавања &gt; Приступачност."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Укључи"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не укључуј"</string>
@@ -1815,7 +1819,7 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Ажурирао је администратор"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Избрисао је администратор"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"Потврди"</string>
- <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Да би се продужило трајање батерије, Уштеда батерије:\n\n• укључује тамну тему\n• искључује или ограничава активности у позадини, неке визуелне ефекте и друге функције, на пример, „Ок Google“\n\n"<annotation id="url">"Сазнајте више"</annotation></string>
+ <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Да би се продужило трајање батерије, Уштеда батерије:\n\n• укључује тамну тему\n• искључује или ограничава активности у позадини, неке визуелне ефекте и друге функције, на пример „Ок Google“\n\n"<annotation id="url">"Сазнајте више"</annotation></string>
<string name="battery_saver_description" msgid="6794188153647295212">"Да би се продужило трајање батерије, Уштеда батерије:\n\n• укључује тамну тему\n• искључује или ограничава активности у позадини, неке визуелне ефекте и друге функције, на пример, „Ок Google“"</string>
<string name="data_saver_description" msgid="4995164271550590517">"Да би се смањила потрошња података, Уштеда података спречава неке апликације да шаљу или примају податке у позадини. Апликација коју тренутно користите може да приступа подацима, али ће то чинити ређе. На пример, слике се неће приказивати док их не додирнете."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Желите да укључите Уштеду података?"</string>
@@ -1860,6 +1864,7 @@
<item quantity="few">За %d с</item>
<item quantity="other">За %d с</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (следећи аларм)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Док не искључите"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 3711e2bf0530..988fb9392025 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ändra dina ljudinställningar"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Tillåter att appen ändrar globala ljudinställningar som volym och vilken högtalarutgång som används."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"spela in ljud"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Appen kan spela in ljud med mikrofonen när som helst."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Appen kan ta spela in ljud med mikrofonen när appen används."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"spela in ljud i bakgrunden"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Appen kan spela in ljud med mikrofonen när som helst."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"skicka kommandon till SIM-kortet"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Tillåter att appen skickar kommandon till SIM-kortet. Detta är mycket farligt."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"känn igen fysisk aktivitet"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Den här appen kan känna igen fysisk aktivitet."</string>
<string name="permlab_camera" msgid="6320282492904119413">"ta bilder och spela in videoklipp"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Appen kan ta kort och spela in video med kameran när som helst."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Appen kan ta bilder och spela in video med kameran när appen används."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ta bilder och spela in video i bakgrunden"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Appen kan ta bilder och spela in video med kameran när som helst."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Ge en app eller tjänst behörighet att ta bilder och spela in videor med systemets kameror"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Denna systemapp med särskild behörighet kan ta bilder och spela in videor med systemets kamera när som helst. Appen måste även ha behörigheten android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Tillåt att en app eller tjänst får återanrop när en kameraenhet öppnas eller stängs."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vill du höja volymen över den rekommenderade nivån?\n\nAtt lyssna med stark volym långa stunder åt gången kan skada hörseln."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vill du använda Aktivera tillgänglighet snabbt?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"När kortkommandot har aktiverats startar du en tillgänglighetsfunktion genom att trycka ned båda volymknapparna i tre sekunder."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Vill du aktivera tillgänglighetsfunktionerna?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vill du aktivera genvägen till tillgänglighetsfunktioner?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras tillgänglighetsfunktionerna. Det kan få enheten ett fungera annorlunda.\n\nAktuella funktioner:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nDu kan ändra vilka funktioner som aktiveras under Inställningar &gt; Tillgänglighet."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vill du aktivera <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vill du aktivera genvägen till <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Om du trycker ned båda volymknapparna i ett par sekunder aktiveras <xliff:g id="SERVICE">%1$s</xliff:g>, en tillgänglighetsfunktion. Det kan leda till att enheten fungerar annorlunda.\n\nDu kan ändra vilken funktion som ska aktiveras med genvägen under Inställningar &gt; Tillgänglighet."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Aktivera"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Aktivera inte"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">I %d tim</item>
<item quantity="one">I en 1 tim</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Till <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Till <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Till <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (nästa alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Tills du stänger av"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 23b6c3b5a029..fd390d80f76a 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"badilisha mipangilio yako ya sauti"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Inaruhusu programu kurekebisha mipangilio ya sauti kila mahali kama vile sauti na ni kipaza sauti kipi ambacho kinatumika kwa kutoa."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"kurekodi sauti"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati wowote."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati programu inatumika."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekodi sauti chinichini"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Programu hii inaweza kurekodi sauti kwa kutumia maikrofoni wakati wowote."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"tuma amri kwenye SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Huruhusu programu kutuma amri kwa SIM. Hii ni hatari sana."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"itambue shughuli unazofanya"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Programu hii inaweza kutambua shughuli unazofanya."</string>
<string name="permlab_camera" msgid="6320282492904119413">"Kupiga picha na kurekodi video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Programu hii inaweza kupiga picha na kurekodi video kwa kutumia kamera wakati wowote."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Programu hii inaweza kupiga picha na kurekodi video kwa kutumia kamera wakati programu inatumika."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"piga picha na urekodi video chinichini"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Programu hii inaweza kupiga picha na kurekodi video kwa kutumia kamera wakati wowote."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Ruhusu programu au huduma ifikie kamera za mfumo ili ipige picha na irekodi video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Programu hii ya mfumo au inayopendelewa inaweza kupiga picha na kurekodi video ikitumia kamera ya mfumo wakati wowote. Inahitaji ruhusa ya android.permission.CAMERA iwepo kwenye programu pia"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Ruhusu programu au huduma ipokee simu zinazopigwa tena kuhusu vifaa vya kamera kufunguliwa au kufungwa."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ungependa kupandisha sauti zaidi ya kiwango kinachopendekezwa?\n\nKusikiliza kwa sauti ya juu kwa muda mrefu kunaweza kuharibu uwezo wako wa kusikia."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ungependa kutumia njia ya mkato ya ufikivu?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Unapowasha kipengele cha njia ya mkato, hatua ya kubonyeza vitufe vyote viwili vya sauti kwa sekunde tatu itafungua kipengele cha ufikivu."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Ungependa kuwasha vipengele vya ufikivu?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Ungependa kuwasha njia ya mkato ya vipengele vya ufikivu?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha vipengele vya ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nVipengele vya sasa:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUnaweza kubadilisha vipengele ulivyochagua katika Mipangilio &gt; Ufikivu."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Ungependa kuwasha <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Ungependa kuwasha njia ya mkato ya <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Hatua ya kushikilia chini vitufe vyote viwili vya sauti kwa sekunde chache huwasha <xliff:g id="SERVICE">%1$s</xliff:g>, kipengele cha ufikivu. Huenda hatua hii ikabadilisha jinsi kifaa chako kinavyofanya kazi.\n\nUnaweza kubadilisha njia hii ya mkato iwe kipengele kingine katika Mipangilio &gt; Ufikivu."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Washa"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Usiwashe"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Kwa saa %d </item>
<item quantity="one">Kwa saa 1 </item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Hadi <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Hadi <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Mpaka <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (kengele inayofuata)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Hadi utakapoizima"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 42b2d9c2f273..c764a205fe59 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"எனது ஆடியோ அமைப்புகளை மாற்றுதல்"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ஒலியளவு மற்றும் வெளியீட்டிற்கு ஸ்பீக்கர்கள் பயன்படுத்தப்படுவது போன்ற ஒட்டுமொத்த ஆடியோ அமைப்புகளைக் கட்டுப்படுத்தப் ஆப்ஸை அனுமதிக்கிறது."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ஆடியோவைப் பதிவுசெய்தல்"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்யலாம்."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"இந்த ஆப்ஸ் உபயோகத்தில் இருக்கும்போதே இதனால் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"பின்புலத்தில் ஆடியோ ரெக்கார்டு செய்தல்"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"இந்த ஆப்ஸால் எப்போது வேண்டுமானாலும் மைக்ரோஃபோனைப் பயன்படுத்தி ஆடியோவை ரெக்கார்டு செய்ய முடியும்."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"கட்டளைகளை சிம்மிற்கு அனுப்புதல்"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"சிம் க்குக் கட்டளைகளை அனுப்ப ஆப்ஸை அனுமதிக்கிறது. இது மிகவும் ஆபத்தானதாகும்."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"உடல் செயல்பாட்டைக் கண்டறிதல்"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"உங்கள் உடல் செயல்பாட்டை இந்த ஆப்ஸால் கண்டறிய முடியும்."</string>
<string name="permlab_camera" msgid="6320282492904119413">"படங்கள் மற்றும் வீடியோக்களை எடுத்தல்"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் கேமராவைப் பயன்படுத்தி படங்களை எடுக்கலாம், வீடியோக்களை ரெக்கார்டு செய்யலாம்."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"இந்த ஆப்ஸ் உபயோகத்தில் இருக்கும்போதே இதனால் கேமராவைப் பயன்படுத்தி படங்கள் எடுக்கவும் வீடியோக்களை ரெக்கார்டு செய்யவும் முடியும்."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"பின்புலத்தில் படங்களையும் வீடியோக்களையும் எடுத்தல்"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"இந்த ஆப்ஸால் எப்போது வேண்டுமானாலும் கேமராவைப் பயன்படுத்தி படங்கள் எடுக்கவும் வீடியோக்களை ரெக்கார்டு செய்யவும் முடியும்."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"படங்களையும் வீடியோக்களையும் எடுப்பதற்கு சிஸ்டம் கேமராக்களை அணுக ஆப்ஸையோ சேவையையோ அனுமதி"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"இந்த முன்னுரிமை பெற்ற அல்லது சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"கேமரா சாதனங்கள் திறக்கப்படும்போதோ மூடப்படும்போதோ அது குறித்த கால்பேக்குகளைப் பெற ஒரு ஆப்ஸையோ சேவையையோ அனுமதிக்கவும்."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"பரிந்துரைத்த அளவை விட ஒலியை அதிகரிக்கவா?\n\nநீண்ட நேரத்திற்கு அதிகளவில் ஒலி கேட்பது கேட்கும் திறனைப் பாதிக்கலாம்."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"அணுகல்தன்மை ஷார்ட்கட்டைப் பயன்படுத்தவா?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"ஷார்ட்கட் இயக்கத்தில் இருக்கும்போது ஒலியளவு பட்டன்கள் இரண்டையும் 3 வினாடிகளுக்கு அழுத்தினால் அணுகல்தன்மை அம்சம் இயக்கப்படும்."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"அணுகல்தன்மை அம்சங்களை ஆன் செய்யவா?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"அணுகல்தன்மை அம்சங்களுக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருந்தால் அணுகல்தன்மை அம்சங்கள் ஆன் செய்யப்படும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nதற்போதைய அம்சங்கள்:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nதேர்ந்தெடுத்த அம்சங்களை அமைப்புகள் &gt; அணுகல்தன்மைக்குச் சென்று உங்களால் மாற்ற முடியும்."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ஐ ஆன் செய்யவா?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> அம்சத்துக்கான ஷார்ட்கட்டை ஆன் செய்யவா?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"இரண்டு ஒலியளவு விசைகளையும் சில விநாடிகள் பிடித்திருப்பதால் அணுகல்தன்மை அம்சமான <xliff:g id="SERVICE">%1$s</xliff:g> ஆன் ஆகும். இதனால் உங்கள் சாதனம் வேலை செய்யும் முறை மாறக்கூடும்.\n\nஅமைப்புகள் &gt; அணுகல்தன்மைக்குச் சென்று இந்த ஷார்ட்கட்டை வேறு அம்சத்திற்கு மாற்ற முடியும்."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ஆன் செய்"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ஆன் செய்யாதே"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d மணிநேரத்திற்கு</item>
<item quantity="one">1 மணிநேரத்திற்கு</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> வரை"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> வரை"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> மணி (அடுத்த அலாரம்) வரை"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"ஆஃப் செய்யும் வரை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 6b9caf15a745..23292a2adc76 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"మీ ఆడియో సెట్టింగ్‌లను మార్చడం"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"వాల్యూమ్ మరియు అవుట్‌పుట్ కోసం ఉపయోగించాల్సిన స్పీకర్ వంటి సార్వజనీన ఆడియో సెట్టింగ్‌లను సవరించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ఆడియోను రికార్డ్ చేయడం"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"ఈ యాప్ మైక్రోఫోన్‌ని ఉపయోగించి ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"యాప్ ఉపయోగంలో ఉన్నపుడు మైక్రోఫోన్‌ను ఉపయోగించి ఈ యాప్, ఆడియోను రికార్డ్ చేయగలదు."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"బ్యాక్‌గ్రౌండ్‌లో ఆడియోను రికార్డ్ చేయగలదు"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"మైక్రోఫోన్‌ను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఆడియోను రికార్డ్ చేయగలదు."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIMకి ఆదేశాలను పంపడం"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"సిమ్‌కు ఆదేశాలను పంపడానికి అనువర్తనాన్ని అనుమతిస్తుంది. ఇది చాలా ప్రమాదకరం."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"భౌతిక కార్యాకలాపాన్ని గుర్తించండి"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"ఈ యాప్ మీ భౌతిక కార్యాకలాపాన్ని గుర్తించగలదు."</string>
<string name="permlab_camera" msgid="6320282492904119413">"చిత్రాలు మరియు వీడియోలు తీయడం"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"ఈ యాప్‌ కెమెరాను ఉపయోగించి ఎప్పుడైనా చిత్రాలను తీయగలదు మరియు వీడియోలను రికార్డ్ చేయగలదు."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"యాప్ ఉపయోగంలో ఉన్నపుడు కెమెరాను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఫోటోలను తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"బ్యాక్‌గ్రౌండ్‌లో ఫోటోలు, వీడియోలను తీయగలదు"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"కెమెరాను ఉపయోగించి ఈ యాప్ ఎప్పుడైనా ఫోటోలను తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ఫోటోలు, వీడియోలు తీయడానికి సిస్టమ్ కెమెరాలకు యాప్, లేదా సేవా యాక్సెస్‌ను అనుమతించండి"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"ఈ విశేష లేదా సిస్టమ్ యాప్ ఎప్పుడైనా సిస్టమ్ కెమెరాను ఉపయోగించి ఫోటోలు తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు. యాప్‌కు android.permission.CAMERA అనుమతి ఇవ్వడం కూడా అవసరం"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"కెమెరా పరికరాలు తెరుచుకుంటున్నప్పుడు లేదా మూసుకుంటున్నప్పుడు కాల్‌బ్యాక్‌లను స్వీకరించడానికి యాప్‌ను లేదా సర్వీస్‌ను అనుమతించండి."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"వాల్యూమ్‌ను సిఫార్సు చేయబడిన స్థాయి కంటే ఎక్కువగా పెంచాలా?\n\nసుదీర్ఘ వ్యవధుల పాటు అధిక వాల్యూమ్‌లో వినడం వలన మీ వినికిడి శక్తి దెబ్బ తినవచ్చు."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"యాక్సెస్ సామర్థ్యం షార్ట్‌కట్‌ను ఉపయోగించాలా?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"షార్ట్‌కట్ ఆన్ చేసి ఉన్నప్పుడు, రెండు వాల్యూమ్ బటన్‌లను 3 సెకన్ల పాటు నొక్కి ఉంచితే యాక్సెస్ సౌలభ్య ఫీచర్ ప్రారంభం అవుతుంది."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"యాక్సెసిబిలిటీ‌ ఫీచ‌ర్‌ల‌ను ఆన్ చేయాలా?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"యాక్సెస్ సౌలభ్య ఫీచర్‌ల కోసం షార్ట్‌కట్‌ను ఆన్ చేయాలా?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"రెండు వాల్యూమ్ కీలను కొంత సేపు నొక్కి పట్టుకుంటే యాక్సెసిబిలిటీ ఫీచ‌ర్‌లు ఆన్ అవుతాయి. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nప్రస్తుత ఫీచర్లు:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nఎంపిక చేసిన ఫీచర్లను మీరు సెట్టింగ్‌లు&gt;యాక్సెసిబిలిటీలో మార్చవచ్చు."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> ఆన్ చేయాాలా?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> షార్ట్‌కట్‌ను ఆన్ చేయాలా?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"రెండు వాల్యూమ్ కీలను కొన్ని సెకన్ల పాటు నొక్కి పట్టుకోవడం ద్వారా యాక్సెసిబిలిటీ అయిన <xliff:g id="SERVICE">%1$s</xliff:g> ఆన్ అవుతుంది. ఇది మీ పరికరం పని చేసే విధానాన్ని మార్చవచ్చు.\n\nసెట్టింగ్‌లు &gt; యాక్సెసిబిలిటీలో, వేరొక ఫీచర్‌ను ప్రారంభించేలా ఈ షార్ట్ కట్‌ను మీరు మార్చవచ్చు."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"ఆన్ చేయి"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ఆన్ చేయకండి"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d గం పాటు</item>
<item quantity="one">1 గం పాటు</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> వరకు"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> వరకు"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (తర్వాత అలారం) వరకు"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"మీరు ఆఫ్‌ చేసే వరకు"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 4b39c63a1bdf..d5d21c633d0a 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"เปลี่ยนการตั้งค่าเสียงของคุณ"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"อนุญาตให้แอปพลิเคชันปรับเปลี่ยนการตั้งค่าเสียงทั้งหมดได้ เช่น ระดับเสียงและลำโพงที่จะใช้งาน"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"บันทึกเสียง"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"แอปนี้สามารถบันทึกเสียงด้วยไมโครโฟนได้ทุกเมื่อ"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"แอปนี้บันทึกเสียงด้วยไมโครโฟนขณะที่มีการใช้แอปได้"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"บันทึกเสียงในเบื้องหลัง"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"แอปนี้บันทึกเสียงด้วยไมโครโฟนได้ทุกเมื่อ"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"ส่งคำสั่งไปยังซิม"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"อนุญาตให้แอปส่งคำสั่งไปยัง SIM ซึ่งอันตรายมาก"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"จดจำกิจกรรมการเคลื่อนไหวร่างกาย"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"แอปนี้จดจำกิจกรรมการเคลื่อนไหวร่างกายของคุณได้"</string>
<string name="permlab_camera" msgid="6320282492904119413">"ถ่ายภาพและวิดีโอ"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"แอปนี้สามารถถ่ายภาพและวิดีโอด้วยกล้องได้ทุกเมื่อ"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"แอปนี้ถ่ายภาพและวิดีโอด้วยกล้องขณะที่มีการใช้แอปได้"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"ถ่ายภาพและวิดีโอในเบื้องหลัง"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"แอปนี้ถ่ายภาพและวิดีโอด้วยกล้องได้ทุกเมื่อ"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"อนุญาตให้แอปพลิเคชันหรือบริการเข้าถึงกล้องของระบบเพื่อถ่ายภาพและวิดีโอ"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"แอปของระบบหรือที่ได้รับสิทธิ์นี้จะถ่ายภาพและบันทึกวิดีโอโดยใช้กล้องของระบบได้ทุกเมื่อ แอปต้องมีสิทธิ์ android.permission.CAMERA ด้วย"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"อนุญาตให้แอปพลิเคชันหรือบริการได้รับโค้ดเรียกกลับเมื่อมีการเปิดหรือปิดอุปกรณ์กล้อง"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"นี่เป็นการเพิ่มระดับเสียงเกินระดับที่แนะนำ\n\nการฟังเสียงดังเป็นเวลานานอาจทำให้การได้ยินของคุณบกพร่องได้"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ใช้ทางลัดการช่วยเหลือพิเศษไหม"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"เมื่อทางลัดเปิดอยู่ การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มนาน 3 วินาทีจะเริ่มฟีเจอร์การช่วยเหลือพิเศษ"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ต้องการเปิดฟีเจอร์การช่วยเหลือพิเศษใช่ไหม"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"เปิดใช้ทางลัดสำหรับฟีเจอร์การช่วยเหลือพิเศษใช่ไหม"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิดฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nฟีเจอร์ปัจจุบัน:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nคุณจะเปลี่ยนฟีเจอร์ที่เลือกไว้ได้ในการตั้งค่า &gt; การช่วยเหลือพิเศษ"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"ต้องการเปิด <xliff:g id="SERVICE">%1$s</xliff:g> ใช่ไหม"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"เปิดใช้ทางลัด <xliff:g id="SERVICE">%1$s</xliff:g> ใช่ไหม"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มค้างไว้ 2-3 วินาทีจะเปิด <xliff:g id="SERVICE">%1$s</xliff:g> ซึ่งเป็นฟีเจอร์การช่วยเหลือพิเศษ การดำเนินการนี้อาจเปลี่ยนแปลงลักษณะการทำงานของอุปกรณ์\n\nคุณแก้ไขทางลัดนี้ให้เปิดฟีเจอร์อื่นได้ในการตั้งค่า &gt; การช่วยเหลือพิเศษ"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"เปิด"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"ไม่ต้องเปิด"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">เป็นเวลา %d ชม.</item>
<item quantity="one">เป็นเวลา 1 ชม.</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"จนถึง <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"จนถึงเวลา <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (การปลุกครั้งถัดไป)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"จนกว่าคุณจะปิด"</string>
@@ -2030,7 +2035,7 @@
<item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> และอีก <xliff:g id="COUNT_3">%d</xliff:g> ไฟล์</item>
<item quantity="one"><xliff:g id="FILE_NAME_0">%s</xliff:g> และอีก <xliff:g id="COUNT_1">%d</xliff:g> ไฟล์</item>
</plurals>
- <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ไม่มีบุคคลที่แนะนำให้แชร์ด้วย"</string>
+ <string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"ไม่พบใครที่แนะนำให้แชร์ด้วย"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"รายชื่อแอป"</string>
<string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"แอปนี้ไม่ได้รับอนุญาตให้บันทึกเสียงแต่จะบันทึกเสียงผ่านอุปกรณ์ USB นี้ได้"</string>
<string name="accessibility_system_action_home_label" msgid="3234748160850301870">"หน้าแรก"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index beebbd23448c..f53851d74ff9 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"baguhin ang mga setting ng iyong audio"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Pinapayagan ang app na baguhin ang mga pandaigdigang setting ng audio gaya ng volume at kung aling speaker ang ginagamit para sa output."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"mag-record ng audio"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Makakapag-record ng audio ang app na ito gamit ang mikropono anumang oras."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Makakapag-record ng audio ang app na ito gamit ang mikropono habang ginagamit ang app."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"mag-record ng audio sa background"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Makakapag-record ng audio ang app na ito gamit ang mikropono anumang oras."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"magpadala ng mga command sa SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Pinapahintulutang magpadala ang app ng mga command sa SIM. Napakapanganib nito."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"tukuyin ang pisikal na aktibidad"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Matutukoy ng app na ito ang iyong pisikal na aktibidad."</string>
<string name="permlab_camera" msgid="6320282492904119413">"kumuha ng mga larawan at video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Makakakuha ng mga larawan at makakapag-record ng mga video ang app na ito gamit ang camera anumang oras."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Makakakuha ng mga larawan at makakapag-record ng mga video ang app na ito gamit ang camera habang ginagamit ang app."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"kumuha ng mga larawan at video sa background"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Makakakuha ng mga larawan at makakapag-record ng mga video ang app na ito gamit ang camera anumang oras."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Bigyan ang isang application o serbisyo ng access sa mga camera ng system para kumuha ng mga larawan at video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ang may pribilehiyong app o system app na ito ay makakakuha ng mga larawan at makakapag-record ng mga video gamit ang isang camera ng system anumang oras. Kinakailangang may android.permission.CAMERA na pahintulot din ang app"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Payagan ang isang application o serbisyo na makatanggap ng mga callback tungkol sa pagbubukas o pagsasara ng mga camera device."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Accessibility?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"I-on ang mga feature ng accessibility?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"I-on ang shortcut para sa mga feature ng pagiging naa-access?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Mao-on ang mga feature ng accessibility kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nMga kasalukuyang feature:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nPuwede mong baguhin ang mga napiling feature sa Mga Setting &gt; Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"I-on ang <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"I-on ang shortcut ng <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Mao-on ang feature ng accessibility na <xliff:g id="SERVICE">%1$s</xliff:g> kapag pinindot nang matagal ang parehong volume key nang ilang segundo. Posibleng mabago nito ang paggana ng iyong device.\n\nPuwede mong palitan ng ibang feature ang shortcut na ito sa Mga Setting &gt; Accessibility."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"I-on"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Huwag i-on"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Sa loob ng %d oras</item>
<item quantity="other">Sa loob ng %d na oras</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Hanggang <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (susunod na alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Hanggang sa i-off mo"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 17852a6615de..5aa336e9b554 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"ses ayarlarınızı değiştirin"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Uygulamaya ses düzeyi ve ses çıkışı için kullanılan hoparlör gibi genel ses ayarlarını değiştirme izni verir."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ses kaydet"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu uygulama, istediği zaman mikrofonu kullanarak ses kaydedebilir."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu uygulama, kullanıldığı sırada mikrofonu kullanarak ses kaydedebilir."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"arka planda ses kaydeder"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu uygulama, herhangi bir zaman mikrofonu kullanarak ses kaydedebilir."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM karta komut gönderme"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Uygulamanın SIM karta komut göndermesine izin verir. Bu izin çok tehlikelidir."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"fiziksel aktiviteyi algıla"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu uygulama fiziksel aktivitenizi algılayabilir."</string>
<string name="permlab_camera" msgid="6320282492904119413">"resim çekme ve görüntü kaydetme"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Bu uygulama, herhangi bir zamanda kamerayı kullanarak fotoğraf ve video çekebilir."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Bu uygulama, kullanıldığı sırada kamerayı kullanarak fotoğraf ve video çekebilir."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"arka planda resim ve video çeker"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Bu uygulama, herhangi bir zaman kamerayı kullanarak fotoğraf ve video çekebilir."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Bir uygulama veya hizmetin fotoğraf ve video çekmek için sistem kameralarına erişmesine izin verin"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ayrıcalık tanınmış bu veya sistem uygulaması herhangi bir zamanda sistem kamerası kullanarak fotoğraf çekebilir ve video kaydedebilir. Uygulamanın da bu ayrıcalığa sahip olması için android.permission.CAMERA izni gerektirir"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Bir uygulama veya hizmetin açılıp kapatılan kamera cihazları hakkında geri çağırmalar almasına izin verin."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ses seviyesi önerilen düzeyin üzerine yükseltilsin mi?\n\nUzun süre yüksek ses seviyesinde dinlemek işitme duyunuza zarar verebilir."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erişilebilirlik Kısayolu Kullanılsın mı?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Kısayol açıkken ses düğmelerinin ikisini birden 3 saniyeliğine basılı tutmanız bir erişilebilirlik özelliğini başlatır."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Erişilebilirlik özellikleri etkinleştirilsin mi?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Erişilebilirlik özellikleri için kısayol açılsın mı?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak, erişilebilirlik özelliklerini açar. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nGeçerli özellikler:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nSeçilen özellikleri Ayarlar &gt; Erişilebilirlik\'te değiştirebilirsiniz."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> etkinleştirilsin mi?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> kısayolu açılsın mı?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ses tuşlarının ikisini birden birkaç saniyeliğine basılı tutmak <xliff:g id="SERVICE">%1$s</xliff:g> erişilebilirlik özelliğini etkinleştirir. Bu, cihazınızın çalışma şeklini değiştirebilir.\n\nBu kısayolu, Ayarlar &gt; Erişilebilirlik\'te başka bir özellikle değiştirebilirsiniz."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Etkinleştir"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Etkinleştirme"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d saat</item>
<item quantity="one">1 saat</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Şu saate kadar: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Şu saate kadar: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (sonraki alarma) saatine kadar"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Siz kapatana kadar"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 31684e0d1b29..da41508b08cf 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -438,13 +438,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"змінювати налаштув-ня звуку"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Дозволяє програмі змінювати загальні налаштування звуку, як-от гучність і динамік, який використовується для виводу сигналу."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"запис-ти аудіо"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Цей додаток може будь-коли записувати звук за допомогою мікрофона."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Цей додаток може записувати звук за допомогою мікрофона, коли ви використовуєте його."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"записувати звук у фоновому режимі"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Цей додаток може будь-коли записувати звук за допомогою мікрофона."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"надсилати команди на SIM-карту"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Дозволяє програмі надсилати команди на SIM-карту. Це дуже небезпечно."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"розпізнавати фізичну активність"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Цей додаток може розпізнавати фізичну активність."</string>
<string name="permlab_camera" msgid="6320282492904119413">"фотограф. та знімати відео"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Цей додаток може будь-коли робити фотографії та записувати відео за допомогою камери."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Цей додаток може робити фотографії та записувати відео за допомогою камери, коли ви використовуєте його."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"робити фотографії та записувати відео у фоновому режимі"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Цей додаток може будь-коли робити фотографії та записувати відео за допомогою камери."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Дозволити додатку або сервісу отримувати доступ до системних камер, робити фото й записувати відео"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Цей пріоритетний системний додаток може будь-коли робити фото й записувати відео, використовуючи камеру системи. Додатку потрібен дозвіл android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Дозволити додатку або сервісу отримувати зворотні виклики щодо відкриття чи закриття камер."</string>
@@ -1133,7 +1137,7 @@
<string name="elapsed_time_short_format_mm_ss" msgid="8689459651807876423">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="2302144714803345056">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="1532369154488982046">"Вибрати все"</string>
- <string name="cut" msgid="2561199725874745819">"Виріз."</string>
+ <string name="cut" msgid="2561199725874745819">"Вирізати"</string>
<string name="copy" msgid="5472512047143665218">"Копіювати"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Не вдалося скопіювати в буфер обміну"</string>
<string name="paste" msgid="461843306215520225">"Вставити"</string>
@@ -1666,10 +1670,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Збільшити гучність понад рекомендований рівень?\n\nЯкщо слухати надто гучну музику тривалий час, можна пошкодити слух."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Використовувати швидке ввімкнення?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Якщо цей засіб увімкнено, ви можете активувати спеціальні можливості, утримуючи обидві кнопки гучності протягом трьох секунд."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Увімкнути спеціальні можливості?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Увімкнути засіб спеціальних можливостей?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, вмикаються спеціальні можливості. Це впливає на роботу пристрою.\n\nПоточні функції:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nВибрані функції можна змінити в налаштуваннях у меню спеціальних можливостей."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Увімкнути <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Увімкнути засіб швидкого доступу до сервісу <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Якщо втримувати обидві клавіші гучності впродовж кількох секунд, буде ввімкнено спеціальні можливості – <xliff:g id="SERVICE">%1$s</xliff:g>. Це може вплинути на роботу пристрою.\n\nДля цієї комбінації клавіш можна вибрати іншу функцію в меню \"Налаштування &gt; Спеціальні можливості\"."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Увімкнути"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Не вмикати"</string>
@@ -1856,7 +1860,7 @@
<item quantity="other">Протягом %1$d хв (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_hours_summary" formatted="false" msgid="7725354244196466758">
- <item quantity="one">%1$d годину (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
+ <item quantity="one">%1$d година (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="few">%1$d години (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="many">%1$d годин (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">%1$d години (до <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
@@ -1891,6 +1895,7 @@
<item quantity="many">Протягом %d год</item>
<item quantity="other">Протягом %d год</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"До: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"До <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (наступний будильник)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Доки ви не вимкнете"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index ed32b2453bf3..94fad2521efa 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"اپنے آڈیو کی ترتیبات کو تبدیل کریں"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"ایپ کو مجموعی آڈیو ترتیبات جیسے والیوم اور آؤٹ پٹ کیلئے جو اسپیکر استعمال ہوتا ہے اس میں ترمیم کرنے کی اجازت دیتا ہے۔"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"آڈیو ریکارڈ کریں"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"یہ ایپ کسی بھی وقت مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"ایپ کے استعمال ہونے کے دوران یہ ایپ مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"پس منظر میں آڈیو ریکارڈ کریں"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"یہ ایپ کسی بھی وقت مائیکروفون استعمال کرتے ہوئے آڈیو ریکارڈ کر سکتی ہے۔"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"‏SIM کو ہدایات بھیجیں"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"‏ایپ کو SIM کو کمانڈز بھیجنے کی اجازت دیتا ہے۔ یہ بہت خطرناک ہے۔"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"جسمانی سرگرمی کی شناخت کریں"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"یہ ایپ آپ کی جسمانی سرگرمی کی شناخت کر سکتی ہے۔"</string>
<string name="permlab_camera" msgid="6320282492904119413">"تصاویر لیں اور ویڈیوز بنائیں"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"یہ ایپ کسی بھی وقت کیمرا استعمال کرتے ہوئے تصاویر لے سکتی ہے اور ویڈیوز ریکارڈ کر سکتی ہے۔"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"ایپ کے استعمال ہونے کے دوران یہ ایپ کیمرا استعمال کرتے ہوئے تصاویر لے سکتی ہے اور ویڈیوز ریکارڈ کر سکتی ہے۔"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"پس منظر میں تصاویر لیں اور ویڈیوز ریکارڈ کریں"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"یہ ایپ کسی بھی وقت کیمرا استعمال کرتے ہوئے تصاویر لے سکتی ہے اور ویڈیوز ریکارڈ کر سکتی ہے۔"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"ایپلیکیشن یا سروس کو سسٹم کے کیمرے تک رسائی حاصل کرنے کی اجازت دیتا ہے تاکہ وہ تصاویر لیں اور ویڈیوز ریکارڈ کریں۔"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"‏یہ مراعات یافتہ یا سسٹم ایپ کسی بھی وقت ایک سسٹم کیمرا استعمال کرتے ہوئے تصاویر اور ویڈیوز ریکارڈ کر سکتی ہے۔ ایپ کے پاس android.permission.CAMERA کے ليے بھی اجازت ہونا ضروری ہے۔"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"ایپلیکیشن یا سروس کو کیمرا کے آلات کے کُھلنے یا بند ہونے سے متعلق کال بیکس موصول کرنے کی اجازت دیں۔"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"والیوم کو تجویز کردہ سطح سے زیادہ کریں؟\n\nزیادہ وقت تک اونچی آواز میں سننے سے آپ کی سماعت کو نقصان پہنچ سکتا ہے۔"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ایکسیسبیلٹی شارٹ کٹ استعمال کریں؟"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"شارٹ کٹ آن ہونے پر، 3 سیکنڈ تک دونوں والیوم بٹنز کو دبانے سے ایک ایکسیسبیلٹی خصوصیت شروع ہو جائے گی۔"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"ایکسیسبیلٹی خصوصیات آن کریں؟َ"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"ایکسیسبیلٹی خصوصیات کے لیے شارٹ کٹ آن کریں؟"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"دونوں والیوم کی کلیدوں کو کچھ سیکنڈز تک دبائیں رکھنے سے ایکسیسبیلٹی خصوصیات آن ہو جاتی ہیں۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nموجودہ خصوصیات:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nآپ ترتیبات اور ایکسیسبیلٹی میں منتخب کردہ خصوصیات کو تبدیل کر سکتے ہیں۔"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> آن کریں؟"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> شارٹ کٹ آن کریں؟"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"والیوم کی دونوں کلیدوں کو کچھ سیکنڈز تک دبائے رکھنے سے <xliff:g id="SERVICE">%1$s</xliff:g> ایکسیسبیلٹی خصوصیت آن ہو جاتی ہے۔ اس سے آپ کے آلے کے کام کرنے کا طریقہ تبدیل ہو سکتا ہے۔\n\nآپ ترتیبات اور ایکسیسبیلٹی میں دیگر خصوصیت کے لیے اس شارٹ کٹ کو تبدیل کر سکتے ہیں۔"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"آن کریں"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"آن نہ کریں"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">‏‎%d گھنٹے کیلئے</item>
<item quantity="one">1 گھنٹہ کیلئے</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> تک (اگلا الارم)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"یہاں تک کہ آپ آف کر دیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 7671809ebcc8..61d29dce50b7 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"audio sozlamalaringizni o‘zgartirish"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ilovalarga tovush va ovoz chiqarish uchun foydalaniladigan karnay kabi global audio sozlamalarini o‘zgartirish uchun ruxsat beradi."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ovoz yozib olish"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Bu ilova xohlagan vaqtda mikrofon yordami audio yozib olishi mumkin."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Bu ilova ishlayotganida u mikrofon orqali audio yozib olishi mumkin."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"orqa fonda ovoz yozib olish"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Bu ilova xohlagan vaqtda mikrofon yordami audio yozib olishi mumkin."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"SIM kartaga buyruqlar yuborish"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Dasturga SIM kartaga buyruqlar jo‘natishga ruxsat beradi. Bu juda ham xavfli."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"jismoniy harakatni aniqlash"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Bu ilova jismoniy harakatlaringizni aniqlay oladi."</string>
<string name="permlab_camera" msgid="6320282492904119413">"rasm va videoga olish"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Bu ilova xohlagan vaqtda kamera orqali suratga olishi va video yozib olishi mumkin."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Bu ilova ishlayotganida u kamera orqali suratga olishi va video yozib olishi mumkin."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"orqa fonda surat va videoga olish"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Bu ilova xohlagan vaqtda kamera orqali suratga olishi va video yozib olishi mumkin."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Ilova yoki xizmatga tizim kamerasi orqali surat va videolar olishga ruxsat berish"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Bu imtiyozli yoki tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA ruxsati ham yoqilgan boʻlishi talab qilinadi"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Ilova yoki xizmatga kamera qurilmalari ochilayotgani yoki yopilayotgani haqida qayta chaqiruvlar qabul qilishi uchun ruxsat berish."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Tovush balandligi tavsiya etilgan darajadan ham yuqori qilinsinmi?\n\nUzoq vaqt davomida baland ovozda tinglash eshitish qobiliyatingizga salbiy ta’sir ko‘rsatishi mumkin."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Tezkor ishga tushirishdan foydalanilsinmi?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Maxsus imkoniyatlar funksiyasidan foydalanish uchun u yoniqligida ikkala tovush tugmasini 3 soniya bosib turing."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Maxsus imkoniyatlar yoqilsinmi?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Maxsus imkoniyatlar uchun tezkor tugma yoqilsinmi?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Maxsus imkoniyatlarni yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nJoriy funksiyalar:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nTanlangan funksiyalarni Sozlamalar ichidagi Maxsus imkoniyatlar ustiga bosib oʻzgartirishingiz mumkin."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"<xliff:g id="SERVICE">%1$s</xliff:g> yoqilsinmi?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"<xliff:g id="SERVICE">%1$s</xliff:g> tezkor tugmasi yoqilsinmi?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"<xliff:g id="SERVICE">%1$s</xliff:g> funksiyasini yoqish uchun ikkala tovush tugmalarini bir necha soniya bosib turing. Qurilmangiz ishlashida oʻzgarish yuz berishi mumkin.\n\nBu tezkor tugmalarni boshqa funksiyaga Sozlamalar ichidagi Maxsus imkoniyatlar orqali tayinlash mumkin."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Yoqilsin"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Yoqilmasin"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d soat</item>
<item quantity="one">1 soat</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> gacha (keyingi signal)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Rejimdan chiqilgunicha"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1899f8fd2077..fd7205d24730 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"thay đổi cài đặt âm thanh của bạn"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Cho phép ứng dụng sửa đổi cài đặt âm thanh chung chẳng hạn như âm lượng và loa nào được sử dụng cho thiết bị ra."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"ghi âm"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Ứng dụng này có thể ghi âm bằng micrô bất kỳ lúc nào."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Ứng dụng này có thể ghi âm bằng micrô khi bạn đang dùng ứng dụng."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"ghi âm trong nền"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Ứng dụng này có thể ghi âm bằng micrô bất kỳ lúc nào."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"gửi lệnh đến SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Cho phép ứng dụng gửi lệnh đến SIM. Việc này rất nguy hiểm."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"nhận dạng hoạt động thể chất"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Ứng dụng này có thể nhận dạng hoạt động thể chất của bạn."</string>
<string name="permlab_camera" msgid="6320282492904119413">"chụp ảnh và quay video"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Ứng dụng này có thể chụp ảnh và quay video bằng máy ảnh bất cứ lúc nào."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Ứng dụng này có thể chụp ảnh và quay video bằng máy ảnh khi bạn đang dùng ứng dụng."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"chụp ảnh và quay video trong nền"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Ứng dụng này có thể chụp ảnh và quay video bằng máy ảnh bất cứ lúc nào."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Cho phép một ứng dụng hoặc dịch vụ truy cập vào máy ảnh hệ thống để chụp ảnh và quay video"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Ứng dụng hệ thống có đặc quyền này có thể dùng máy ảnh hệ thống để chụp ảnh và quay video bất cứ lúc nào. Ngoài ra, ứng dụng này cũng cần có quyền android.permission.CAMERA"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Cho phép một ứng dụng hoặc dịch vụ nhận lệnh gọi lại khi các thiết bị máy ảnh đang được mở/đóng."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bạn tăng âm lượng lên quá mức khuyên dùng?\n\nViệc nghe ở mức âm lượng cao trong thời gian dài có thể gây tổn thương thính giác của bạn."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sử dụng phím tắt Hỗ trợ tiếp cận?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Khi phím tắt này đang bật, thao tác nhấn cả hai nút âm lượng trong 3 giây sẽ mở tính năng hỗ trợ tiếp cận."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Bật các tính năng hỗ trợ tiếp cận?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Bật phím tắt cho các tính năng hỗ trợ tiếp cận?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật các tính năng hỗ trợ tiếp cận. Việc bật các tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nCác tính năng hiện tại:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nBạn có thể thay đổi những tính năng đã chọn trong phần Cài đặt &gt; Hỗ trợ tiếp cận."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Bật <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Bật phím tắt cho <xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Thao tác nhấn và giữ cả hai phím âm lượng trong vài giây sẽ bật <xliff:g id="SERVICE">%1$s</xliff:g>, một tính năng hỗ trợ tiếp cận. Việc bật tính năng này có thể thay đổi cách thiết bị của bạn hoạt động.\n\nBạn có thể chuyển phím tắt này thành một tính năng khác trong phần Cài đặt &gt; Hỗ trợ tiếp cận."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Bật"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Không bật"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">Trong %d giờ</item>
<item quantity="one">Trong 1 giờ</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Cho tới <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Cho đến <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Cho tới <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (cảnh báo tiếp theo)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Cho đến khi bạn tắt"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 0a241dab0954..16775d5c0324 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改您的音频设置"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允许该应用修改全局音频设置,例如音量和用于输出的扬声器。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"录音"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"此应用可随时使用麦克风进行录音。"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"当您使用此应用时,它可以使用麦克风录音。"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在后台录音"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此应用可以随时使用麦克风录音。"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"向 SIM 卡发送命令"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允许应用向SIM卡发送命令(此权限具有很高的危险性)。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"识别身体活动"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"此应用可以识别您的身体活动。"</string>
<string name="permlab_camera" msgid="6320282492904119413">"拍摄照片和视频"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"此应用可随时使用相机拍摄照片和录制视频。"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"当您使用此应用时,它可以使用相机拍摄照片和录制视频。"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"在后台拍摄照片和视频"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"此应用可以随时使用相机拍摄照片和录制视频。"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"要拍照或录制视频,请允许应用或服务访问系统相机"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"这个具有特权的系统应用随时可以使用系统相机拍照及录制视频。另外,应用还需要获取 android.permission.CAMERA 权限"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允许应用或服务接收与打开或关闭摄像头设备有关的回调。"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要将音量调高到建议的音量以上吗?\n\n长时间保持高音量可能会损伤听力。"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用无障碍快捷方式吗?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"启用这项快捷方式后,同时按下两个音量按钮 3 秒钟即可启动无障碍功能。"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要开启无障碍功能吗?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要开启无障碍功能快捷方式吗?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同时按住两个音量键几秒钟,即可开启无障碍功能。这样做可能会改变您设备的工作方式。\n\n当前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可以在“设置”&gt;“无障碍”中更改所选功能。"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要开启<xliff:g id="SERVICE">%1$s</xliff:g>吗?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要开启<xliff:g id="SERVICE">%1$s</xliff:g>快捷方式吗?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同时按住两个音量键几秒钟,即可开启<xliff:g id="SERVICE">%1$s</xliff:g>无障碍功能。这样做可能会改变您设备的工作方式。\n\n您可以在“设置”&gt;“无障碍”中将此快捷方式更改为开启另一项功能。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"开启"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"不开启"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d 小时</item>
<item quantity="one">1 小时</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"结束时间:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"直到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>(闹钟下次响铃时)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"直到您将其关闭"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 6e584d6182d9..bd13574b5135 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"更改音效設定"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音頻設定,例如音量和用於輸出的喇叭。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音效"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"此應用程式可以隨時使用麥克風錄音。"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"此應用程式在使用期間可使用麥克風錄音。"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"此應用程式可隨時使用麥克風錄音。"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"發送指令至 SIM 卡"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這項操作具有高危險性。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"識別體能活動"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"此應用程式可識別您的體能活動。"</string>
<string name="permlab_camera" msgid="6320282492904119413">"拍照和拍攝影片"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"此應用程式可以隨時使用相機拍照和攝錄。"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"此應用程式在使用期間可使用相機拍照及錄影。"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"在背景拍照及錄影"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"此應用程式可隨時使用相機拍照及錄影。"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"允許應用程式或服務存取系統相機來拍照和攝錄"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"這個獲特別權限的系統應用程式可以在任何時候使用系統相機來拍照和攝錄。此外,應用程式亦需要 android.permission.CAMERA 權限"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允許應用程式或服務接收相機裝置開啟或關閉的相關回電。"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙功能快速鍵嗎?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用快速鍵後,同時按住音量按鈕 3 秒便可啟用無障礙功能。"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要開啟無障礙功能嗎?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能捷徑嗎?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按下兩個音量鍵幾秒,以開啟無障礙功能。這可能會變更裝置的運作。\n\n目前功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n您可在「設定」&gt;「無障礙功能」中變更所選功能。"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要啟用 <xliff:g id="SERVICE">%1$s</xliff:g> 嗎?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 捷徑嗎?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按下兩個音量鍵幾秒,以開啟 <xliff:g id="SERVICE">%1$s</xliff:g> 無障礙功能。這可能會變更裝置的運作。\n\n您可在「設定」&gt;「無障礙功能」中變更此快速鍵。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"不要開啟"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d 小時</item>
<item quantity="one">1 小時</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"完成時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"直至<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (下一次響鬧)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"直至您關閉為止"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index ab1a42375871..0637742f1344 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"變更音訊設定"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"允許應用程式修改全域音訊設定,例如音量和用來輸出的喇叭。"</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"錄製音訊"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"這個應用程式隨時可使用麥克風錄音。"</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"這個應用程式在使用期間可以使用麥克風錄音。"</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"在背景錄音"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"這個應用程式隨時可以使用麥克風錄音。"</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"傳送指令到 SIM 卡"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"允許應用程式傳送指令到 SIM 卡。這麼做非常危險。"</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"辨識體能活動"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"這個應用程式可以辨識你從事的體能活動。"</string>
<string name="permlab_camera" msgid="6320282492904119413">"拍攝相片和影片"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"這個應用程式隨時可使用相機拍照及錄影。"</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"這個應用程式在使用期間可以使用相機拍照及錄影。"</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"在背景拍照及錄影"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"這個應用程式隨時可以使用相機拍照及錄影。"</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"如要拍照或錄影,請允許應用程式或服務存取系統攝影機"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"這個具有特殊權限的系統應用程式隨時可以使用系統攝影機拍照及錄影。此外,你也必須將 android.permission.CAMERA 權限授予這個應用程式"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"允許應用程式或服務接收相機裝置開啟或關閉的相關回呼。"</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量,比建議的音量更大聲嗎?\n\n長時間聆聽高分貝音量可能會使你的聽力受損。"</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙捷徑嗎?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"啟用捷徑功能,只要同時按下兩個音量按鈕 3 秒,就能啟動無障礙功能。"</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"要開啟無障礙功能嗎?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"要開啟無障礙功能快速鍵嗎?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"同時按住音量調高鍵和調低鍵數秒,即可開啟無障礙功能。這麼做可能會改變裝置的運作方式。\n\n目前的功能:\n<xliff:g id="SERVICE">%1$s</xliff:g>\n你可以在 [設定] &gt; [無障礙設定] 中變更選取的功能。"</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"要開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」嗎?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"要開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」快速鍵嗎?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"同時按住音量調高鍵和調低鍵數秒,即可開啟「<xliff:g id="SERVICE">%1$s</xliff:g>」無障礙功能。這麼做可能會改變裝置的運作方式。\n\n你可以在 [設定] &gt; [無障礙設定] 中變更這個快速鍵觸發的功能。"</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"開啟"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"不要開啟"</string>
@@ -1829,6 +1833,7 @@
<item quantity="other">%d 小時</item>
<item quantity="one">1 小時</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"結束時間:<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"到<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> 為止 (下一個鬧鐘)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"直到你關閉為止"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 582d4350882c..ea97a6e3fd4f 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -432,13 +432,17 @@
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"shintsha izilungiselelo zakho zomsindo"</string>
<string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Ivumela uhlelo lokusebenza ukushintsha izilungiselelo zomsindo we-global njengevolomu nokuthi isiphi isipika esisetshenziselwa okukhiphayo."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"qopha umsindo"</string>
- <string name="permdesc_recordAudio" msgid="3976213377904701093">"Lolu hlelo lokusebenza lungafunda umsindo lisebenzisa imakrofoni noma kunini."</string>
+ <string name="permdesc_recordAudio" msgid="5857246765327514062">"Lolu hlelo lokusebenza lungarekhoda umsindo lisebenzisa imakrofoni kuyilapho uhlelo lokusebenza lusetshenziswa."</string>
+ <string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"rekhoda umsindo ngemuva"</string>
+ <string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"Lolu hlelo lokusebenza lungafunda umsindo lisebenzisa imakrofoni noma kunini."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"thumela imilayezo ku-SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Ivumela uhlelo lokusebenza ukuthumela imiyalo ku-SIM. Lokhu kuyingozi kakhulu."</string>
<string name="permlab_activityRecognition" msgid="1782303296053990884">"bona umsebenzi"</string>
<string name="permdesc_activityRecognition" msgid="8667484762991357519">"Lolu hlelo lokusebenza lingabona umsebenzi wakho."</string>
<string name="permlab_camera" msgid="6320282492904119413">"thatha izithombe namavidiyo"</string>
- <string name="permdesc_camera" msgid="1354600178048761499">"Lolu hlelo lokusebenza lungathatha izithombe futhi lirekhode amavidiyo lusebenzisa ikhamera noma kunini."</string>
+ <string name="permdesc_camera" msgid="5240801376168647151">"Lolu hlelo lokusebenza lungathatha izithombe futhi lirekhode amavidiyo lisebenzisa ikhamera kuyilapho uhlelo lokusebenza lusetshenziswa."</string>
+ <string name="permlab_backgroundCamera" msgid="7549917926079731681">"thatha izithombe namavidiyo ngemuva"</string>
+ <string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Lolu hlelo lokusebenza lungathatha izithombe futhi lirekhode amavidiyo lusebenzisa ikhamera noma kunini."</string>
<string name="permlab_systemCamera" msgid="3642917457796210580">"Vumela uhlelo lokusebenza noma isevisi ukufinyelela kumakhamera wesistimu ukuze uthathe izithombe namavidiyo"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Lolu hlelo lokusebenza oluhle noma lwesistimu lingathatha izithombe futhi lirekhode amavidiyo lisebenzisa ikhamera yesistimu noma kunini. Idinga imvume ye-android.permission.CAMERA ukuthi iphathwe nawuhlelo lokusebenza"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Vumela uhlelo lokusebenza noma isevisi ukwamukela ukuphinda ufonelwe mayelana namadivayisi wekhamera avuliwe noma avaliwe."</string>
@@ -1622,10 +1626,10 @@
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Khuphukisa ivolumu ngaphezu kweleveli enconyiwe?\n\nUkulalela ngevolumu ephezulu izikhathi ezide kungahle kulimaze ukuzwa kwakho."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sebenzisa isinqamuleli sokufinyelela?"</string>
<string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"Uma isinqamuleli sivuliwe, ukucindezela zombili izinkinobho zevolumu amasekhondi angu-3 kuzoqalisa isici sokufinyelela."</string>
- <string name="accessibility_shortcut_multiple_service_warning_title" msgid="8417489297036013065">"Uvula izici zokufinyelela?"</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Vula isinqamuleli sezici zokufinyeleleka?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula izici zokufinyelela. Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nIzici zamanje:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nUngashintsha izici ezikhethiwe Kuzilungiselelo &gt; Ukufinyeleleka."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="6935581470716541531">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
- <string name="accessibility_shortcut_single_service_warning_title" msgid="2819109500943271385">"Vula i-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
+ <string name="accessibility_shortcut_single_service_warning_title" msgid="1909518473488345266">"Vula isinqamuleli se-<xliff:g id="SERVICE">%1$s</xliff:g>?"</string>
<string name="accessibility_shortcut_single_service_warning" msgid="6363127705112844257">"Ukubambela phansi bobabili okhiye bevolumu amasekhondi ambalwa kuvula i-<xliff:g id="SERVICE">%1$s</xliff:g>, eyisici sokufinyelela Lokhu kungashintsha indlela idivayisi yakho esebenza ngayo.\n\nUngashintshela lesi sinqamuleli kwesinye isici Kuzilungiselelo &gt; Ukufinyeleleka."</string>
<string name="accessibility_shortcut_on" msgid="5463618449556111344">"Vula"</string>
<string name="accessibility_shortcut_off" msgid="3651336255403648739">"Ungavuli"</string>
@@ -1829,6 +1833,7 @@
<item quantity="one">Ngamahora angu-%d</item>
<item quantity="other">Ngamahora angu-%d</item>
</plurals>
+ <string name="zen_mode_until_next_day" msgid="1403042784161725038">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Kuze kube ngu-<xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (i-alamu elandelayo)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Uze uvale isikrini"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 20ef017f59f8..64de32c9ad09 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -199,6 +199,9 @@
<!-- The underline color and thickness for auto correction suggestion -->
<attr name="textAppearanceAutoCorrectionSuggestion" format="reference" />
+ <!-- The underline color and thickness for grammar error suggestion -->
+ <attr name="textAppearanceGrammarErrorSuggestion" format="reference" />
+
<!-- The underline color -->
<attr name="textUnderlineColor" format="reference|color" />
<!-- The underline thickness -->
@@ -9180,21 +9183,32 @@
<attr name="fontVariationSettings" format="string" />
</declare-styleable>
- <!-- Attributes that are read when parsing a &lt;fontfamily&gt; tag. -->
+ <!-- Attributes that are read when parsing a &lt;fontfamily&gt; tag.
+ {@deprecated Use Jetpack Core library instead.}
+ -->
<declare-styleable name="FontFamily">
- <!-- The authority of the Font Provider to be used for the request. -->
+ <!-- The authority of the Font Provider to be used for the request.
+ {@deprecated Use app:fontProviderAuthority with Jetpack Core library instead for
+ consistent behavior across all devices.}
+ -->
<attr name="fontProviderAuthority" format="string" />
<!-- The package for the Font Provider to be used for the request. This is used to verify
- the identity of the provider. -->
+ the identity of the provider.
+ {@deprecated Use app:fontProviderPackage with Jetpack Core library instead.}
+ -->
<attr name="fontProviderPackage" format="string" />
<!-- The query to be sent over to the provider. Refer to your font provider's documentation
- on the format of this string. -->
+ on the format of this string.
+ {@deprecated Use app:fontProviderQuery with Jetpack Core library instead.}
+ -->
<attr name="fontProviderQuery" format="string" />
<!-- The sets of hashes for the certificates the provider should be signed with. This is
used to verify the identity of the provider, and is only required if the provider is not
part of the system image. This value may point to one list or a list of lists, where each
individual list represents one collection of signature hashes. Refer to your font provider's
- documentation for these values. -->
+ documentation for these values.
+ {@deprecated Use app:fontProviderCerts with Jetpack Core library instead.}
+ -->
<attr name="fontProviderCerts" format="reference" />
</declare-styleable>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c0e449c3f865..25c64a9f8781 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -344,6 +344,11 @@
the app is uninstalled.
-->
<flag name="immutablyRestricted" value="0x10" />
+ <!--
+ Modifier for permission restriction. This permission cannot
+ be exempted by the installer.
+ -->
+ <flag name="installerExemptIgnored" value="0x20" />
</attr>
<!-- Specified the name of a group that this permission is associated
@@ -996,6 +1001,8 @@
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
+ <!-- The user has enabled or disabled displaying all text in bold -->
+ <flag name="forceBoldText" value="0x10000000" />
</attr>
<!-- Indicate that the activity can be launched as the embedded child of another
@@ -2009,6 +2016,22 @@
<attr name="requiredNotFeature" format="string" />
</declare-styleable>
+ <!-- <code>required-feature</code> and <code>required-not-feature</code> elements inside
+ <code>uses-permission<code/> can be used to request the permission based on the fact
+ whether the system supports or does not support certain features.
+ If multiple <code>required-feature</code> and/or <code>required-not-feature</code> elements
+ are present, the permission will be “requested” only if the system supports all of the
+ listed "required-features" and does not support any of the "required-not-features".
+ -->
+ <declare-styleable name="AndroidManifestRequiredFeature">
+ <!-- The name of the feature. -->
+ <attr name="name" />
+ </declare-styleable>
+ <declare-styleable name="AndroidManifestRequiredNotFeature">
+ <!-- The name of the feature. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>uses-configuration</code> tag specifies
a specific hardware configuration value used by the application.
For example an application might specify that it requires
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3e1d02282aa4..a255097cf0c6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -271,11 +271,6 @@
when there's no network connection. If the scan doesn't timeout, use zero -->
<integer name="config_radioScanningTimeout">0</integer>
- <!-- When true, Android uses the PAC implementation included in WebView to handle
- networks with PAC scripts.
- When false, Android's own implementation of libpac is used.-->
- <bool name ="config_useWebViewPacProcessor">true</bool>
-
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
@@ -1583,6 +1578,26 @@
config_timeZoneRulesUpdateTrackingEnabled are true.] -->
<integer name="config_timeZoneRulesCheckRetryCount">5</integer>
+ <!-- Whether to enable primary location time zone provider overlay which allows the primary
+ location time zone provider to be replaced by an app at run-time. When disabled, only the
+ config_primaryLocationTimeZoneProviderPackageName package will be searched for the primary
+ location time zone provider, otherwise any system package is eligible. Anyone who wants to
+ disable the overlay mechanism can set it to false. -->
+ <bool name="config_enablePrimaryLocationTimeZoneOverlay" translatable="false">false</bool>
+ <!-- Package name providing the primary location time zone provider. Used only when
+ config_enablePrimaryLocationTimeZoneOverlay is false. -->
+ <string name="config_primaryLocationTimeZoneProviderPackageName" translatable="false">@null</string>
+
+ <!-- Whether to enable secondary location time zone provider overlay which allows the secondary
+ location time zone provider to be replaced by an app at run-time. When disabled, only the
+ config_secondaryLocationTimeZoneProviderPackageName package will be searched for the
+ secondary location time zone provider, otherwise any system package is eligible. Anyone who
+ wants to disable the overlay mechanism can set it to false. -->
+ <bool name="config_enableSecondaryLocationTimeZoneOverlay" translatable="false">false</bool>
+ <!-- Package name providing the secondary location time zone provider. Used only when
+ config_enableSecondaryLocationTimeZoneOverlay is false. -->
+ <string name="config_secondaryLocationTimeZoneProviderPackageName" translatable="false">@null</string>
+
<!-- Whether to enable network location overlay which allows network location provider to be
replaced by an app at run-time. When disabled, only the
config_networkLocationProviderPackageName package will be searched for network location
@@ -1829,6 +1844,8 @@
<string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false"></string>
+ <!-- The name of the package that will hold the system video call role. -->
+ <string name="config_systemVideoCall" translatable="false"></string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -3442,7 +3459,7 @@
<string name="config_emergency_call_number" translatable="false">112</string>
<!-- Package name that provides Emergency Dialer -->
- <string name="config_emergency_dialer_package">com.android.phone</string>
+ <string name="config_emergency_dialer_package" translatable="false">com.android.phone</string>
<!-- Do not translate. Mcc codes whose existence trigger the presence of emergency
affordances-->
@@ -3649,6 +3666,8 @@
-->
<string name="config_defaultContentSuggestionsService" translatable="false"></string>
+ <string name="config_defaultMusicRecognitionService" translatable="false"></string>
+
<!-- The package name for the default retail demo app.
This package must be trusted, as it has the permissions to query the usage stats on the
device.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 1e2d554a088b..fe17eca35545 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3071,6 +3071,8 @@
<public-group type="string" first-id="0x01040028">
<!-- @hide @SystemApi @TestApi -->
<public name="config_systemAutomotiveCluster" />
+ <!-- @hide @SystemApi @TestApi -->
+ <public name="config_systemVideoCall" />
</public-group>
<public-group type="id" first-id="0x01020055">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4e6affb60d03..ad0384d8512e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1168,7 +1168,12 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_recordAudio">record audio</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_recordAudio">This app can record audio using the microphone at any time.</string>
+ <string name="permdesc_recordAudio">This app can record audio using the microphone while the app is in use.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_recordBackgroundAudio">record audio in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_recordBackgroundAudio">This app can record audio using the microphone at any time.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_sim_communication">send commands to the SIM</string>
@@ -1183,7 +1188,12 @@
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_camera">take pictures and videos</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_camera">This app can take pictures and record videos using the camera at any time.</string>
+ <string name="permdesc_camera">This app can take pictures and record videos using the camera while the app is in use.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permlab_backgroundCamera">take pictures and videos in the background</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_backgroundCamera">This app can take pictures and record videos using the camera at any time.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
<string name="permlab_systemCamera">Allow an application or service access to system cameras to take pictures and videos</string>
@@ -4376,7 +4386,7 @@
</string>
<!-- Dialog title for dialog shown when the multiple accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_multiple_service_warning_title">Turn on accessibility features?</string>
+ <string name="accessibility_shortcut_multiple_service_warning_title">Turn on shortcut for accessibility features?</string>
<!-- Message shown in dialog when user is in the process of enabling the multiple accessibility service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_multiple_service_warning">Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="service" example="TalkBack">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility.</string>
@@ -4385,7 +4395,7 @@
<string name="accessibility_shortcut_multiple_service_list">\t• <xliff:g id="service" example="TalkBack">%1$s</xliff:g>\n</string>
<!-- Dialog title for dialog shown when this accessibility shortcut is activated, and we want to confirm that the user understands what's going to happen. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>?</string>
+ <string name="accessibility_shortcut_single_service_warning_title">Turn on <xliff:g id="service" example="TalkBack">%1$s</xliff:g> shortcut?</string>
<!-- Message shown in dialog when user is in the process of enabling this accessibility service via the volume buttons shortcut for the first time. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_single_service_warning">Holding down both volume keys for a few seconds turns on <xliff:g id="service" example="TalkBack">%1$s</xliff:g>, an accessibility feature. This may change how your device works.\n\nYou can change this shortcut to another feature in Settings > Accessibility.</string>
@@ -4486,6 +4496,9 @@
shown in the warning dialog about the accessibility shortcut. -->
<string name="color_correction_feature_name">Color Correction</string>
+ <!-- Title of Reduce Bright Colors feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] -->
+ <string name="reduce_bright_colors_feature_name">Reduce Bright Colors</string>
+
<!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index f920083f5cb3..24afe07b57cf 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -306,6 +306,10 @@ please see styles_device_defaults.xml.
<item name="textUnderlineColor">@color/holo_blue_light</item>
</style>
+ <style name="TextAppearance.GrammarErrorSuggestion" parent="TextAppearance.Suggestion">
+ <item name="textUnderlineColor">@color/holo_blue_light</item>
+ </style>
+
<!-- Widget Styles -->
<style name="Widget">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a54b227be159..e5d465873ae8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -263,6 +263,7 @@
<java-symbol type="attr" name="searchDialogTheme" />
<java-symbol type="attr" name="textAppearanceAutoCorrectionSuggestion" />
<java-symbol type="attr" name="textAppearanceEasyCorrectSuggestion" />
+ <java-symbol type="attr" name="textAppearanceGrammarErrorSuggestion" />
<java-symbol type="attr" name="textAppearanceMisspelledSuggestion" />
<java-symbol type="attr" name="textColorSearchUrl" />
<java-symbol type="attr" name="timePickerStyle" />
@@ -287,7 +288,6 @@
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
<java-symbol type="bool" name="config_disableTransitionAnimation" />
<java-symbol type="bool" name="config_enableAutoPowerModes" />
- <java-symbol type="bool" name="config_useWebViewPacProcessor" />
<java-symbol type="integer" name="config_autoPowerModeThresholdAngle" />
<java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" />
<java-symbol type="bool" name="config_autoPowerModePreferWristTilt" />
@@ -2176,6 +2176,10 @@
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="config_deviceConfiguratorPackageName" />
+ <java-symbol type="bool" name="config_enablePrimaryLocationTimeZoneOverlay" />
+ <java-symbol type="string" name="config_primaryLocationTimeZoneProviderPackageName" />
+ <java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneOverlay" />
+ <java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
<java-symbol type="string" name="config_optionalPackageVerifierName" />
<java-symbol type="layout" name="resolver_list" />
@@ -3263,6 +3267,7 @@
<java-symbol type="string" name="accessibility_shortcut_disabling_service" />
<java-symbol type="string" name="color_inversion_feature_name" />
<java-symbol type="string" name="color_correction_feature_name" />
+ <java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="config_defaultAccessibilityService" />
<java-symbol type="string" name="accessibility_shortcut_spoken_feedback" />
@@ -3468,6 +3473,7 @@
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
<java-symbol type="string" name="config_defaultContentSuggestionsService" />
+ <java-symbol type="string" name="config_defaultMusicRecognitionService" />
<java-symbol type="string" name="config_defaultAttentionService" />
<java-symbol type="string" name="config_defaultSystemCaptionsService" />
<java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
@@ -3562,6 +3568,7 @@
<java-symbol type="id" name="bubble_button" />
<java-symbol type="dimen" name="bubble_visible_padding_end" />
<java-symbol type="dimen" name="bubble_gone_padding_end" />
+ <java-symbol type="dimen" name="text_size_body_2_material" />
<java-symbol type="dimen" name="messaging_avatar_size" />
<java-symbol type="dimen" name="messaging_group_sending_progress_size" />
<java-symbol type="dimen" name="messaging_image_rounding" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 47a0e7d071f8..049ba23a1a97 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -93,6 +93,7 @@ please see themes_device_defaults.xml.
<item name="textAppearanceEasyCorrectSuggestion">@style/TextAppearance.EasyCorrectSuggestion</item>
<item name="textAppearanceMisspelledSuggestion">@style/TextAppearance.MisspelledSuggestion</item>
<item name="textAppearanceAutoCorrectionSuggestion">@style/TextAppearance.AutoCorrectionSuggestion</item>
+ <item name="textAppearanceGrammarErrorSuggestion">@style/TextAppearance.GrammarErrorSuggestion</item>
<item name="textAppearanceButton">@style/TextAppearance.Widget.Button</item>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 6ae6faa5446e..38dce150d85c 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -138,6 +138,9 @@
<!-- accessibility test permissions -->
<uses-permission android:name="android.permission.RETRIEVE_WINDOW_CONTENT" />
+ <!-- vibrator test permissions -->
+ <uses-permission android:name="android.permission.VIBRATE" />
+
<!-- vr test permissions -->
<uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
@@ -161,6 +164,14 @@
<category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
</intent-filter>
</activity>
+ <activity android:name="android.view.ViewInputConnectionTestActivity"
+ android:label="View Input Connection Test"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
<activity android:name="StubTestBrowserActivity"
android:label="Stubbed Test Browser"
android:exported="true">
@@ -228,15 +239,6 @@
</intent-filter>
</activity>
- <activity android:name="android.widget.focus.ListOfButtons"
- android:label="ListOfButtons"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
- </intent-filter>
- </activity>
-
<activity android:name="android.widget.focus.LinearLayoutGrid"
android:label="LinearLayoutGrid"
android:exported="true">
@@ -1608,6 +1610,7 @@
</activity>
<activity android:name="android.app.activity.ActivityThreadTest$TestActivity"
+ android:configChanges="screenLayout|screenSize|orientation|smallestScreenSize"
android:supportsPictureInPicture="true"
android:exported="true">
</activity>
diff --git a/packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml b/core/tests/coretests/res/layout/activity_view_ic_test.xml
index 03348756231b..fa26869c5997 100644
--- a/packages/SystemUI/res/drawable/tv_rect_dark_right_rounded.xml
+++ b/core/tests/coretests/res/layout/activity_view_ic_test.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ 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.
@@ -15,12 +15,9 @@
~ limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
-
- <corners
- android:bottomRightRadius="8dp"
- android:topRightRadius="8dp" />
- <solid android:color="@color/tv_audio_recording_indicator_background" />
-
-</shape> \ No newline at end of file
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
index c9510918e555..3df0a6871639 100644
--- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java
+++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java
@@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.NotificationHistory.HistoricalNotification;
import android.graphics.drawable.Icon;
import android.os.Parcel;
-import android.util.Slog;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -31,6 +30,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class NotificationHistoryTest {
@@ -117,8 +117,8 @@ public class NotificationHistoryTest {
history.addNotificationToWrite(n);
assertThat(history.getNotificationsToWrite().size()).isEqualTo(2);
- assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n2);
- assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
+ assertThat(history.getNotificationsToWrite().get(0)).isSameInstanceAs(n2);
+ assertThat(history.getNotificationsToWrite().get(1)).isSameInstanceAs(n);
assertThat(history.getHistoryCount()).isEqualTo(2);
}
@@ -141,11 +141,11 @@ public class NotificationHistoryTest {
history.addNotificationsToWrite(secondHistory);
assertThat(history.getNotificationsToWrite().size()).isEqualTo(5);
- assertThat(history.getNotificationsToWrite().get(0)).isSameAs(n3);
- assertThat(history.getNotificationsToWrite().get(1)).isSameAs(n);
- assertThat(history.getNotificationsToWrite().get(2)).isSameAs(n4);
- assertThat(history.getNotificationsToWrite().get(3)).isSameAs(n2);
- assertThat(history.getNotificationsToWrite().get(4)).isSameAs(n5);
+ assertThat(history.getNotificationsToWrite().get(0)).isSameInstanceAs(n3);
+ assertThat(history.getNotificationsToWrite().get(1)).isSameInstanceAs(n);
+ assertThat(history.getNotificationsToWrite().get(2)).isSameInstanceAs(n4);
+ assertThat(history.getNotificationsToWrite().get(3)).isSameInstanceAs(n2);
+ assertThat(history.getNotificationsToWrite().get(4)).isSameInstanceAs(n5);
assertThat(history.getHistoryCount()).isEqualTo(5);
assertThat(history.getPooledStringsToWrite()).asList().contains(n2.getChannelName());
@@ -297,7 +297,7 @@ public class NotificationHistoryTest {
for (int i = 1; i <= 10; i++) {
HistoricalNotification n = getHistoricalNotification("pkg", i);
- if (i != 2) {
+ if (i != 2 && i != 4) {
postRemoveExpectedStrings.add(n.getPackage());
postRemoveExpectedStrings.add(n.getChannelName());
postRemoveExpectedStrings.add(n.getChannelId());
@@ -318,10 +318,10 @@ public class NotificationHistoryTest {
// 1 package name and 20 unique channel names and ids and 5 conversation ids
assertThat(history.getPooledStringsToWrite().length).isEqualTo(26);
- history.removeConversationFromWrite("pkg", "convo2");
+ history.removeConversationsFromWrite("pkg", Set.of("convo2", "convo4"));
- // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids
- assertThat(history.getPooledStringsToWrite().length).isEqualTo(23);
+ // 1 package names and 8 * 2 unique channel names and ids and 3 conversation ids
+ assertThat(history.getPooledStringsToWrite().length).isEqualTo(20);
assertThat(history.getNotificationsToWrite())
.containsExactlyElementsIn(postRemoveExpectedEntries);
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 0a751dd7c66b..400b05c09fa5 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,14 +25,15 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.ActivityThread;
+import android.app.ActivityThread.ActivityClientRecord;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.ResourcesManager;
@@ -51,13 +52,15 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.View;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -69,6 +72,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test for verifying {@link android.app.ActivityThread} class.
@@ -77,7 +81,10 @@ import java.util.concurrent.CountDownLatch;
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
+@Presubmit
+@FlakyTest(detail = "Promote once confirmed non-flaky")
public class ActivityThreadTest {
+ private static final int TIMEOUT_SEC = 10;
// The first sequence number to try with. Use a large number to avoid conflicts with the first a
// few sequence numbers the framework used to launch the test activity.
@@ -114,11 +121,12 @@ public class ActivityThreadTest {
final ActivityThread activityThread = activity.getActivityThread();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
activityThread.executeTransaction(newResumeTransaction(activity));
- assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
- true /* finalStateRequest */, "test"));
+ final ActivityClientRecord r = getActivityClientRecord(activity);
+ assertFalse(activityThread.performResumeActivity(r, true /* finalStateRequest */,
+ "test"));
- assertNull(activityThread.performResumeActivity(activity.getActivityToken(),
- false /* finalStateRequest */, "test"));
+ assertFalse(activityThread.performResumeActivity(r, false /* finalStateRequest */,
+ "test"));
});
}
@@ -244,20 +252,18 @@ public class ActivityThreadTest {
newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
newerConfig.seq = seq + 2;
- activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
- newerConfig);
+ final ActivityClientRecord r = getActivityClientRecord(activity);
+ activityThread.updatePendingActivityConfiguration(r, newerConfig);
final Configuration olderConfig = new Configuration();
olderConfig.orientation = orientation;
olderConfig.seq = seq + 1;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- olderConfig, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
assertEquals(numOfConfig, activity.mNumOfConfigChanges);
assertEquals(olderConfig.orientation, activity.mConfig.orientation);
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- newerConfig, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
assertEquals(newerConfig.orientation, activity.mConfig.orientation);
});
@@ -274,7 +280,7 @@ public class ActivityThreadTest {
config.seq = BASE_SEQ;
config.orientation = ORIENTATION_PORTRAIT;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
config, INVALID_DISPLAY);
});
@@ -310,7 +316,7 @@ public class ActivityThreadTest {
transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
appThread.scheduleTransaction(transaction);
- activity.mTestLatch.await();
+ activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
activity.mConfigLatch.countDown();
activity.mConfigLatch = null;
@@ -335,7 +341,7 @@ public class ActivityThreadTest {
config.seq = BASE_SEQ;
config.orientation = ORIENTATION_PORTRAIT;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
config, INVALID_DISPLAY);
});
@@ -353,7 +359,7 @@ public class ActivityThreadTest {
// Wait until the main thread is performing the configuration change for the configuration
// with sequence number BASE_SEQ + 1 before proceeding. This is to mimic the situation where
// the activity takes very long time to process configuration changes.
- activity.mTestLatch.await();
+ activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
config = new Configuration();
config.seq = BASE_SEQ + 2;
@@ -472,10 +478,10 @@ public class ActivityThreadTest {
newActivityConfig.orientation = newActivityConfig.orientation == ORIENTATION_PORTRAIT
? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
- activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
- newActivityConfig);
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- newActivityConfig, INVALID_DISPLAY);
+ final ActivityClientRecord r = getActivityClientRecord(activity);
+ activityThread.updatePendingActivityConfiguration(r, newActivityConfig);
+ activityThread.handleActivityConfigurationChanged(r, newActivityConfig,
+ INVALID_DISPLAY);
assertEquals("Virtual display orientation must not change when activity"
+ " configuration orientation changes.",
@@ -537,6 +543,53 @@ public class ActivityThreadTest {
}
@Test
+ public void testHandleProcessConfigurationChanged_DependOnProcessState() {
+ final ActivityThread activityThread = ActivityThread.currentActivityThread();
+ final Configuration origConfig = activityThread.getConfiguration();
+ final int newDpi = origConfig.densityDpi + 10;
+ final Configuration newConfig = new Configuration(origConfig);
+ newConfig.seq++;
+ newConfig.densityDpi = newDpi;
+
+ activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
+ false /* fromIPC */);
+
+ applyProcessConfiguration(activityThread, newConfig);
+ try {
+ // In the cached state, the configuration is only set as pending and not applied.
+ assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi);
+ assertTrue(activityThread.isCachedProcessState());
+ } finally {
+ // The foreground state is the default state of instrumentation.
+ activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
+ false /* fromIPC */);
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ try {
+ // The state becomes non-cached, the pending configuration should be applied.
+ assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi);
+ assertFalse(activityThread.isCachedProcessState());
+ } finally {
+ // Restore to the original configuration.
+ activityThread.getConfiguration().seq = origConfig.seq - 1;
+ applyProcessConfiguration(activityThread, origConfig);
+ }
+ }
+
+ private static void applyProcessConfiguration(ActivityThread thread, Configuration config) {
+ final ClientTransaction clientTransaction = newTransaction(thread,
+ null /* activityToken */);
+ clientTransaction.addCallback(ConfigurationChangeItem.obtain(config));
+ final IApplicationThread appThread = thread.getApplicationThread();
+ try {
+ appThread.scheduleTransaction(clientTransaction);
+ } catch (Exception ignored) {
+ }
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
public void testResumeAfterNewIntent() {
final Activity activity = mActivityTestRule.launchActivity(new Intent());
final ActivityThread activityThread = activity.getActivityThread();
@@ -544,14 +597,15 @@ public class ActivityThreadTest {
rIntents.add(new ReferrerIntent(new Intent(), "android.app.activity"));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false));
+ activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true));
});
- assertThat(activity.isResumed()).isFalse();
+ assertThat(activity.isResumed()).isTrue();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, true));
+ activityThread.executeTransaction(newStopTransaction(activity));
+ activityThread.executeTransaction(newNewIntentTransaction(activity, rIntents, false));
});
- assertThat(activity.isResumed()).isTrue();
+ assertThat(activity.isResumed()).isFalse();
}
@Test
@@ -560,9 +614,10 @@ public class ActivityThreadTest {
startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
final ActivityThread activityThread = activity.getActivityThread();
+ final ActivityClientRecord r = getActivityClientRecord(activity);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+ activityThread.handlePictureInPictureRequested(r);
});
assertTrue(activity.pipRequested());
@@ -575,9 +630,10 @@ public class ActivityThreadTest {
startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
final ActivityThread activityThread = activity.getActivityThread();
+ final ActivityClientRecord r = getActivityClientRecord(activity);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+ activityThread.handlePictureInPictureRequested(r);
});
assertTrue(activity.pipRequested());
@@ -588,9 +644,10 @@ public class ActivityThreadTest {
public void testHandlePictureInPictureRequested_notOverridden() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
final ActivityThread activityThread = activity.getActivityThread();
+ final ActivityClientRecord r = getActivityClientRecord(activity);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+ activityThread.handlePictureInPictureRequested(r);
});
assertTrue(activity.pipRequested());
@@ -599,8 +656,9 @@ public class ActivityThreadTest {
}
/**
- * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
- * to try to push activity configuration to the activity for the given sequence number.
+ * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
+ * Configuration, int)} to try to push activity configuration to the activity for the given
+ * sequence number.
* <p>
* It uses orientation to push the configuration and it tries a different orientation if the
* first attempt doesn't make through, to rule out the possibility that the previous
@@ -613,13 +671,13 @@ public class ActivityThreadTest {
*/
private int applyConfigurationChange(TestActivity activity, int seq) {
final ActivityThread activityThread = activity.getActivityThread();
+ final ActivityClientRecord r = getActivityClientRecord(activity);
final int numOfConfig = activity.mNumOfConfigChanges;
Configuration config = new Configuration();
config.orientation = ORIENTATION_PORTRAIT;
config.seq = seq;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
- INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
if (activity.mNumOfConfigChanges > numOfConfig) {
return config.seq;
@@ -628,12 +686,17 @@ public class ActivityThreadTest {
config = new Configuration();
config.orientation = ORIENTATION_LANDSCAPE;
config.seq = seq + 1;
- activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
- INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
return config.seq;
}
+ private static ActivityClientRecord getActivityClientRecord(Activity activity) {
+ final ActivityThread thread = activity.getActivityThread();
+ final IBinder token = activity.getActivityToken();
+ return thread.getActivityClient(token);
+ }
+
private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
null, 0, new MergedConfiguration(), false /* preserveWindow */);
@@ -730,7 +793,7 @@ public class ActivityThreadTest {
mTestLatch.countDown();
}
try {
- mConfigLatch.await();
+ mConfigLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
index 0d5025a2b83d..54a281f2a931 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
@@ -238,7 +238,8 @@ public class AppSearchDocumentTest {
.setSchema("schemaType1")
.setCreationTimestampMs(5L)
.setScore(1)
- .setTtlMs(1L);
+ .setTtlMs(1L)
+ .setNamespace("");
HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
propertyProtoMap.put("longKey1",
PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
deleted file mode 100644
index cdc6d250feeb..000000000000
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.app.appsearch;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import android.app.appsearch.AppSearchSchema.PropertyConfig;
-import android.app.appsearch.proto.IndexingConfig;
-import android.app.appsearch.proto.PropertyConfigProto;
-import android.app.appsearch.proto.SchemaTypeConfigProto;
-import android.app.appsearch.proto.TermMatchType;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-
-@SmallTest
-public class AppSearchSchemaTest {
- @Test
- public void testGetProto_Email() {
- AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).build();
-
- SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder()
- .setSchemaType("Email")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("subject")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- android.app.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("body")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- android.app.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(
- android.app.appsearch.proto.IndexingConfig
- .TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).build();
-
- assertThat(emailSchema.getProto()).isEqualTo(expectedEmailProto);
- }
-
- @Test
- public void testGetProto_MusicRecording() {
- AppSearchSchema musicRecordingSchema = new AppSearchSchema.Builder("MusicRecording")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("artist")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("pubDate")
- .setDataType(PropertyConfig.DATA_TYPE_INT64)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_NONE)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_NONE)
- .build()
- ).build();
-
- SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder()
- .setSchemaType("MusicRecording")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("artist")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
- .setIndexingConfig(
- android.app.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(
- android.app.appsearch.proto.IndexingConfig
- .TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- )
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("pubDate")
- .setDataType(PropertyConfigProto.DataType.Code.INT64)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- android.app.appsearch.proto.IndexingConfig.newBuilder()
- .setTokenizerType(
- android.app.appsearch.proto.IndexingConfig
- .TokenizerType.Code.NONE)
- .setTermMatchType(TermMatchType.Code.UNKNOWN)
- )
- ).build();
-
- assertThat(musicRecordingSchema.getProto()).isEqualTo(expectedMusicRecordingProto);
- }
-
- @Test
- public void testInvalidEnums() {
- PropertyConfig.Builder builder = new AppSearchSchema.PropertyConfig.Builder("test");
- assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
- assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
- }
-
- @Test
- public void testMissingFields() {
- PropertyConfig.Builder builder = new AppSearchSchema.PropertyConfig.Builder("test");
- Exception e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: dataType");
-
- builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
- e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: schemaType");
-
- builder.setSchemaType("TestType");
- e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Missing field: cardinality");
-
- builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
- builder.build();
- }
-
- @Test
- public void testDuplicateProperties() {
- AppSearchSchema.Builder builder = new AppSearchSchema.Builder("Email")
- .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- ).addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
- .setDataType(PropertyConfig.DATA_TYPE_STRING)
- .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
- .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
- .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
- .build()
- );
-
- Exception e = expectThrows(IllegalSchemaException.class, builder::build);
- assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
- }
-}
diff --git a/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java b/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java
index 1d71ec4520c3..ac0f44bb17e4 100644
--- a/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/SearchResultsTest.java
@@ -59,7 +59,7 @@ public class SearchResultsTest {
@Test
public void buildSearchSpecWithoutTermMatchType() {
- assertThrows(RuntimeException.class, () -> SearchSpec.newBuilder()
+ assertThrows(RuntimeException.class, () -> new SearchSpec.Builder()
.setSchemaTypes("testSchemaType")
.build());
}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
new file mode 100644
index 000000000000..c171270e23ea
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchSchemaTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.app.appsearch;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+import android.app.appsearch.exceptions.IllegalSchemaException;
+
+import org.junit.Test;
+
+public class AppSearchSchemaTest {
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ expectThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ expectThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = new PropertyConfig.Builder("test");
+ IllegalSchemaException e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+
+ @Test
+ public void testDuplicateProperties() {
+ AppSearchSchema.Builder builder = new AppSearchSchema.Builder("Email")
+ .addProperty(new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()
+ );
+ IllegalSchemaException e = expectThrows(IllegalSchemaException.class,
+ () -> builder.addProperty(new PropertyConfig.Builder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()));
+ assertThat(e).hasMessageThat().contains("Property defined more than once: subject");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 3c32c71cf961..0ae789af477c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -32,11 +32,12 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
@@ -225,6 +226,7 @@ public class TransactionExecutorTests {
when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
IBinder token = mock(IBinder.class);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
token /* activityToken */);
@@ -236,9 +238,9 @@ public class TransactionExecutorTests {
mExecutor.execute(transaction);
InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
- inOrder.verify(callback1, times(1)).execute(eq(mTransactionHandler), eq(token), any());
- inOrder.verify(callback2, times(1)).execute(eq(mTransactionHandler), eq(token), any());
- inOrder.verify(stateRequest, times(1)).execute(eq(mTransactionHandler), eq(token), any());
+ inOrder.verify(callback1).execute(eq(mTransactionHandler), eq(token), any());
+ inOrder.verify(callback2).execute(eq(mTransactionHandler), eq(token), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
}
@Test
@@ -273,7 +275,7 @@ public class TransactionExecutorTests {
// The launch transaction should not be executed because its token is in the
// to-be-destroyed container.
- verify(launchItem, times(0)).execute(any(), any(), any());
+ verify(launchItem, never()).execute(any(), any(), any());
// After the destroy transaction has been executed, the token should be removed.
mExecutor.execute(destroyTransaction);
@@ -282,6 +284,8 @@ public class TransactionExecutorTests {
@Test
public void testActivityResultRequiredStateResolution() {
+ when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
IBinder token = mock(IBinder.class);
@@ -292,12 +296,12 @@ public class TransactionExecutorTests {
// Verify resolution that should get to onPause
mClientRecord.setState(ON_RESUME);
mExecutor.executeCallbacks(transaction);
- verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
// Verify resolution that should get to onStart
mClientRecord.setState(ON_STOP);
mExecutor.executeCallbacks(transaction);
- verify(mExecutor, times(1)).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
}
@Test
@@ -433,6 +437,36 @@ public class TransactionExecutorTests {
mExecutorHelper.getClosestPreExecutionState(mClientRecord, ON_RESUME));
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testActivityItemNullRecordThrowsException() {
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */, token);
+ transaction.addCallback(activityItem);
+ when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+ mExecutor.executeCallbacks(transaction);
+ }
+
+ @Test
+ public void testActivityItemExecute() {
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */, token);
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ transaction.addCallback(activityItem);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ transaction.setLifecycleStateRequest(stateRequest);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ mExecutor.execute(transaction);
+
+ final InOrder inOrder = inOrder(activityItem, stateRequest);
+ inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
private static int[] shuffledArray(int[] inputArray) {
final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
Collections.shuffle(list);
@@ -489,13 +523,13 @@ public class TransactionExecutorTests {
public static final Parcelable.Creator<StubItem> CREATOR =
new Parcelable.Creator<StubItem>() {
- public StubItem createFromParcel(Parcel in) {
- return new StubItem(in);
- }
-
- public StubItem[] newArray(int size) {
- return new StubItem[size];
- }
- };
+ public StubItem createFromParcel(Parcel in) {
+ return new StubItem(in);
+ }
+
+ public StubItem[] newArray(int size) {
+ return new StubItem[size];
+ }
+ };
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 2bf9848304ad..7766b575c156 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -192,7 +192,7 @@ public class TransactionParcelTests {
PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments(
- Surface.ROTATION_90, DisplayCutout.NO_CUTOUT);
+ Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT);
LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo,
config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
@@ -352,7 +352,8 @@ public class TransactionParcelTests {
ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(),
null /* activityToken */);
transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(),
- new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT)));
+ new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080,
+ DisplayCutout.NO_CUTOUT)));
writeAndPrepareForReading(transaction);
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
new file mode 100644
index 000000000000..01a25b27baf6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 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.app.time;
+
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TimeZoneCapabilitiesTest {
+
+ private static final UserHandle TEST_USER_HANDLE = UserHandle.of(12345);
+
+ @Test
+ public void testEquals() {
+ TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+
+ builder2.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertNotEquals(one, two);
+ }
+
+ builder1.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ {
+ TimeZoneCapabilities one = builder1.build();
+ TimeZoneCapabilities two = builder2.build();
+ assertEquals(one, two);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+
+ builder.setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
+ assertRoundTripParcelable(builder.build());
+ }
+
+ @Test
+ public void testTryApplyConfigChanges_permitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_POSSESSED)
+ .build();
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertEquals(expected, capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+ }
+
+ @Test
+ public void testTryApplyConfigChanges_notPermitted() {
+ TimeZoneConfiguration oldConfiguration =
+ new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+ .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+ .setSuggestManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+ .build();
+
+ TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
+ .setAutoDetectionEnabled(false)
+ .build();
+
+ assertNull(capabilities.tryApplyConfigChanges(oldConfiguration, configChange));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
index faf908de8d4a..3948eb86c4f6 100644
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneConfigurationTest.java
+++ b/core/tests/coretests/src/android/app/time/TimeZoneConfigurationTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package android.app.time;
import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
@@ -23,16 +23,20 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import org.junit.Test;
+import org.junit.runner.RunWith;
+@RunWith(AndroidJUnit4.class)
+@SmallTest
public class TimeZoneConfigurationTest {
- private static final int ARBITRARY_USER_ID = 9876;
-
@Test
public void testBuilder_copyConstructor() {
TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true);
TimeZoneConfiguration configuration1 = builder1.build();
@@ -45,27 +49,27 @@ public class TimeZoneConfigurationTest {
@Test
public void testIntrospectionMethods() {
- TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID).build();
+ TimeZoneConfiguration empty = new TimeZoneConfiguration.Builder().build();
assertFalse(empty.isComplete());
- assertFalse(empty.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+ assertFalse(empty.hasIsAutoDetectionEnabled());
- TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.setGeoDetectionEnabled(true)
.build();
assertTrue(completeConfig.isComplete());
- assertTrue(completeConfig.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED));
+ assertTrue(completeConfig.hasIsGeoDetectionEnabled());
}
@Test
public void testBuilder_mergeProperties() {
- TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
.build();
{
TimeZoneConfiguration mergedEmptyAnd1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.mergeProperties(configuration1)
.build();
assertEquals(configuration1, mergedEmptyAnd1);
@@ -73,7 +77,7 @@ public class TimeZoneConfigurationTest {
{
TimeZoneConfiguration configuration2 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(false)
.build();
@@ -90,22 +94,14 @@ public class TimeZoneConfigurationTest {
@Test
public void testEquals() {
TimeZoneConfiguration.Builder builder1 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
{
TimeZoneConfiguration one = builder1.build();
assertEquals(one, one);
}
- {
- TimeZoneConfiguration.Builder differentUserBuilder =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID + 1);
- TimeZoneConfiguration one = builder1.build();
- TimeZoneConfiguration two = differentUserBuilder.build();
- assertNotEquals(one, two);
- }
-
TimeZoneConfiguration.Builder builder2 =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
{
TimeZoneConfiguration one = builder1.build();
TimeZoneConfiguration two = builder2.build();
@@ -159,7 +155,7 @@ public class TimeZoneConfigurationTest {
@Test
public void testParcelable() {
TimeZoneConfiguration.Builder builder =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID);
+ new TimeZoneConfiguration.Builder();
assertRoundTripParcelable(builder.build());
builder.setAutoDetectionEnabled(true);
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java b/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
deleted file mode 100644
index db127c6cb9ed..000000000000
--- a/core/tests/coretests/src/android/app/timezonedetector/TimeZoneCapabilitiesTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 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.app.timezonedetector;
-
-import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-
-public class TimeZoneCapabilitiesTest {
-
- private static final int ARBITRARY_USER_ID = 12345;
-
- @Test
- public void testEquals() {
- TimeZoneConfiguration configuration1 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneConfiguration configuration2 = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .setGeoDetectionEnabled(false)
- .build();
-
- TimeZoneCapabilities.Builder builder1 = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration1)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration1)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfiguration(configuration2);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfiguration(configuration2);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
-
- builder2.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertNotEquals(one, two);
- }
-
- builder1.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- {
- TimeZoneCapabilities one = builder1.build();
- TimeZoneCapabilities two = builder2.build();
- assertEquals(one, two);
- }
- }
-
- @Test
- public void testParcelable() {
- TimeZoneConfiguration configuration = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
- .setConfiguration(configuration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED);
- assertRoundTripParcelable(builder.build());
-
- builder.setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
-
- builder.setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
-
- builder.setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED);
- assertRoundTripParcelable(builder.build());
- }
-
- @Test
- public void testApplyUpdate_permitted() {
- TimeZoneConfiguration oldConfiguration =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
- .setConfiguration(oldConfiguration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_POSSESSED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_POSSESSED)
- .setSuggestManualTimeZone(CAPABILITY_POSSESSED)
- .build();
- assertEquals(oldConfiguration, capabilities.getConfiguration());
-
- TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .build();
-
- TimeZoneConfiguration expected = new TimeZoneConfiguration.Builder(oldConfiguration)
- .setAutoDetectionEnabled(false)
- .build();
- assertEquals(expected, capabilities.applyUpdate(configChange));
- }
-
- @Test
- public void testApplyUpdate_notPermitted() {
- TimeZoneConfiguration oldConfiguration =
- new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
- .build();
- TimeZoneCapabilities capabilities = new TimeZoneCapabilities.Builder()
- .setConfiguration(oldConfiguration)
- .setConfigureAutoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setConfigureGeoDetectionEnabled(CAPABILITY_NOT_ALLOWED)
- .setSuggestManualTimeZone(CAPABILITY_NOT_ALLOWED)
- .build();
- assertEquals(oldConfiguration, capabilities.getConfiguration());
-
- TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
- .setAutoDetectionEnabled(false)
- .build();
-
- assertNull(capabilities.applyUpdate(configChange));
- }
-}
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index 777f4a3e03a8..d1776fb7e5c1 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -19,6 +19,7 @@ package android.content;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.google.common.truth.Truth.assertThat;
@@ -188,19 +189,38 @@ public class ContextTest {
assertFalse(wrapper.isUiContext());
- wrapper = new ContextWrapper(new TestUiContext());
+ wrapper = new ContextWrapper(createUiContext());
assertTrue(wrapper.isUiContext());
}
- private static class TestUiContext extends ContextWrapper {
- TestUiContext() {
- super(null /* base */);
- }
+ @Test
+ public void testIsUiContext_UiContextDerivedContext() {
+ final Context uiContext = createUiContext();
+ Context context = uiContext.createAttributionContext(null /* attributionTag */);
- @Override
- public boolean isUiContext() {
- return true;
- }
+ assertTrue(context.isUiContext());
+
+ context = uiContext.createConfigurationContext(new Configuration());
+
+ assertTrue(context.isUiContext());
+ }
+
+ @Test
+ public void testIsUiContext_UiContextDerivedDisplayContext() {
+ final Context uiContext = createUiContext();
+ final Display secondaryDisplay =
+ getSecondaryDisplay(uiContext.getSystemService(DisplayManager.class));
+ final Context context = uiContext.createDisplayContext(secondaryDisplay);
+
+ assertFalse(context.isUiContext());
+ }
+
+ private Context createUiContext() {
+ final Context appContext = ApplicationProvider.getApplicationContext();
+ final DisplayManager displayManager = appContext.getSystemService(DisplayManager.class);
+ final Display display = displayManager.getDisplay(DEFAULT_DISPLAY);
+ return appContext.createDisplayContext(display)
+ .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
}
}
diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
index ba060fa630c5..593e70e6b257 100644
--- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
+++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java
@@ -45,7 +45,8 @@ public class CompoundFormulaTest {
CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
assertThat(compoundFormula.getConnector()).isEqualTo(CompoundFormula.AND);
- assertThat(compoundFormula.getFormulas()).containsAllOf(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2);
+ assertThat(compoundFormula.getFormulas())
+ .containsAtLeast(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2);
}
@Test
diff --git a/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
index d45fee97950f..9ad63adda6b7 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
+++ b/core/tests/coretests/src/android/content/pm/parsing/result/ParseInputAndResultTest.kt
@@ -113,7 +113,7 @@ class ParseInputAndResultTest {
assertError(result)
assertThat(result.errorCode).isEqualTo(errorCode)
assertThat(result.errorMessage).isEqualTo(errorMessage)
- assertThat(result.exception).isSameAs(exception)
+ assertThat(result.exception).isSameInstanceAs(exception)
}
@Test
@@ -125,13 +125,13 @@ class ParseInputAndResultTest {
assertError(result)
assertThat(result.errorCode).isEqualTo(errorCode)
assertThat(result.errorMessage).isEqualTo(errorMessage)
- assertThat(result.exception).isSameAs(exception)
+ assertThat(result.exception).isSameInstanceAs(exception)
val carriedResult = input.error<Int>(result)
assertError(carriedResult)
assertThat(carriedResult.errorCode).isEqualTo(errorCode)
assertThat(carriedResult.errorMessage).isEqualTo(errorMessage)
- assertThat(carriedResult.exception).isSameAs(exception)
+ assertThat(carriedResult.exception).isSameInstanceAs(exception)
}
@Test
@@ -259,7 +259,7 @@ class ParseInputAndResultTest {
private fun assertSuccess(expected: Any? = null, result: ParseResult<*>) {
assertThat(result.isError).isFalse()
assertThat(result.isSuccess).isTrue()
- assertThat(result.result).isSameAs(expected)
+ assertThat(result.result).isSameInstanceAs(expected)
assertThat(result.errorCode).isEqualTo(PackageManager.INSTALL_SUCCEEDED)
assertThat(result.errorMessage).isNull()
assertThat(result.exception).isNull()
diff --git a/core/tests/coretests/src/android/os/FileBridgeTest.java b/core/tests/coretests/src/android/os/FileBridgeTest.java
index d4f6b1fcec4e..708bfa6ece2e 100644
--- a/core/tests/coretests/src/android/os/FileBridgeTest.java
+++ b/core/tests/coretests/src/android/os/FileBridgeTest.java
@@ -16,6 +16,9 @@
package android.os;
+import static android.os.ParcelFileDescriptor.MODE_CREATE;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+
import android.os.FileBridge.FileBridgeOutputStream;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
@@ -25,7 +28,6 @@ import libcore.io.Streams;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
@@ -33,7 +35,7 @@ import java.util.Random;
public class FileBridgeTest extends AndroidTestCase {
private File file;
- private FileOutputStream fileOs;
+ private ParcelFileDescriptor outputFile;
private FileBridge bridge;
private FileBridgeOutputStream client;
@@ -44,17 +46,17 @@ public class FileBridgeTest extends AndroidTestCase {
file = getContext().getFileStreamPath("meow.dat");
file.delete();
- fileOs = new FileOutputStream(file);
+ outputFile = ParcelFileDescriptor.open(file, MODE_CREATE | MODE_READ_WRITE);
bridge = new FileBridge();
- bridge.setTargetFile(fileOs.getFD());
+ bridge.setTargetFile(outputFile);
bridge.start();
client = new FileBridgeOutputStream(bridge.getClientSocket());
}
@Override
protected void tearDown() throws Exception {
- fileOs.close();
+ outputFile.close();
file.delete();
}
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
new file mode 100644
index 000000000000..c3d84ecc2abf
--- /dev/null
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.media.AudioAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.junit.MockitoJUnitRunner;
+
+/**
+ * Tests for {@link Vibrator}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksCoreTests:VibratorTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class VibratorTest {
+
+ private Vibrator mVibratorSpy;
+
+ @Before
+ public void setUp() {
+ mVibratorSpy = spy(InstrumentationRegistry.getContext().getSystemService(Vibrator.class));
+ }
+
+ @Test
+ public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+
+ mVibratorSpy.vibrate(effect, audioAttributes);
+
+ ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass(
+ VibrationAttributes.class);
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), isNull(), captor.capture());
+
+ VibrationAttributes vibrationAttributes = captor.getValue();
+ assertEquals(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
+ vibrationAttributes.getUsage());
+ // Keeps original AudioAttributes usage to be used by the VibratorService.
+ assertEquals(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ vibrationAttributes.getAudioUsage());
+ }
+
+ @Test
+ public void vibrate_withUnknownAudioAttributes_hasTouchUsageFromEffect() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
+ AudioAttributes.USAGE_UNKNOWN).build();
+
+ mVibratorSpy.vibrate(effect, audioAttributes);
+
+ ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass(
+ VibrationAttributes.class);
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), isNull(), captor.capture());
+
+ VibrationAttributes vibrationAttributes = captor.getValue();
+ assertEquals(VibrationAttributes.USAGE_TOUCH,
+ vibrationAttributes.getUsage());
+ // Sets AudioAttributes usage based on effect.
+ assertEquals(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+ vibrationAttributes.getAudioUsage());
+ }
+
+ @Test
+ public void vibrate_withoutAudioAttributes_hasTouchUsageFromEffect() {
+ mVibratorSpy.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+
+ ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass(
+ VibrationAttributes.class);
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), any(), isNull(), captor.capture());
+
+ VibrationAttributes vibrationAttributes = captor.getValue();
+ assertEquals(VibrationAttributes.USAGE_TOUCH, vibrationAttributes.getUsage());
+ // Sets AudioAttributes usage based on effect.
+ assertEquals(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+ vibrationAttributes.getAudioUsage());
+ }
+
+ @Test
+ public void vibrate_withoutAudioAttributesAndLongEffect_hasUnknownUsage() {
+ mVibratorSpy.vibrate(VibrationEffect.createOneShot(10_000, 255));
+
+ ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass(
+ VibrationAttributes.class);
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), any(), isNull(), captor.capture());
+
+ VibrationAttributes vibrationAttributes = captor.getValue();
+ assertEquals(VibrationAttributes.USAGE_UNKNOWN, vibrationAttributes.getUsage());
+ assertEquals(AudioAttributes.USAGE_UNKNOWN, vibrationAttributes.getAudioUsage());
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
new file mode 100644
index 000000000000..f92ea99e8f64
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.text;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.text.PositionedGlyphs;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextShaperTest {
+
+ @Test
+ public void testFontWithPath() {
+ TextPaint p = new TextPaint();
+ p.setFontFeatureSettings("'wght' 900");
+ List<PositionedGlyphs> glyphs = StyledTextShaper.shapeText("a", 0, 1,
+ TextDirectionHeuristics.LTR, p);
+ assertThat(glyphs.size()).isEqualTo(1);
+ // This test only passes if the font of the Latin font is variable font.
+ assertThat(glyphs.get(0).getFont(0).getFile()).isNotNull();
+
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index be6ef195fa96..285ad9fae13c 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -792,5 +792,6 @@ public class TextUtilsTest {
assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3));
assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3));
assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
+ assertNull(TextUtils.trimToLengthWithEllipsis(null, 3));
}
}
diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
index 2fc42e91a8cc..3cf1722d49d3 100644
--- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
+++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java
@@ -77,8 +77,10 @@ public class DisplayAdjustmentsTests {
final int realRotation = Surface.ROTATION_0;
final int fixedRotation = Surface.ROTATION_90;
- mDisplayAdjustments.setFixedRotationAdjustments(
- new FixedRotationAdjustments(fixedRotation, null /* cutout */));
+ final int appWidth = 1080;
+ final int appHeight = 1920;
+ mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments(
+ fixedRotation, appWidth, appHeight, null /* cutout */));
final int w = 1000;
final int h = 2000;
@@ -95,13 +97,21 @@ public class DisplayAdjustmentsTests {
metrics.heightPixels = metrics.noncompatHeightPixels = h;
final DisplayMetrics flippedMetrics = new DisplayMetrics();
- flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h;
+ // The physical dpi should not be adjusted.
+ flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w;
flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h;
- flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w;
+ flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h;
flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w;
mDisplayAdjustments.adjustMetrics(metrics, realRotation);
assertEquals(flippedMetrics, metrics);
+
+ mDisplayAdjustments.adjustGlobalAppMetrics(metrics);
+
+ assertEquals(appWidth, metrics.widthPixels);
+ assertEquals(appWidth, metrics.noncompatWidthPixels);
+ assertEquals(appHeight, metrics.heightPixels);
+ assertEquals(appHeight, metrics.noncompatHeightPixels);
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsFlagsTest.java b/core/tests/coretests/src/android/view/InsetsFlagsTest.java
deleted file mode 100644
index b4302e79ed6a..000000000000
--- a/core/tests/coretests/src/android/view/InsetsFlagsTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 static android.view.InsetsFlags.getAppearance;
-import static android.view.View.NAVIGATION_BAR_TRANSLUCENT;
-import static android.view.View.NAVIGATION_BAR_TRANSPARENT;
-import static android.view.View.STATUS_BAR_TRANSLUCENT;
-import static android.view.View.STATUS_BAR_TRANSPARENT;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
-import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
-
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.WindowInsetsController.Appearance;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link InsetsFlags}.
- *
- * <p>Build/Install/Run:
- * atest FrameworksCoreTests:InsetsFlagsTest
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class InsetsFlagsTest {
-
- @Test
- public void testGetAppearance() {
- assertContainsAppearance(APPEARANCE_LOW_PROFILE_BARS, SYSTEM_UI_FLAG_LOW_PROFILE);
- assertContainsAppearance(APPEARANCE_LIGHT_STATUS_BARS, SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
- assertContainsAppearance(APPEARANCE_LIGHT_NAVIGATION_BARS,
- SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
- assertContainsAppearance(APPEARANCE_OPAQUE_STATUS_BARS,
- 0xffffffff & ~(STATUS_BAR_TRANSLUCENT | STATUS_BAR_TRANSPARENT));
- assertContainsAppearance(APPEARANCE_OPAQUE_NAVIGATION_BARS,
- 0xffffffff & ~(NAVIGATION_BAR_TRANSLUCENT | NAVIGATION_BAR_TRANSPARENT));
- }
-
- void assertContainsAppearance(@Appearance int appearance, int systemUiVisibility) {
- assertTrue((getAppearance(systemUiVisibility) & appearance) == appearance);
- }
-}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index afab7696e87d..8a000a034702 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -124,6 +124,22 @@ public class InsetsStateTest {
}
@Test
+ public void testCalculateInsets_extraNavRightClimateTop() throws Exception {
+ mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
+ mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
+ mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
+ false, DisplayCutout.NO_CUTOUT, 0, 0, 0, TYPE_APPLICATION,
+ WINDOWING_MODE_UNDEFINED, null);
+ // ITYPE_CLIMATE_BAR is a type of status bar and ITYPE_EXTRA_NAVIGATION_BAR is a type
+ // of navigation bar.
+ assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
+ assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
+ assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
+ }
+
+ @Test
public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
@@ -336,6 +352,8 @@ public class InsetsStateTest {
public void testGetDefaultVisibility() {
assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR));
assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(InsetsState.getDefaultVisibility(ITYPE_CLIMATE_BAR));
+ assertTrue(InsetsState.getDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR));
assertTrue(InsetsState.getDefaultVisibility(ITYPE_CAPTION_BAR));
assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
}
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTest.java b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
new file mode 100644
index 000000000000..d667af39f585
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.os.Handler;
+import android.text.format.DateUtils;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for internal APIs/behaviors of {@link View} and {@link InputConnection}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ViewInputConnectionTest {
+ @Rule
+ public ActivityTestRule<ViewInputConnectionTestActivity> mActivityRule =
+ new ActivityTestRule<>(ViewInputConnectionTestActivity.class);
+
+ private Instrumentation mInstrumentation;
+ private ViewInputConnectionTestActivity mActivity;
+ private InputMethodManager mImm;
+
+ @Before
+ public void before() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ PollingCheck.waitFor(5 * DateUtils.SECOND_IN_MILLIS, mActivity::hasWindowFocus);
+ assertTrue(mActivity.hasWindowFocus());
+ mImm = mActivity.getSystemService(InputMethodManager.class);
+ }
+
+ @Test
+ public void testInputConnectionCallbacks() throws Throwable {
+ // Add two EditText inputs to the layout view.
+ final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+ final TestEditText editText1 = new TestEditText(mActivity, false);
+ final TestEditText editText2 = new TestEditText(mActivity, false);
+ mActivityRule.runOnUiThread(() -> {
+ viewGroup.addView(editText1);
+ viewGroup.addView(editText2);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ // Focus into the first EditText.
+ mActivityRule.runOnUiThread(editText1::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(editText1.isFocused()).isTrue();
+ assertThat(editText2.isFocused()).isFalse();
+
+ // Show the IME for the first EditText. Assert that the appropriate opened/closed callbacks
+ // have been invoked (InputConnection opened for the first EditText).
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText1, 0));
+ mInstrumentation.waitForIdleSync();
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionOpened).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+ assertThat(editText2.mCalledOnCreateInputConnection).isFalse();
+ assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+ });
+
+ // Focus into the second EditText.
+ mActivityRule.runOnUiThread(editText2::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(editText1.isFocused()).isFalse();
+ assertThat(editText2.isFocused()).isTrue();
+
+ // Show the IME for the second EditText. Assert that the appropriate opened/closed callbacks
+ // have been invoked (InputConnection closed for the first EditText and opened for the
+ // second EditText).
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText2, 0));
+ mInstrumentation.waitForIdleSync();
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionOpened).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionClosed).isTrue();
+
+ assertThat(editText2.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText2.mCalledOnInputConnectionOpened).isTrue();
+ assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+ });
+ }
+
+ @Test
+ public void testInputConnectionCallbacks_nullInputConnection() throws Throwable {
+ // Add two EditText inputs to the layout view.
+ final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+ final TestEditText editText1 = new TestEditText(mActivity, true);
+ final TestEditText editText2 = new TestEditText(mActivity, true);
+ mActivityRule.runOnUiThread(() -> {
+ viewGroup.addView(editText1);
+ viewGroup.addView(editText2);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ // Focus into the first EditText.
+ mActivityRule.runOnUiThread(editText1::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(editText1.isFocused()).isTrue();
+ assertThat(editText2.isFocused()).isFalse();
+
+ // Show the IME for the first EditText. Assert that the opened/closed callbacks are not
+ // invoked since there's no input connection.
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText1, 0));
+ mInstrumentation.waitForIdleSync();
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+ assertThat(editText2.mCalledOnCreateInputConnection).isFalse();
+ assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+ });
+
+ // Focus into the second EditText.
+ mActivityRule.runOnUiThread(editText2::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(editText1.isFocused()).isFalse();
+ assertThat(editText2.isFocused()).isTrue();
+
+ // Show the IME for the second EditText. Assert that the opened/closed callbacks are not
+ // invoked since there's no input connection.
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(editText2, 0));
+ mInstrumentation.waitForIdleSync();
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(editText1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText1.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(editText1.mCalledOnInputConnectionClosed).isFalse();
+
+ assertThat(editText2.mCalledOnCreateInputConnection).isTrue();
+ assertThat(editText2.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(editText2.mCalledOnInputConnectionClosed).isFalse();
+ });
+ }
+
+ @Test
+ public void testInputConnectionCallbacks_nonEditableInput() throws Throwable {
+ final ViewGroup viewGroup = mActivity.findViewById(R.id.root);
+ final TestButton view1 = new TestButton(mActivity);
+ final TestButton view2 = new TestButton(mActivity);
+ mActivityRule.runOnUiThread(() -> {
+ viewGroup.addView(view1);
+ viewGroup.addView(view2);
+ });
+ mInstrumentation.waitForIdleSync();
+
+ // Request focus + IME on the first view.
+ mActivityRule.runOnUiThread(view1::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(view1.isFocused()).isTrue();
+ assertThat(view2.isFocused()).isFalse();
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(view1, 0));
+ mInstrumentation.waitForIdleSync();
+
+ // Assert that the opened/closed callbacks are not invoked since there's no InputConnection.
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(view1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(view1.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(view1.mCalledOnInputConnectionClosed).isFalse();
+
+ assertThat(view2.mCalledOnCreateInputConnection).isFalse();
+ assertThat(view2.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(view2.mCalledOnInputConnectionClosed).isFalse();
+ });
+
+ // Request focus + IME on the second view.
+ mActivityRule.runOnUiThread(view2::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertThat(view1.isFocused()).isFalse();
+ assertThat(view2.isFocused()).isTrue();
+ mActivityRule.runOnUiThread(() -> mImm.showSoftInput(view1, 0));
+ mInstrumentation.waitForIdleSync();
+
+ // Assert that the opened/closed callbacks are not invoked since there's no InputConnection.
+ mActivityRule.runOnUiThread(() -> {
+ assertThat(view1.mCalledOnCreateInputConnection).isTrue();
+ assertThat(view1.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(view1.mCalledOnInputConnectionClosed).isFalse();
+
+ assertThat(view2.mCalledOnCreateInputConnection).isTrue();
+ assertThat(view2.mCalledOnInputConnectionOpened).isFalse();
+ assertThat(view2.mCalledOnInputConnectionClosed).isFalse();
+ });
+ }
+
+ private static class TestEditText extends EditText {
+ private final boolean mReturnNullInputConnection;
+
+ public boolean mCalledOnCreateInputConnection = false;
+ public boolean mCalledOnInputConnectionOpened = false;
+ public boolean mCalledOnInputConnectionClosed = false;
+
+ TestEditText(Context context, boolean returnNullInputConnection) {
+ super(context);
+ mReturnNullInputConnection = returnNullInputConnection;
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ mCalledOnCreateInputConnection = true;
+ if (mReturnNullInputConnection) {
+ return null;
+ } else {
+ return super.onCreateInputConnection(outAttrs);
+ }
+ }
+
+ @Override
+ public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
+ mCalledOnInputConnectionOpened = true;
+ super.onInputConnectionOpenedInternal(inputConnection, editorInfo, handler);
+ }
+
+ @Override
+ public void onInputConnectionClosedInternal() {
+ mCalledOnInputConnectionClosed = true;
+ super.onInputConnectionClosedInternal();
+ }
+ }
+
+ private static class TestButton extends Button {
+ public boolean mCalledOnCreateInputConnection = false;
+ public boolean mCalledOnInputConnectionOpened = false;
+ public boolean mCalledOnInputConnectionClosed = false;
+
+ TestButton(Context context) {
+ super(context);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ mCalledOnCreateInputConnection = true;
+ return super.onCreateInputConnection(outAttrs);
+ }
+
+ @Override
+ public void onInputConnectionOpenedInternal(@NonNull InputConnection inputConnection,
+ @NonNull EditorInfo editorInfo, @Nullable Handler handler) {
+ mCalledOnInputConnectionOpened = true;
+ super.onInputConnectionOpenedInternal(inputConnection, editorInfo, handler);
+ }
+
+ @Override
+ public void onInputConnectionClosedInternal() {
+ mCalledOnInputConnectionClosed = true;
+ super.onInputConnectionClosedInternal();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java b/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java
new file mode 100644
index 000000000000..55c812df6a6a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewInputConnectionTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+public class ViewInputConnectionTestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view_ic_test);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 5c410878c99d..92fb52837c36 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -17,16 +17,23 @@
package android.view.inputmethod;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import android.annotation.Nullable;
+import android.graphics.BlurMaskFilter;
import android.os.Parcel;
import android.os.UserHandle;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
+import android.text.style.MaskFilterSpan;
+import android.text.style.UnderlineSpan;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -280,6 +287,55 @@ public class EditorInfoTest {
editorInfo.getInitialTextBeforeCursor(/* length= */ 60, /* flags= */ 1);
}
+ @Test
+ public void testSpanAfterSurroundingTextRetrieval() {
+ final int flags = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
+ final SpannableStringBuilder sb =
+ new SpannableStringBuilder("ParcelableSpan and non-ParcelableSpan test");
+ final int parcelableStart = 0;
+ final int parcelableEnd = 14;
+ final int nonParcelableStart = 19;
+ final int nonParcelableEnd = 37;
+ final UnderlineSpan parcelableSpan = new UnderlineSpan();
+ final MaskFilterSpan nonParcelableSpan =
+ new MaskFilterSpan(new BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL));
+
+ // Set spans
+ sb.setSpan(parcelableSpan, parcelableStart, parcelableEnd, flags);
+ sb.setSpan(nonParcelableSpan, nonParcelableStart, nonParcelableEnd, flags);
+
+ Object[] spansBefore = sb.getSpans(/* queryStart= */ 0, sb.length(), Object.class);
+ Object[] parcelableSpanBefore = sb.getSpans(parcelableStart, parcelableEnd, Object.class);
+
+ // Verify the original spans length is 2, include ParcelableSpan and non-ParcelableSpan.
+ assertNotNull(spansBefore);
+ assertEquals(2, spansBefore.length);
+
+ // Set initial surrounding text then retrieve the text.
+ EditorInfo editorInfo = new EditorInfo();
+ editorInfo.initialSelStart = sb.length();
+ editorInfo.initialSelEnd = sb.length();
+ editorInfo.inputType = EditorInfo.TYPE_CLASS_TEXT;
+ editorInfo.setInitialSurroundingText(sb);
+ SpannableString textBeforeCursor =
+ (SpannableString) editorInfo.getInitialTextBeforeCursor(
+ /* length= */ 60, /* flags= */ 1);
+
+ Object[] spansAfter =
+ textBeforeCursor.getSpans(/* queryStart= */ 0, sb.length(), Object.class);
+ Object[] parcelableSpanAfter =
+ textBeforeCursor.getSpans(parcelableStart, parcelableEnd, Object.class);
+ Object[] nonParcelableSpanAfter =
+ textBeforeCursor.getSpans(nonParcelableStart, nonParcelableEnd, Object.class);
+
+ // Verify only remain ParcelableSpan and it's different from the original Span instance.
+ assertNotNull(spansAfter);
+ assertEquals(1, spansAfter.length);
+ assertEquals(1, parcelableSpanAfter.length);
+ assertEquals(0, nonParcelableSpanAfter.length);
+ assertNotEquals(parcelableSpanBefore, parcelableSpanAfter);
+ }
+
private static void assertExpectedTextLength(EditorInfo editorInfo,
@Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength,
@Nullable Integer expectAfterCursorLength) {
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 628252d8ca6c..402b92a3f2a2 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -52,7 +52,8 @@ public class TextClassificationManagerTest {
@Test
public void testGetLocalTextClassifier() {
- assertThat(mTcm.getTextClassifier(TextClassifier.LOCAL)).isSameAs(TextClassifier.NO_OP);
+ assertThat(mTcm.getTextClassifier(TextClassifier.LOCAL))
+ .isSameInstanceAs(TextClassifier.NO_OP);
}
@Test
diff --git a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
index ec8dfcbfbde9..91266f0bbb9e 100644
--- a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
@@ -16,20 +16,10 @@
package android.widget;
-import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT;
-
import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.Activity;
import android.app.Instrumentation;
-import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Intent;
import android.text.Selection;
import android.text.Spannable;
@@ -44,10 +34,6 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mockito;
-
-import java.util.Set;
/**
* Tests for {@link Intent#ACTION_PROCESS_TEXT} functionality in {@link TextView}.
@@ -78,9 +64,6 @@ public class TextViewProcessTextTest {
mTextView.setTextIsSelectable(true);
Selection.setSelection((Spannable) mTextView.getText(), 0, mTextView.getText().length());
- MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper();
- mTextView.setRichContentReceiver(mockReceiverWrapper);
-
// We need to run this in the UI thread, as it will create a Toast.
mActivityRule.runOnUiThread(() -> {
triggerOnActivityResult(Activity.RESULT_OK, "Text is replaced.");
@@ -89,46 +72,6 @@ public class TextViewProcessTextTest {
// This is a TextView, which can't be modified. Hence no change should have been made.
assertEquals(originalText, mTextView.getText().toString());
- verifyZeroInteractions(mockReceiverWrapper.mMock);
- }
-
- @Test
- public void testProcessTextActivityResultEditable_defaultRichContentReceiver()
- throws Throwable {
- mActivityRule.runOnUiThread(() -> mTextView = new EditText(mActivity));
- mInstrumentation.waitForIdleSync();
- CharSequence originalText = "This is some text.";
- mTextView.setText(originalText, BufferType.SPANNABLE);
- assertEquals(originalText, mTextView.getText().toString());
- mTextView.setTextIsSelectable(true);
- Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
-
- CharSequence newText = "Text is replaced.";
- triggerOnActivityResult(Activity.RESULT_OK, newText);
-
- assertEquals(newText, mTextView.getText().toString());
- }
-
- @Test
- public void testProcessTextActivityResultEditable_customRichContentReceiver() throws Throwable {
- mActivityRule.runOnUiThread(() -> mTextView = new EditText(mActivity));
- mInstrumentation.waitForIdleSync();
- CharSequence originalText = "This is some text.";
- mTextView.setText(originalText, BufferType.SPANNABLE);
- assertEquals(originalText, mTextView.getText().toString());
- mTextView.setTextIsSelectable(true);
- Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
-
- MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper();
- mTextView.setRichContentReceiver(mockReceiverWrapper);
-
- CharSequence newText = "Text is replaced.";
- triggerOnActivityResult(Activity.RESULT_OK, newText);
-
- ClipData expectedClip = ClipData.newPlainText("", newText);
- verify(mockReceiverWrapper.mMock, times(1)).onReceive(
- eq(mTextView), clipEq(expectedClip), eq(SOURCE_PROCESS_TEXT), eq(0));
- verifyNoMoreInteractions(mockReceiverWrapper.mMock);
}
@Test
@@ -141,14 +84,10 @@ public class TextViewProcessTextTest {
mTextView.setTextIsSelectable(true);
Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
- MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper();
- mTextView.setRichContentReceiver(mockReceiverWrapper);
-
CharSequence newText = "Text is replaced.";
triggerOnActivityResult(Activity.RESULT_CANCELED, newText);
assertEquals(originalText, mTextView.getText().toString());
- verifyZeroInteractions(mockReceiverWrapper.mMock);
}
@Test
@@ -161,13 +100,9 @@ public class TextViewProcessTextTest {
mTextView.setTextIsSelectable(true);
Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
- MockReceiverWrapper mockReceiverWrapper = new MockReceiverWrapper();
- mTextView.setRichContentReceiver(mockReceiverWrapper);
-
mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, null);
assertEquals(originalText, mTextView.getText().toString());
- verifyZeroInteractions(mockReceiverWrapper.mMock);
}
private void triggerOnActivityResult(int resultCode, CharSequence replacementText) {
@@ -175,55 +110,4 @@ public class TextViewProcessTextTest {
data.putExtra(Intent.EXTRA_PROCESS_TEXT, replacementText);
mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, resultCode, data);
}
-
- // This wrapper is used so that we only mock and verify the public callback methods. In addition
- // to the public methods, the RichContentReceiver interface has some hidden default methods;
- // we don't want to mock or assert calls to these helper functions (they are an implementation
- // detail).
- private static class MockReceiverWrapper implements RichContentReceiver<TextView> {
- private final RichContentReceiver<TextView> mMock;
-
- @SuppressWarnings("unchecked")
- MockReceiverWrapper() {
- this.mMock = Mockito.mock(RichContentReceiver.class);
- }
-
- public RichContentReceiver<TextView> getMock() {
- return mMock;
- }
-
- @Override
- public boolean onReceive(TextView view, ClipData clip, @Source int source,
- @Flags int flags) {
- return mMock.onReceive(view, clip, source, flags);
- }
-
- @Override
- public Set<String> getSupportedMimeTypes() {
- return mMock.getSupportedMimeTypes();
- }
- }
-
- private static ClipData clipEq(ClipData expected) {
- return argThat(new ClipDataArgumentMatcher(expected));
- }
-
- private static class ClipDataArgumentMatcher implements ArgumentMatcher<ClipData> {
- private final ClipData mExpected;
-
- private ClipDataArgumentMatcher(ClipData expected) {
- this.mExpected = expected;
- }
-
- @Override
- public boolean matches(ClipData actual) {
- ClipDescription actualDesc = actual.getDescription();
- ClipDescription expectedDesc = mExpected.getDescription();
- return expectedDesc.getLabel().equals(actualDesc.getLabel())
- && actualDesc.getMimeTypeCount() == 1
- && expectedDesc.getMimeType(0).equals(actualDesc.getMimeType(0))
- && actual.getItemCount() == 1
- && mExpected.getItemAt(0).getText().equals(actual.getItemAt(0).getText());
- }
- }
}
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java b/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java
deleted file mode 100644
index 936c9999e7f8..000000000000
--- a/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2007 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.widget.focus;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-
-import com.google.android.collect.Lists;
-
-import java.util.List;
-
-public class ListOfEditTexts extends Activity {
-
- private int mLinesPerEditText = 12;
-
- private ListView mListView;
- private LinearLayout mLinearLayout;
-
- public ListView getListView() {
- return mListView;
- }
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- // create linear layout
- mLinearLayout = new LinearLayout(this);
- mLinearLayout.setOrientation(LinearLayout.VERTICAL);
- mLinearLayout.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
-
- // add a button above
- Button buttonAbove = new Button(this);
- buttonAbove.setLayoutParams(
- new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- buttonAbove.setText("button above list");
- mLinearLayout.addView(buttonAbove);
-
- // add a list view to it
- mListView = new ListView(this);
- mListView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- mListView.setDrawSelectorOnTop(false);
- mListView.setItemsCanFocus(true);
- mListView.setLayoutParams((new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- 0,
- 1f)));
-
- List<String> bodies = Lists.newArrayList(
- getBody("zero hello, my name is android"),
- getBody("one i'm a paranoid android"),
- getBody("two i robot. huh huh."),
- getBody("three not the g-phone!"));
-
- mListView.setAdapter(new MyAdapter(this, bodies));
- mLinearLayout.addView(mListView);
-
- // add button below
- Button buttonBelow = new Button(this);
- buttonBelow.setLayoutParams(
- new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- buttonBelow.setText("button below list");
- mLinearLayout.addView(buttonBelow);
-
- setContentView(mLinearLayout);
- }
-
- String getBody(String line) {
- StringBuilder sb = new StringBuilder((line.length() + 5) * mLinesPerEditText);
- for (int i = 0; i < mLinesPerEditText; i++) {
- sb.append(i + 1).append(' ').append(line);
- if (i < mLinesPerEditText - 1) {
- sb.append('\n'); // all but last line
- }
- }
- return sb.toString();
- }
-
-
- private static class MyAdapter extends ArrayAdapter<String> {
-
- public MyAdapter(Context context, List<String> bodies) {
- super(context, 0, bodies);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- String body = getItem(position);
-
- if (convertView != null) {
- ((EditText) convertView).setText(body);
- return convertView;
- }
-
- EditText editText = new EditText(getContext());
- editText.setText(body);
- return editText;
- }
- }
-}
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index f108eb8aeb0b..a2bc77a71c90 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -81,7 +81,7 @@ public class AndroidFutureTest {
future.completeExceptionally(origException);
ExecutionException executionException =
expectThrows(ExecutionException.class, future::get);
- assertThat(executionException.getCause()).isSameAs(origException);
+ assertThat(executionException.getCause()).isSameInstanceAs(origException);
}
@Test
@@ -92,7 +92,7 @@ public class AndroidFutureTest {
CountDownLatch latch = new CountDownLatch(1);
future.whenComplete((obj, err) -> {
assertThat(obj).isNull();
- assertThat(err).isSameAs(origException);
+ assertThat(err).isSameInstanceAs(origException);
latch.countDown();
});
latch.await();
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index ece5037de15f..e17800f7b6fd 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -16,7 +16,7 @@
package com.android.internal.jank;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_GESTURE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.google.common.truth.Truth.assertThat;
@@ -75,20 +75,13 @@ public class FrameTrackerTest {
doNothing().when(mRenderer).addObserver(any());
doNothing().when(mRenderer).removeObserver(any());
- Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
- mTracker = Mockito.spy(new FrameTracker(session, handler, mRenderer, mWrapper));
+ Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ mTracker = Mockito.spy(
+ new FrameTracker(session, handler, mRenderer, mWrapper));
doNothing().when(mTracker).triggerPerfetto();
}
@Test
- public void testIgnoresSecondBegin() {
- // Observer should be only added once in continuous calls.
- mTracker.begin();
- mTracker.begin();
- verify(mRenderer, only()).addObserver(any());
- }
-
- @Test
public void testOnlyFirstFrameOverThreshold() {
// Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP
when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP))
@@ -170,6 +163,29 @@ public class FrameTrackerTest {
verify(mTracker).triggerPerfetto();
}
+ @Test
+ public void testBeginCancel() {
+ mTracker.begin();
+ verify(mRenderer).addObserver(any());
+
+ // First frame - not janky
+ setupFirstFrameMockWithDuration(4);
+ mTracker.onFrameMetricsAvailable(0);
+
+ // normal frame - not janky
+ setupOtherFrameMockWithDuration(12);
+ mTracker.onFrameMetricsAvailable(0);
+
+ // a janky frame
+ setupOtherFrameMockWithDuration(30);
+ mTracker.onFrameMetricsAvailable(0);
+
+ mTracker.cancel();
+ verify(mRenderer).removeObserver(any());
+ // Since the tracker has been cancelled, shouldn't trigger perfetto.
+ verify(mTracker, never()).triggerPerfetto();
+ }
+
private void setupFirstFrameMockWithDuration(long durationMillis) {
doReturn(1L).when(mWrapper).getMetric(FrameMetrics.FIRST_DRAW_FRAME);
doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis))
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 5c0b0c94f6d0..a9cfd286688b 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -16,15 +16,22 @@
package com.android.internal.jank;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_GESTURE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Message;
import android.view.View;
import android.view.ViewAttachTestActivity;
@@ -38,17 +45,20 @@ import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
-import org.mockito.Mockito;
-import org.testng.Assert;
+import org.mockito.ArgumentCaptor;
-import java.util.HashMap;
-import java.util.Map;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
@SmallTest
public class InteractionJankMonitorTest {
private ViewAttachTestActivity mActivity;
private View mView;
- private FrameTracker mTracker;
+ private HandlerThread mWorker;
@Rule
public ActivityTestRule<ViewAttachTestActivity> mRule =
@@ -61,55 +71,52 @@ public class InteractionJankMonitorTest {
mView = mActivity.getWindow().getDecorView();
assertThat(mView.isAttachedToWindow()).isTrue();
- InteractionJankMonitor.reset();
+ InteractionJankMonitor.abandon();
- // Prepare a FrameTracker to inject.
- Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
- FrameMetricsWrapper wrapper = Mockito.spy(new FrameTracker.FrameMetricsWrapper());
- ThreadedRendererWrapper renderer =
- Mockito.spy(new ThreadedRendererWrapper(mView.getThreadedRenderer()));
- Handler handler = mActivity.getMainThreadHandler();
- mTracker = Mockito.spy(new FrameTracker(session, handler, renderer, wrapper));
+ Handler handler = spy(new Handler(mActivity.getMainLooper()));
+ doReturn(true).when(handler).sendMessageAtTime(any(), anyLong());
+ mWorker = spy(new HandlerThread("Interaction-jank-monitor-test"));
+ doNothing().when(mWorker).start();
+ doReturn(handler).when(mWorker).getThreadHandler();
}
@Test
public void testBeginEnd() {
- // Should throw exception if the view is not attached.
- Assert.assertThrows(IllegalStateException.class,
- () -> InteractionJankMonitor.init(new View(mActivity)));
+ // Should return false if the view is not attached.
+ InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
+ assertThat(monitor.init(new View(mActivity))).isFalse();
// Verify we init InteractionJankMonitor correctly.
- Map<String, FrameTracker> map = new HashMap<>();
- HandlerThread worker = Mockito.spy(new HandlerThread("Aot-test"));
- doNothing().when(worker).start();
- InteractionJankMonitor.init(mView, mView.getThreadedRenderer(), map, worker);
- verify(worker).start();
+ assertThat(monitor.init(mView)).isTrue();
+ verify(mWorker).start();
+
+ Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
+ new ThreadedRendererWrapper(mView.getThreadedRenderer()),
+ new FrameMetricsWrapper()));
+ doReturn(tracker).when(monitor).createFrameTracker(any());
// Simulate a trace session and see if begin / end are invoked.
- Session session = new Session(CUJ_NOTIFICATION_SHADE_GESTURE);
- assertThat(map.get(session.getName())).isNull();
- InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE, mTracker);
- verify(mTracker).begin();
- assertThat(map.get(session.getName())).isEqualTo(mTracker);
- InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE);
- verify(mTracker).end();
- assertThat(map.get(session.getName())).isNull();
+ assertThat(monitor.begin(session.getCuj())).isTrue();
+ verify(tracker).begin();
+ assertThat(monitor.end(session.getCuj())).isTrue();
+ verify(tracker).end();
}
@Test
public void testCheckInitState() {
- // Should throw exception if invoking begin / end without init invocation.
- Assert.assertThrows(IllegalStateException.class,
- () -> InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE));
- Assert.assertThrows(IllegalStateException.class,
- () -> InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE));
+ InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker);
+
+ // Should return false if invoking begin / end without init invocation.
+ assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
+ assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse();
// Everything should be fine if invoking init first.
boolean thrown = false;
try {
- InteractionJankMonitor.init(mActivity.getWindow().getDecorView());
- InteractionJankMonitor.begin(CUJ_NOTIFICATION_SHADE_GESTURE);
- InteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_GESTURE);
+ monitor.init(mView);
+ assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
+ assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue();
} catch (Exception ex) {
thrown = true;
} finally {
@@ -117,4 +124,50 @@ public class InteractionJankMonitorTest {
}
}
+ @Test
+ public void testBeginCancel() {
+ InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
+
+ ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+ assertThat(monitor.init(mView)).isTrue();
+
+ Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
+ new ThreadedRendererWrapper(mView.getThreadedRenderer()),
+ new FrameMetricsWrapper()));
+ doReturn(tracker).when(monitor).createFrameTracker(any());
+
+ assertThat(monitor.begin(session.getCuj())).isTrue();
+ verify(tracker).begin();
+ verify(mWorker.getThreadHandler()).sendMessageAtTime(captor.capture(), anyLong());
+ Runnable runnable = captor.getValue().getCallback();
+ assertThat(runnable).isNotNull();
+ mWorker.getThreadHandler().removeCallbacks(runnable);
+ runnable.run();
+ verify(tracker).cancel();
+ }
+
+ @Test
+ public void testCujTypeEnumCorrectlyDefined() throws Exception {
+ List<Field> cujEnumFields =
+ Arrays.stream(InteractionJankMonitor.class.getDeclaredFields())
+ .filter(field -> field.getName().startsWith("CUJ_")
+ && Modifier.isStatic(field.getModifiers())
+ && field.getType() == int.class)
+ .collect(Collectors.toList());
+
+ HashSet<Integer> allValues = new HashSet<>();
+ for (Field field : cujEnumFields) {
+ int fieldValue = field.getInt(null);
+ assertWithMessage(
+ "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE",
+ field.getName())
+ .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length)
+ .isTrue();
+ assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
+ field.getName())
+ .that(allValues.add(fieldValue))
+ .isTrue();
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
index 22c41f3c9622..85f9c97629e3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBinderCallStatsTest.java
@@ -60,7 +60,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase {
stat1.cpuTimeMicros = 1000;
callStats.add(stat1);
- bi.noteBinderCallStats(workSourceUid, 42, callStats, null);
+ bi.noteBinderCallStats(workSourceUid, 42, callStats);
callStats.clear();
BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(callingUid,
@@ -70,7 +70,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase {
stat2.cpuTimeMicros = 500;
callStats.add(stat2);
- bi.noteBinderCallStats(workSourceUid, 8, callStats, null);
+ bi.noteBinderCallStats(workSourceUid, 8, callStats);
BatteryStatsImpl.Uid uid = bi.getUidStatsLocked(workSourceUid);
assertEquals(42 + 8, uid.getBinderCallCount());
@@ -112,7 +112,7 @@ public class BatteryStatsBinderCallStatsTest extends TestCase {
stat1b.cpuTimeMicros = 1500;
callStats.add(stat1b);
- bi.noteBinderCallStats(workSourceUid1, 65, callStats, null);
+ bi.noteBinderCallStats(workSourceUid1, 65, callStats);
// No recorded stats for some methods. Must use the global average.
callStats.clear();
@@ -121,11 +121,11 @@ public class BatteryStatsBinderCallStatsTest extends TestCase {
stat2.incrementalCallCount = 10;
callStats.add(stat2);
- bi.noteBinderCallStats(workSourceUid2, 40, callStats, null);
+ bi.noteBinderCallStats(workSourceUid2, 40, callStats);
// No stats for any calls. Must use the global average
callStats.clear();
- bi.noteBinderCallStats(workSourceUid3, 50, callStats, null);
+ bi.noteBinderCallStats(workSourceUid3, 50, callStats);
bi.updateSystemServiceCallStats();
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 96250db4aa51..0eb34a993dec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -47,8 +47,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Random;
+import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -770,13 +772,27 @@ public class BinderCallsStatsTest {
bcs.setSamplingInterval(1);
bcs.setTrackScreenInteractive(false);
+
final ArrayList<BinderCallsStats.CallStat> callStatsList = new ArrayList<>();
- bcs.setCallStatsObserver(
- (workSourceUid, incrementalCallCount, callStats, binderThreadIds) ->
- callStatsList.addAll(callStats));
+ final Set<Integer> nativeTids = new HashSet<>();
+ bcs.setCallStatsObserver(new BinderInternal.CallStatsObserver() {
+ @Override
+ public void noteCallStats(int workSourceUid, long incrementalCallCount,
+ Collection<BinderCallsStats.CallStat> callStats) {
+ callStatsList.addAll(callStats);
+ }
+
+ @Override
+ public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
+ for (int tid : binderThreadNativeTids) {
+ nativeTids.add(tid);
+ }
+ }
+ });
Binder binder = new Binder();
+ bcs.nativeTid = 1000;
CallSession callSession = bcs.callStarted(binder, 1, WORKSOURCE_UID);
bcs.time += 10;
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
@@ -785,6 +801,7 @@ public class BinderCallsStatsTest {
bcs.time += 20;
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
+ bcs.nativeTid = 2000;
callSession = bcs.callStarted(binder, 2, WORKSOURCE_UID);
bcs.time += 30;
bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE, WORKSOURCE_UID);
@@ -809,6 +826,10 @@ public class BinderCallsStatsTest {
assertEquals(30, callStats.maxCpuTimeMicros);
}
}
+
+ assertEquals(2, nativeTids.size());
+ assertTrue(nativeTids.contains(1000));
+ assertTrue(nativeTids.contains(2000));
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 59148870fa3c..942045c8bf35 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -107,7 +107,7 @@ public class BinderDeathDispatcherTest {
if (!isAlive) {
return false;
}
- assertThat(mRecipient).isSameAs(recipient);
+ assertThat(mRecipient).isSameInstanceAs(recipient);
mRecipient = null;
return true;
}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
new file mode 100644
index 000000000000..b5720a262b3d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.internal.os;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleProcessCpuThreadReaderTest {
+
+ private File mProcDirectory;
+
+ @Before
+ public void setUp() {
+ Context context = InstrumentationRegistry.getContext();
+ mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtils.deleteContents(mProcDirectory);
+ }
+
+ @Test
+ public void getProcessCpuUsage() throws IOException {
+ setupDirectory(42,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ // Units are 10ms aka 10000Us
+ new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 600}},
+ new int[] {4500, 500});
+
+ KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42,
+ mProcDirectory.toPath());
+ KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
+ reader.getProcessCpuUsage(new int[] {2, 3});
+ assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(new long[] {2000, 13000});
+ assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(new long[] {1000, 9000});
+ assertThat(processCpuUsage.processCpuTimesMillis).isEqualTo(new long[] {6666, 43333});
+ }
+
+ @Test
+ public void getCpuFrequencyCount() throws IOException {
+ setupDirectory(13,
+ new int[] {13},
+ new int[] {1000, 2000, 3000},
+ new int[][] {{100, 200, 300}},
+ new int[] {14, 15});
+
+ KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13,
+ mProcDirectory.toPath());
+ int cpuFrequencyCount = reader.getCpuFrequencyCount();
+ assertThat(cpuFrequencyCount).isEqualTo(3);
+ }
+
+ private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies,
+ int[][] threadCpuTimes, int[] processCpuTimes)
+ throws IOException {
+
+ assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
+
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(
+ mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
+ for (int i = 0; i < cpuFrequencies.length; i++) {
+ final String line = cpuFrequencies[i] + " 0\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ }
+
+ Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
+
+ // Make /proc/$PID
+ assertTrue(processPath.toFile().mkdirs());
+
+ // Write /proc/$PID/stat. Only the fields 14-17 matter.
+ try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
+ timeInStateStream.write(
+ (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
+ + processCpuTimes[0] + " "
+ + processCpuTimes[1] + " "
+ + "16 17 18 19 20 ...").getBytes());
+ }
+
+ // Make /proc/$PID/task
+ final Path selfThreadsPath = processPath.resolve("task");
+ assertTrue(selfThreadsPath.toFile().mkdirs());
+
+ // Make thread directories
+ for (int i = 0; i < threadIds.length; i++) {
+ // Make /proc/$PID/task/$TID
+ final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
+ assertTrue(threadPath.toFile().mkdirs());
+
+ // Make /proc/$PID/task/$TID/time_in_state
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(threadPath.resolve("time_in_state"))) {
+ for (int j = 0; j < cpuFrequencies.length; j++) {
+ final String line = cpuFrequencies[j] + " " + threadCpuTimes[i][j] + "\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
index 10ba54865dbe..121c637310c6 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
@@ -55,82 +55,107 @@ public class SystemServerCpuThreadReaderTest {
@Test
public void testReaderDelta_firstTime() throws IOException {
- int uid = 42;
+ int pid = 42;
setupDirectory(
- mProcDirectory.toPath().resolve(String.valueOf(uid)),
- new int[]{42, 1, 2, 3},
- new int[]{1000, 2000},
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
// Units are 10ms aka 10000Us
- new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}});
+ new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
+ new int[] {1400, 1500});
SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
- mProcDirectory.toPath(), uid);
- reader.setBinderThreadNativeTids(new int[]{1, 3});
+ mProcDirectory.toPath(), pid);
+ reader.setBinderThreadNativeTids(new int[] {1, 3});
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
reader.readDelta();
- assertArrayEquals(new long[]{100 * 10000, 1100 * 10000},
+ assertArrayEquals(new long[] {100 * 10000, 1100 * 10000},
systemServiceCpuThreadTimes.threadCpuTimesUs);
- assertArrayEquals(new long[]{0, 600 * 10000},
+ assertArrayEquals(new long[] {0, 600 * 10000},
systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
}
@Test
public void testReaderDelta_nextTime() throws IOException {
- int uid = 42;
+ int pid = 42;
setupDirectory(
- mProcDirectory.toPath().resolve(String.valueOf(uid)),
- new int[]{42, 1, 2, 3},
- new int[]{1000, 2000},
- new int[][]{{100, 200}, {0, 200}, {0, 300}, {0, 400}});
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
+ new int[] {1400, 1500});
SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
- mProcDirectory.toPath(), uid);
- reader.setBinderThreadNativeTids(new int[]{1, 3});
+ mProcDirectory.toPath(), pid);
+ reader.setBinderThreadNativeTids(new int[] {1, 3});
// First time, populate "last" snapshot
reader.readDelta();
FileUtils.deleteContents(mProcDirectory);
setupDirectory(
- mProcDirectory.toPath().resolve(String.valueOf(uid)),
- new int[]{42, 1, 2, 3},
- new int[]{1000, 2000},
- new int[][]{{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}});
+ pid,
+ new int[] {42, 1, 2, 3},
+ new int[] {1000, 2000},
+ new int[][] {{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}},
+ new int[] {2400, 2500});
// Second time, get the actual delta
SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
reader.readDelta();
- assertArrayEquals(new long[]{3100 * 10000, 2500 * 10000},
+ assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000},
systemServiceCpuThreadTimes.threadCpuTimesUs);
- assertArrayEquals(new long[]{1800 * 10000, 1400 * 10000},
+ assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000},
systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
}
- private void setupDirectory(Path processPath, int[] threadIds, int[] cpuFrequencies,
- int[][] cpuTimes) throws IOException {
+ private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes,
+ int[] processCpuTimes)
+ throws IOException {
+
+ assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
+
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(
+ mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
+ for (int i = 0; i < cpuFrequencies.length; i++) {
+ final String line = cpuFrequencies[i] + " 0\n";
+ timeInStateStream.write(line.getBytes());
+ }
+ }
+
+ Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
// Make /proc/$PID
assertTrue(processPath.toFile().mkdirs());
+ // Write /proc/$PID/stat. Only the fields 14-17 matter.
+ try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
+ timeInStateStream.write(
+ (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
+ + processCpuTimes[0] + " "
+ + processCpuTimes[1] + " "
+ + "16 17 18 19 20 ...").getBytes());
+ }
+
// Make /proc/$PID/task
final Path selfThreadsPath = processPath.resolve("task");
assertTrue(selfThreadsPath.toFile().mkdirs());
- // Make thread directories in reverse order, as they are read in order of creation by
- // CpuThreadProcReader
+ // Make thread directories
for (int i = 0; i < threadIds.length; i++) {
// Make /proc/$PID/task/$TID
final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
assertTrue(threadPath.toFile().mkdirs());
// Make /proc/$PID/task/$TID/time_in_state
- final OutputStream timeInStateStream =
- Files.newOutputStream(threadPath.resolve("time_in_state"));
- for (int j = 0; j < cpuFrequencies.length; j++) {
- final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
- timeInStateStream.write(line.getBytes());
+ try (OutputStream timeInStateStream =
+ Files.newOutputStream(threadPath.resolve("time_in_state"))) {
+ for (int j = 0; j < cpuFrequencies.length; j++) {
+ final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
+ timeInStateStream.write(line.getBytes());
+ }
}
- timeInStateStream.close();
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index ac5443e1c7ce..bd0bbe99ee2f 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -43,18 +43,18 @@ public class SystemServicePowerCalculatorTest {
private PowerProfile mProfile;
private MockBatteryStatsImpl mMockBatteryStats;
private MockKernelCpuUidFreqTimeReader mMockCpuUidFreqTimeReader;
- private MockServerCpuThreadReader mMockServerCpuThreadReader;
+ private MockSystemServerCpuThreadReader mMockSystemServerCpuThreadReader;
private SystemServicePowerCalculator mSystemServicePowerCalculator;
@Before
public void setUp() throws IOException {
Context context = InstrumentationRegistry.getContext();
mProfile = new PowerProfile(context, true /* forTest */);
- mMockServerCpuThreadReader = new MockServerCpuThreadReader();
+ mMockSystemServerCpuThreadReader = new MockSystemServerCpuThreadReader();
mMockCpuUidFreqTimeReader = new MockKernelCpuUidFreqTimeReader();
mMockBatteryStats = new MockBatteryStatsImpl(new MockClocks())
.setPowerProfile(mProfile)
- .setSystemServerCpuThreadReader(mMockServerCpuThreadReader)
+ .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
.setKernelCpuUidFreqTimeReader(mMockCpuUidFreqTimeReader)
.setUserInfoProvider(new MockUserInfoProvider());
mMockBatteryStats.getOnBatteryTimeBase().setRunning(true, 0, 0);
@@ -65,12 +65,13 @@ public class SystemServicePowerCalculatorTest {
@Test
public void testCalculateApp() {
// Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
- mMockServerCpuThreadReader.setThreadTimes(
- new long[]{30000, 40000, 50000, 60000, 70000, 80000, 90000},
- new long[]{20000, 30000, 40000, 50000, 60000, 70000, 80000});
+ mMockSystemServerCpuThreadReader.setCpuTimes(
+ new long[] {10000, 15000, 20000, 25000, 30000, 35000, 40000},
+ new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
+ new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
mMockCpuUidFreqTimeReader.setSystemServerCpuTimes(
- new long[]{10000, 20000, 30000, 40000, 50000, 60000, 70000}
+ new long[] {10000, 20000, 30000, 40000, 50000, 60000, 70000}
);
mMockBatteryStats.readKernelUidCpuFreqTimesLocked(null, true, false);
@@ -87,7 +88,7 @@ public class SystemServicePowerCalculatorTest {
stat1.cpuTimeMicros = 1000000;
callStats.add(stat1);
- mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats, null);
+ mMockBatteryStats.noteBinderCallStats(workSourceUid1, 100, callStats);
callStats.clear();
BinderCallsStats.CallStat stat2 = new BinderCallsStats.CallStat(workSourceUid2,
@@ -97,7 +98,7 @@ public class SystemServicePowerCalculatorTest {
stat2.cpuTimeMicros = 9000000;
callStats.add(stat2);
- mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats, null);
+ mMockBatteryStats.noteBinderCallStats(workSourceUid2, 100, callStats);
mMockBatteryStats.updateSystemServiceCallStats();
mMockBatteryStats.updateSystemServerThreadStats();
@@ -106,13 +107,13 @@ public class SystemServicePowerCalculatorTest {
mMockBatteryStats.getUidStatsLocked(workSourceUid1), 0);
mSystemServicePowerCalculator.calculateApp(app1, app1.uidObj, 0, 0,
BatteryStats.STATS_SINCE_CHARGED);
- assertEquals(0.27162, app1.systemServiceCpuPowerMah, 0.00001);
+ assertEquals(0.00016269, app1.systemServiceCpuPowerMah, 0.0000001);
BatterySipper app2 = new BatterySipper(BatterySipper.DrainType.APP,
mMockBatteryStats.getUidStatsLocked(workSourceUid2), 0);
mSystemServicePowerCalculator.calculateApp(app2, app2.uidObj, 0, 0,
BatteryStats.STATS_SINCE_CHARGED);
- assertEquals(2.44458, app2.systemServiceCpuPowerMah, 0.00001);
+ assertEquals(0.00146426, app2.systemServiceCpuPowerMah, 0.0000001);
}
private static class MockKernelCpuUidFreqTimeReader extends
@@ -140,14 +141,16 @@ public class SystemServicePowerCalculatorTest {
}
}
- private static class MockServerCpuThreadReader extends SystemServerCpuThreadReader {
+ private static class MockSystemServerCpuThreadReader extends SystemServerCpuThreadReader {
private SystemServiceCpuThreadTimes mThreadTimes = new SystemServiceCpuThreadTimes();
- MockServerCpuThreadReader() {
+ MockSystemServerCpuThreadReader() {
super(null);
}
- public void setThreadTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
+ public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs,
+ long[] binderThreadCpuTimesUs) {
+ mThreadTimes.processCpuTimesUs = processCpuTimesUs;
mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 9f68ef31c166..7eca320d4aeb 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -74,7 +74,7 @@ public class RegisterStatusBarResultTest {
assertThat(copy.mImeBackDisposition).isEqualTo(original.mImeBackDisposition);
assertThat(copy.mShowImeSwitcher).isEqualTo(original.mShowImeSwitcher);
assertThat(copy.mDisabledFlags2).isEqualTo(original.mDisabledFlags2);
- assertThat(copy.mImeToken).isSameAs(original.mImeToken);
+ assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
assertThat(copy.mAppFullscreen).isEqualTo(original.mAppFullscreen);
assertThat(copy.mAppImmersive).isEqualTo(original.mAppImmersive);
diff --git a/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java
new file mode 100644
index 000000000000..88bbcc29e71a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/view/RecyclerViewCaptureHelperTest.java
@@ -0,0 +1,343 @@
+/*
+ * 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.internal.view;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.view.ScrollCaptureViewHelper.ScrollResult;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+
+import com.google.common.truth.Truth;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class RecyclerViewCaptureHelperTest {
+ private static final int CHILD_VIEWS = 12;
+ private static final int CHILD_VIEW_HEIGHT = 300;
+ private static final int WINDOW_WIDTH = 800;
+ private static final int WINDOW_HEIGHT = 1200;
+ private static final int CAPTURE_HEIGHT = 600;
+
+ private FrameLayout mParent;
+ private RecyclerView mTarget;
+ private WindowManager mWm;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ private Context mContext;
+ private float mDensity;
+ private LinearLayoutManager mLinearLayoutManager;
+ private Instrumentation mInstrumentation;
+
+ @Before
+ @UiThreadTest
+ public void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+ mDensity = mContext.getResources().getDisplayMetrics().density;
+
+ mParent = new FrameLayout(mContext);
+
+ mTarget = new RecyclerView(mContext);
+ mParent.addView(mTarget, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ mTarget.setAdapter(new TestAdapter());
+ mLinearLayoutManager =
+ new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false);
+ mTarget.setLayoutManager(mLinearLayoutManager);
+ mWm = mContext.getSystemService(WindowManager.class);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(WINDOW_WIDTH, WINDOW_HEIGHT,
+ TYPE_APPLICATION_OVERLAY, FLAG_NOT_TOUCHABLE, PixelFormat.OPAQUE);
+ mWindowLayoutParams.setTitle("ScrollViewCaptureHelper");
+ mWindowLayoutParams.gravity = Gravity.CENTER;
+ mWm.addView(mParent, mWindowLayoutParams);
+ }
+
+ @After
+ @UiThreadTest
+ public void tearDown() {
+ mWm.removeViewImmediate(mParent);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromTop() {
+ mTarget.scrollBy(0, -(WINDOW_HEIGHT * 3));
+ // mTarget.createSnapshot(new ViewDebug.HardwareCanvasProvider(), false);
+
+ RecyclerViewCaptureHelper rvc = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = rvc.onComputeScrollBounds(mTarget);
+ rvc.onPrepareForStart(mTarget, scrollBounds);
+
+ assertThat(scrollBounds.height()).isGreaterThan(CAPTURE_HEIGHT);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+ ScrollResult scrollResult = rvc.onScrollRequested(mTarget,
+ scrollBounds, request);
+
+ // The result is an empty rectangle and no scrolling, since it
+ // is not possible to physically scroll further up to make the
+ // requested area visible at all (it doesn't exist).
+ assertEmpty(scrollResult.availableArea);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromTop() {
+ mTarget.scrollBy(0, -(WINDOW_HEIGHT * 3));
+
+ RecyclerViewCaptureHelper rvc = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = rvc.onComputeScrollBounds(mTarget);
+ rvc.onPrepareForStart(mTarget, scrollBounds);
+
+ assertThat(scrollBounds.height()).isGreaterThan(CAPTURE_HEIGHT);
+
+ // Capture between y = +1200 to +1800 pixels BELOW current top
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ ScrollResult scrollResult = rvc.onScrollRequested(mTarget, scrollBounds, request);
+ assertThat(request).isEqualTo(scrollResult.requestedArea);
+ assertThat(request).isEqualTo(scrollResult.availableArea);
+ assertThat(scrollResult.scrollDelta).isEqualTo(CAPTURE_HEIGHT);
+ assertAvailableAreaCompletelyVisible(scrollResult, mTarget);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromMiddle() {
+ mTarget.scrollBy(0, WINDOW_HEIGHT);
+
+ RecyclerViewCaptureHelper helper = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = helper.onComputeScrollBounds(mTarget);
+ helper.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+ ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request);
+ assertThat(request).isEqualTo(scrollResult.requestedArea);
+ assertThat(request).isEqualTo(scrollResult.availableArea);
+ assertThat(scrollResult.scrollDelta).isEqualTo(-CAPTURE_HEIGHT);
+ assertAvailableAreaCompletelyVisible(scrollResult, mTarget);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromMiddle() {
+ mTarget.scrollBy(0, WINDOW_HEIGHT);
+
+ RecyclerViewCaptureHelper helper = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = helper.onComputeScrollBounds(mTarget);
+ helper.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request);
+ assertThat(request).isEqualTo(scrollResult.requestedArea);
+ assertThat(request).isEqualTo(scrollResult.availableArea);
+ assertThat(scrollResult.scrollDelta).isEqualTo(CAPTURE_HEIGHT);
+ assertAvailableAreaCompletelyVisible(scrollResult, mTarget);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromBottom() {
+ mTarget.scrollBy(0, WINDOW_HEIGHT * 2);
+
+ RecyclerViewCaptureHelper helper = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = helper.onComputeScrollBounds(mTarget);
+ helper.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+ ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request);
+ assertThat(request).isEqualTo(scrollResult.requestedArea);
+ assertThat(request).isEqualTo(scrollResult.availableArea);
+ assertThat(scrollResult.scrollDelta).isEqualTo(-CAPTURE_HEIGHT);
+ assertAvailableAreaCompletelyVisible(scrollResult, mTarget);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromBottom() {
+ mTarget.scrollBy(0, WINDOW_HEIGHT * 3);
+
+ RecyclerViewCaptureHelper rvc = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = rvc.onComputeScrollBounds(mTarget);
+ rvc.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ ScrollResult scrollResult = rvc.onScrollRequested(mTarget,
+ scrollBounds, request);
+ Truth.assertThat(request).isEqualTo(scrollResult.requestedArea);
+
+ // The result is an empty rectangle and no scrolling, since it
+ // is not possible to physically scroll further down to make the
+ // requested area visible at all (it doesn't exist).
+ assertEmpty(scrollResult.availableArea);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_offTopEdge() {
+ mTarget.scrollBy(0, -(WINDOW_HEIGHT * 3));
+
+ RecyclerViewCaptureHelper helper = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = helper.onComputeScrollBounds(mTarget);
+ helper.onPrepareForStart(mTarget, scrollBounds);
+
+ // Create a request which lands halfway off the top of the content
+ //from -1500 to -900, (starting at 1200 = -300 to +300 within the content)
+ int top = 0;
+ Rect request = new Rect(
+ 0, top - (CAPTURE_HEIGHT / 2),
+ scrollBounds.width(), top + (CAPTURE_HEIGHT / 2));
+
+ ScrollResult scrollResult = helper.onScrollRequested(mTarget, scrollBounds, request);
+ assertThat(request).isEqualTo(scrollResult.requestedArea);
+
+ ScrollResult result = helper.onScrollRequested(mTarget, scrollBounds, request);
+ // The result is a partial result
+ Rect expectedResult = new Rect(request);
+ expectedResult.top += (CAPTURE_HEIGHT / 2); // top half clipped
+ assertThat(expectedResult).isEqualTo(result.availableArea);
+ assertThat(scrollResult.scrollDelta).isEqualTo(0);
+ assertAvailableAreaPartiallyVisible(scrollResult, mTarget);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_offBottomEdge() {
+ mTarget.scrollBy(0, WINDOW_HEIGHT * 2);
+
+ RecyclerViewCaptureHelper helper = new RecyclerViewCaptureHelper();
+ Rect scrollBounds = helper.onComputeScrollBounds(mTarget);
+ helper.onPrepareForStart(mTarget, scrollBounds);
+
+ // Create a request which lands halfway off the bottom of the content
+ //from 600 to to 1200, (starting at 2400 = 3000 to 3600 within the content)
+
+ int bottom = WINDOW_HEIGHT;
+ Rect request = new Rect(
+ 0, bottom - (CAPTURE_HEIGHT / 2),
+ scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2));
+
+ ScrollResult result = helper.onScrollRequested(mTarget, scrollBounds, request);
+
+ Rect expectedResult = new Rect(request);
+ expectedResult.bottom -= 300; // bottom half clipped
+ assertThat(expectedResult).isEqualTo(result.availableArea);
+ assertThat(result.scrollDelta).isEqualTo(0);
+ assertAvailableAreaPartiallyVisible(result, mTarget);
+ }
+
+ static final class TestViewHolder extends RecyclerView.ViewHolder {
+ TestViewHolder(View itemView) {
+ super(itemView);
+ }
+ }
+
+ static final class TestAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private final Random mRandom = new Random();
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new TestViewHolder(new TextView(parent.getContext()));
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ TextView view = (TextView) holder.itemView;
+ view.setText("Child #" + position);
+ view.setTextColor(Color.WHITE);
+ view.setTextSize(30f);
+ view.setBackgroundColor(Color.rgb(mRandom.nextFloat(), mRandom.nextFloat(),
+ mRandom.nextFloat()));
+ view.setMinHeight(CHILD_VIEW_HEIGHT);
+ }
+
+ @Override
+ public int getItemCount() {
+ return CHILD_VIEWS;
+ }
+ }
+
+ static void assertEmpty(Rect r) {
+ if (r != null && !r.isEmpty()) {
+ fail("Not true that " + r + " is empty");
+ }
+ }
+
+ static Rect getVisibleRect(View v) {
+ Rect r = new Rect(0, 0, v.getWidth(), v.getHeight());
+ v.getLocalVisibleRect(r);
+ return r;
+ }
+
+ static void assertAvailableAreaCompletelyVisible(ScrollResult result, View container) {
+ Rect requested = new Rect(result.availableArea);
+ requested.offset(0, -result.scrollDelta); // make relative
+ Rect localVisible = getVisibleRect(container);
+ if (!localVisible.contains(requested)) {
+ fail("Not true that all of " + requested + " is contained by " + localVisible);
+ }
+ }
+
+ static void assertAvailableAreaPartiallyVisible(ScrollResult result, View container) {
+ Rect requested = new Rect(result.availableArea);
+ requested.offset(0, -result.scrollDelta); // make relative
+ Rect localVisible = getVisibleRect(container);
+ if (!Rect.intersects(localVisible, requested)) {
+ fail("Not true that any of " + requested + " is contained by " + localVisible);
+ }
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 9d95de7eb60f..d266cdbccf79 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -85,7 +85,7 @@ import java.util.concurrent.TimeUnit;
@MediumTest
@Presubmit
public class ActivityThreadClientTest {
- private static final long WAIT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+ private static final long WAIT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
@Test
@UiThreadTest
@@ -243,27 +243,27 @@ public class ActivityThreadClientTest {
}
private void startActivity(ActivityClientRecord r) {
- mThread.handleStartActivity(r.token, null /* pendingActions */);
+ mThread.handleStartActivity(r, null /* pendingActions */);
}
private void resumeActivity(ActivityClientRecord r) {
- mThread.handleResumeActivity(r.token, true /* finalStateRequest */,
+ mThread.handleResumeActivity(r, true /* finalStateRequest */,
true /* isForward */, "test");
}
private void pauseActivity(ActivityClientRecord r) {
- mThread.handlePauseActivity(r.token, false /* finished */,
+ mThread.handlePauseActivity(r, false /* finished */,
false /* userLeaving */, 0 /* configChanges */, null /* pendingActions */,
"test");
}
private void stopActivity(ActivityClientRecord r) {
- mThread.handleStopActivity(r.token, 0 /* configChanges */,
+ mThread.handleStopActivity(r, 0 /* configChanges */,
new PendingTransactionActions(), false /* finalStateRequest */, "test");
}
private void destroyActivity(ActivityClientRecord r) {
- mThread.handleDestroyActivity(r.token, true /* finishing */, 0 /* configChanges */,
+ mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
false /* getNonConfigInstance */, "test");
}
diff --git a/core/tests/powertests/PowerStatsViewer/Android.bp b/core/tests/powertests/PowerStatsViewer/Android.bp
new file mode 100644
index 000000000000..a3dc4fb4ff74
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/Android.bp
@@ -0,0 +1,13 @@
+android_test {
+ name: "PowerStatsViewer",
+ srcs: ["src/**/*.java"],
+ defaults: ["SettingsLibDefaults"],
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "androidx.cardview_cardview",
+ "androidx.recyclerview_recyclerview",
+ "com.google.android.material_material",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml b/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
new file mode 100644
index 000000000000..378d0350e347
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.core.powerstatsviewer"
+ android:sharedUserId="android.uid.system">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.BATTERY_STATS"/>
+
+ <application
+ android:theme="@style/Theme"
+ android:label="Power Stats Viewer">
+ <activity android:name=".PowerStatsViewerActivity"
+ android:label="Power Stats Viewer"
+ android:launchMode="singleTop"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".AppPickerActivity"
+ android:label="Power Stats - Select an App"/>
+
+ </application>
+</manifest>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml
new file mode 100644
index 000000000000..fe6fe2d41155
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/app_info_layout.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/secondary_app_icon_size"
+ android:layout_height="@dimen/secondary_app_icon_size"
+ android:layout_marginEnd="12dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="4dp"
+ android:gravity="start|center_vertical"/>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ <TextView
+ android:id="@+id/uid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"/>
+
+ <TextView
+ android:id="@+id/packages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textDirection="locale"
+ android:maxLines="3"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"/>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/power_mah"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginStart="16dp"
+ android:gravity="right"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:visibility="gone"/>
+
+</LinearLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml
new file mode 100644
index 000000000000..6f289996f982
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/app_picker_layout.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/app_list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+
+ <ProgressBar
+ style="?android:attr/progressBarStyleLarge"
+ android:id="@+id/loading_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"/>
+</FrameLayout> \ No newline at end of file
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml
new file mode 100644
index 000000000000..1ced825adf31
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_entry_layout.xml
@@ -0,0 +1,49 @@
+<?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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearanceBody"/>
+
+ <TextView
+ android:id="@+id/amount"
+ android:layout_width="0dp"
+ android:layout_weight="0.7"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:textAppearance="@style/TextAppearanceBody"/>
+
+ <TextView
+ android:id="@+id/percent"
+ android:layout_width="64dp"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:textAppearance="@style/TextAppearanceBody"/>
+</LinearLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml
new file mode 100644
index 000000000000..99494188f422
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/layout/power_stats_viewer_layout.xml
@@ -0,0 +1,77 @@
+<?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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.cardview.widget.CardView
+ style="@style/LoadTestCardView"
+ android:id="@+id/app_card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="10dp"
+ android:layout_marginStart="10dp"
+ android:padding="20dp">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:minHeight="80dp"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp">
+
+ <include layout="@layout/app_info_layout"/>
+
+ </LinearLayout>
+ </androidx.cardview.widget.CardView>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/power_stats_data_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:id="@+id/empty_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:visibility="gone"
+ android:text="No power stats available"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/loading_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#AAFFFFFF">
+ <ProgressBar
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"/>
+ </FrameLayout>
+</FrameLayout>
diff --git a/core/tests/powertests/PowerStatsViewer/res/values/styles.xml b/core/tests/powertests/PowerStatsViewer/res/values/styles.xml
new file mode 100644
index 000000000000..629d729e7b9a
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/res/values/styles.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<resources>
+ <style name="Theme" parent="Theme.MaterialComponents.Light">
+ <item name="colorPrimary">#34a853</item>
+ <item name="android:windowActionBar">true</item>
+ <item name="android:windowNoTitle">false</item>
+ </style>
+
+ <style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
+ <item name="cardBackgroundColor">#ceead6</item>
+ </style>
+
+ <style name="TextAppearanceBody" parent="android:TextAppearance.DeviceDefault">
+ <item name="android:textColor">#000000</item>
+ <item name="android:textSize">18sp</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java
new file mode 100644
index 000000000000..85265613928f
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppInfoHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import com.android.internal.os.BatterySipper;
+
+class AppInfoHelper {
+
+ private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
+
+ public static class AppInfo {
+ public int uid;
+ public CharSequence label;
+ public double powerMah;
+ public ApplicationInfo iconInfo;
+ public CharSequence packages;
+ }
+
+ @Nullable
+ public static AppInfo makeApplicationInfo(PackageManager packageManager, int uid,
+ @Nullable BatterySipper sipper) {
+ if (sipper != null && sipper.drainType != BatterySipper.DrainType.APP) {
+ return null;
+ }
+
+ String packageWithHighestDrain = null;
+
+ AppInfo info = new AppInfo();
+ info.uid = uid;
+ if (sipper != null) {
+ sipper.sumPower();
+ info.powerMah = sipper.totalSmearedPowerMah;
+ packageWithHighestDrain = sipper.packageWithHighestDrain;
+ }
+ if (info.uid == Process.ROOT_UID) {
+ info.label = "<root>";
+ } else {
+ String[] packages = packageManager.getPackagesForUid(info.uid);
+ String primaryPackageName = null;
+ if (info.uid == Process.SYSTEM_UID) {
+ primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
+ } else if (packages != null) {
+ for (String name : packages) {
+ primaryPackageName = name;
+ if (name.equals(packageWithHighestDrain)) {
+ break;
+ }
+ }
+ }
+
+ if (primaryPackageName != null) {
+ try {
+ ApplicationInfo applicationInfo =
+ packageManager.getApplicationInfo(primaryPackageName, 0);
+ info.label = applicationInfo.loadLabel(packageManager);
+ info.iconInfo = applicationInfo;
+ } catch (PackageManager.NameNotFoundException e) {
+ info.label = primaryPackageName;
+ }
+ } else if (packageWithHighestDrain != null) {
+ info.label = packageWithHighestDrain;
+ }
+
+ if (packages != null && packages.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ if (primaryPackageName != null) {
+ sb.append(primaryPackageName);
+ }
+ for (String packageName : packages) {
+ if (packageName.equals(primaryPackageName)) {
+ continue;
+ }
+
+ if (sb.length() != 0) {
+ sb.append(", ");
+ }
+ sb.append(packageName);
+ }
+
+ info.packages = sb;
+ }
+ }
+
+ // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
+ if (info.iconInfo == null) {
+ try {
+ info.iconInfo =
+ packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
+ } catch (PackageManager.NameNotFoundException nameNotFoundException) {
+ // Won't happen
+ }
+ }
+ return info;
+ }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java
new file mode 100644
index 000000000000..b4fc73c0f744
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/AppPickerActivity.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2008 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.activity.ComponentActivity;
+import androidx.activity.result.contract.ActivityResultContract;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.frameworks.core.powerstatsviewer.AppInfoHelper.AppInfo;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Picker, showing a sorted list of applications consuming power. Returns the selected
+ * application UID or Process.INVALID_UID.
+ */
+public class AppPickerActivity extends ComponentActivity {
+ private static final String TAG = "AppPicker";
+
+ public static final ActivityResultContract<Void, Integer> CONTRACT =
+ new ActivityResultContract<Void, Integer>() {
+ @NonNull
+ @Override
+ public Intent createIntent(@NonNull Context context, Void aVoid) {
+ return new Intent(context, AppPickerActivity.class);
+ }
+
+ @Override
+ public Integer parseResult(int resultCode, @Nullable Intent intent) {
+ if (resultCode != RESULT_OK || intent == null) {
+ return Process.INVALID_UID;
+ }
+ return intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+ }
+ };
+
+ private AppListAdapter mAppListAdapter;
+ private RecyclerView mAppList;
+ private View mLoadingView;
+
+ private interface OnAppSelectedListener {
+ void onAppSelected(int uid);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.app_picker_layout);
+
+ mLoadingView = findViewById(R.id.loading_view);
+
+ mAppList = findViewById(R.id.app_list_view);
+ mAppList.setLayoutManager(new LinearLayoutManager(this));
+ mAppListAdapter = new AppListAdapter(AppPickerActivity.this::setSelectedUid);
+ mAppList.setAdapter(mAppListAdapter);
+
+ LoaderManager.getInstance(this).initLoader(0, null,
+ new AppListLoaderCallbacks());
+ }
+
+ protected void setSelectedUid(int uid) {
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ onBackPressed();
+ return true;
+ }
+
+ private static class AppListLoader extends AsyncLoaderCompat<List<AppInfo>> {
+ private final BatteryStatsHelper mStatsHelper;
+ private final UserManager mUserManager;
+ private final PackageManager mPackageManager;
+
+ AppListLoader(Context context) {
+ super(context);
+ mUserManager = context.getSystemService(UserManager.class);
+ mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
+ mStatsHelper.create((Bundle) null);
+ mStatsHelper.clearStats();
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public List<AppInfo> loadInBackground() {
+ List<AppInfo> applicationList = new ArrayList<>();
+
+ mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
+ mUserManager.getUserProfiles());
+
+ final List<BatterySipper> usageList = mStatsHelper.getUsageList();
+ for (BatterySipper sipper : usageList) {
+ AppInfo info =
+ AppInfoHelper.makeApplicationInfo(mPackageManager, sipper.getUid(), sipper);
+ if (info != null) {
+ applicationList.add(info);
+ }
+ }
+
+ applicationList.sort(
+ Comparator.comparing((AppInfo a) -> a.powerMah).reversed());
+ return applicationList;
+ }
+
+ @Override
+ protected void onDiscardResult(List<AppInfo> result) {
+ }
+ }
+
+ private class AppListLoaderCallbacks implements
+ LoaderManager.LoaderCallbacks<List<AppInfo>> {
+
+ @NonNull
+ @Override
+ public Loader<List<AppInfo>> onCreateLoader(int id, Bundle args) {
+ return new AppListLoader(AppPickerActivity.this);
+ }
+
+ @Override
+ public void onLoadFinished(@NonNull Loader<List<AppInfo>> loader,
+ List<AppInfo> applicationList) {
+ mAppListAdapter.setApplicationList(applicationList);
+ mAppList.setVisibility(View.VISIBLE);
+ mLoadingView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(@NonNull Loader<List<AppInfo>> loader) {
+ }
+ }
+
+ public class AppListAdapter extends RecyclerView.Adapter<AppViewHolder> {
+ private final OnAppSelectedListener mListener;
+ private List<AppInfo> mApplicationList;
+
+ public AppListAdapter(OnAppSelectedListener listener) {
+ mListener = listener;
+ }
+
+ void setApplicationList(List<AppInfo> applicationList) {
+ mApplicationList = applicationList;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mApplicationList.size();
+ }
+
+ @NonNull
+ @Override
+ public AppViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
+ View view = layoutInflater.inflate(R.layout.app_info_layout, viewGroup, false);
+ return new AppViewHolder(view, mListener);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AppViewHolder appViewHolder, int position) {
+ AppInfo item = mApplicationList.get(position);
+ appViewHolder.uid = item.uid;
+ appViewHolder.titleView.setText(item.label);
+ appViewHolder.uidView.setText(
+ String.format(Locale.getDefault(), "UID: %d", item.uid));
+ appViewHolder.powerView.setText(
+ String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
+ appViewHolder.iconView.setImageDrawable(
+ item.iconInfo.loadIcon(getPackageManager()));
+ if (item.packages != null) {
+ appViewHolder.packagesView.setText(item.packages);
+ appViewHolder.packagesView.setVisibility(View.VISIBLE);
+ } else {
+ appViewHolder.packagesView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ // View Holder used when displaying apps
+ public static class AppViewHolder extends RecyclerView.ViewHolder
+ implements View.OnClickListener {
+ private final OnAppSelectedListener mListener;
+
+ public int uid;
+ public TextView titleView;
+ public TextView uidView;
+ public ImageView iconView;
+ public TextView packagesView;
+ public TextView powerView;
+
+ AppViewHolder(View view, OnAppSelectedListener listener) {
+ super(view);
+ mListener = listener;
+ view.setOnClickListener(this);
+ titleView = view.findViewById(android.R.id.title);
+ uidView = view.findViewById(R.id.uid);
+ iconView = view.findViewById(android.R.id.icon);
+ packagesView = view.findViewById(R.id.packages);
+ powerView = view.findViewById(R.id.power_mah);
+ powerView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mListener.onAppSelected(uid);
+ }
+ }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java
new file mode 100644
index 000000000000..09f20baf499c
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsData.java
@@ -0,0 +1,240 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.os.Process;
+
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PowerStatsData {
+ private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
+ private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
+ private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
+ private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
+ PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
+
+ enum EntryType {
+ POWER,
+ DURATION,
+ }
+
+ public static class Entry {
+ public String title;
+ public EntryType entryType;
+ public double value;
+ public double total;
+ }
+
+ private final AppInfoHelper.AppInfo mAppInfo;
+ private final List<Entry> mEntries = new ArrayList<>();
+
+ public PowerStatsData(Context context, BatteryStatsHelper batteryStatsHelper,
+ int uid) {
+ List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
+
+ double totalPowerMah = 0;
+ double totalSmearedPowerMah = 0;
+ double totalPowerExcludeSystemMah = 0;
+ double totalScreenPower = 0;
+ double totalProportionalSmearMah = 0;
+ double totalCpuPowerMah = 0;
+ double totalSystemServiceCpuPowerMah = 0;
+ double totalUsagePowerMah = 0;
+ double totalWakeLockPowerMah = 0;
+ double totalMobileRadioPowerMah = 0;
+ double totalWifiPowerMah = 0;
+ double totalBluetoothPowerMah = 0;
+ double totalGpsPowerMah = 0;
+ double totalCameraPowerMah = 0;
+ double totalFlashlightPowerMah = 0;
+ double totalSensorPowerMah = 0;
+ double totalAudioPowerMah = 0;
+ double totalVideoPowerMah = 0;
+
+ long totalCpuTimeMs = 0;
+ long totalCpuFgTimeMs = 0;
+ long totalWakeLockTimeMs = 0;
+ long totalWifiRunningTimeMs = 0;
+ long totalBluetoothRunningTimeMs = 0;
+ long totalGpsTimeMs = 0;
+ long totalCameraTimeMs = 0;
+ long totalFlashlightTimeMs = 0;
+ long totalAudioTimeMs = 0;
+ long totalVideoTimeMs = 0;
+
+ BatterySipper uidSipper = null;
+ for (BatterySipper sipper : usageList) {
+ if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
+ totalScreenPower = sipper.sumPower();
+ }
+
+ if (isHiddenDrainType(sipper.drainType)) {
+ continue;
+ }
+
+ if (sipper.drainType == BatterySipper.DrainType.APP && sipper.getUid() == uid) {
+ uidSipper = sipper;
+ }
+
+ totalPowerMah += sipper.sumPower();
+ totalSmearedPowerMah += sipper.totalSmearedPowerMah;
+ totalProportionalSmearMah += sipper.proportionalSmearMah;
+
+ if (!isSystemSipper(sipper)) {
+ totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
+ }
+
+ totalCpuPowerMah += sipper.cpuPowerMah;
+ totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
+ totalUsagePowerMah += sipper.usagePowerMah;
+ totalWakeLockPowerMah += sipper.wakeLockPowerMah;
+ totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
+ totalWifiPowerMah += sipper.wifiPowerMah;
+ totalBluetoothPowerMah += sipper.bluetoothPowerMah;
+ totalGpsPowerMah += sipper.gpsPowerMah;
+ totalCameraPowerMah += sipper.cameraPowerMah;
+ totalFlashlightPowerMah += sipper.flashlightPowerMah;
+ totalSensorPowerMah += sipper.sensorPowerMah;
+ totalAudioPowerMah += sipper.audioPowerMah;
+ totalVideoPowerMah += sipper.videoPowerMah;
+
+ totalCpuTimeMs += sipper.cpuTimeMs;
+ totalCpuFgTimeMs += sipper.cpuFgTimeMs;
+ totalWakeLockTimeMs += sipper.wakeLockTimeMs;
+ totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
+ totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
+ totalGpsTimeMs += sipper.gpsTimeMs;
+ totalCameraTimeMs += sipper.cameraTimeMs;
+ totalFlashlightTimeMs += sipper.flashlightTimeMs;
+ totalAudioTimeMs += sipper.audioTimeMs;
+ totalVideoTimeMs += sipper.videoTimeMs;
+ }
+
+ mAppInfo = AppInfoHelper.makeApplicationInfo(context.getPackageManager(), uid, uidSipper);
+
+ if (uidSipper == null) {
+ return;
+ }
+
+ addEntry("Total power", EntryType.POWER,
+ uidSipper.totalSmearedPowerMah, totalSmearedPowerMah);
+ addEntry("... excluding system", EntryType.POWER,
+ uidSipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
+ addEntry("Screen, smeared", EntryType.POWER,
+ uidSipper.screenPowerMah, totalScreenPower);
+ addEntry("Other, smeared", EntryType.POWER,
+ uidSipper.proportionalSmearMah, totalProportionalSmearMah);
+ addEntry("Excluding smeared", EntryType.POWER,
+ uidSipper.totalPowerMah, totalPowerMah);
+ addEntry("CPU", EntryType.POWER,
+ uidSipper.cpuPowerMah, totalCpuPowerMah);
+ addEntry("System services", EntryType.POWER,
+ uidSipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
+ addEntry("RAM", EntryType.POWER,
+ uidSipper.usagePowerMah, totalUsagePowerMah);
+ addEntry("Wake lock", EntryType.POWER,
+ uidSipper.wakeLockPowerMah, totalWakeLockPowerMah);
+ addEntry("Mobile radio", EntryType.POWER,
+ uidSipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
+ addEntry("WiFi", EntryType.POWER,
+ uidSipper.wifiPowerMah, totalWifiPowerMah);
+ addEntry("Bluetooth", EntryType.POWER,
+ uidSipper.bluetoothPowerMah, totalBluetoothPowerMah);
+ addEntry("GPS", EntryType.POWER,
+ uidSipper.gpsPowerMah, totalGpsPowerMah);
+ addEntry("Camera", EntryType.POWER,
+ uidSipper.cameraPowerMah, totalCameraPowerMah);
+ addEntry("Flashlight", EntryType.POWER,
+ uidSipper.flashlightPowerMah, totalFlashlightPowerMah);
+ addEntry("Sensors", EntryType.POWER,
+ uidSipper.sensorPowerMah, totalSensorPowerMah);
+ addEntry("Audio", EntryType.POWER,
+ uidSipper.audioPowerMah, totalAudioPowerMah);
+ addEntry("Video", EntryType.POWER,
+ uidSipper.videoPowerMah, totalVideoPowerMah);
+
+ addEntry("CPU time", EntryType.DURATION,
+ uidSipper.cpuTimeMs, totalCpuTimeMs);
+ addEntry("CPU foreground time", EntryType.DURATION,
+ uidSipper.cpuFgTimeMs, totalCpuFgTimeMs);
+ addEntry("Wake lock time", EntryType.DURATION,
+ uidSipper.wakeLockTimeMs, totalWakeLockTimeMs);
+ addEntry("WiFi running time", EntryType.DURATION,
+ uidSipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
+ addEntry("Bluetooth time", EntryType.DURATION,
+ uidSipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
+ addEntry("GPS time", EntryType.DURATION,
+ uidSipper.gpsTimeMs, totalGpsTimeMs);
+ addEntry("Camera time", EntryType.DURATION,
+ uidSipper.cameraTimeMs, totalCameraTimeMs);
+ addEntry("Flashlight time", EntryType.DURATION,
+ uidSipper.flashlightTimeMs, totalFlashlightTimeMs);
+ addEntry("Audio time", EntryType.DURATION,
+ uidSipper.audioTimeMs, totalAudioTimeMs);
+ addEntry("Video time", EntryType.DURATION,
+ uidSipper.videoTimeMs, totalVideoTimeMs);
+ }
+
+ protected boolean isHiddenDrainType(BatterySipper.DrainType drainType) {
+ return drainType == BatterySipper.DrainType.IDLE
+ || drainType == BatterySipper.DrainType.CELL
+ || drainType == BatterySipper.DrainType.SCREEN
+ || drainType == BatterySipper.DrainType.UNACCOUNTED
+ || drainType == BatterySipper.DrainType.OVERCOUNTED
+ || drainType == BatterySipper.DrainType.BLUETOOTH
+ || drainType == BatterySipper.DrainType.WIFI;
+ }
+
+ private boolean isSystemSipper(BatterySipper sipper) {
+ final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
+ if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
+ return true;
+ } else if (sipper.mPackages != null) {
+ for (final String packageName : sipper.mPackages) {
+ for (final String systemPackage : PACKAGES_SYSTEM) {
+ if (systemPackage.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void addEntry(String title, EntryType entryType, double amount, double totalAmount) {
+ Entry entry = new Entry();
+ entry.title = title;
+ entry.entryType = entryType;
+ entry.value = amount;
+ entry.total = totalAmount;
+ mEntries.add(entry);
+ }
+
+ public AppInfoHelper.AppInfo getAppInfo() {
+ return mAppInfo;
+ }
+
+ public List<Entry> getEntries() {
+ return mEntries;
+ }
+}
diff --git a/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java
new file mode 100644
index 000000000000..1605e9cf1b76
--- /dev/null
+++ b/core/tests/powertests/PowerStatsViewer/src/com/android/frameworks/core/powerstatsviewer/PowerStatsViewerActivity.java
@@ -0,0 +1,263 @@
+/*
+ * 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.frameworks.core.powerstatsviewer;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.activity.ComponentActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.app.LoaderManager.LoaderCallbacks;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class PowerStatsViewerActivity extends ComponentActivity {
+ private static final int POWER_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
+ public static final String PREF_SELECTED_UID = "selectedUid";
+ private static final String LOADER_ARG_UID = "uid";
+
+ private PowerStatsDataAdapter mPowerStatsDataAdapter;
+ private Runnable mPowerStatsRefresh = this::periodicPowerStatsRefresh;
+ private SharedPreferences mSharedPref;
+ private int mUid = Process.INVALID_UID;
+ private TextView mTitleView;
+ private TextView mUidView;
+ private ImageView mIconView;
+ private TextView mPackagesView;
+ private RecyclerView mPowerStatsDataView;
+ private View mLoadingView;
+ private View mEmptyView;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mSharedPref = getPreferences(Context.MODE_PRIVATE);
+
+ setContentView(R.layout.power_stats_viewer_layout);
+
+ View appCard = findViewById(R.id.app_card);
+ appCard.setOnClickListener((e) -> startAppPicker());
+
+ mTitleView = findViewById(android.R.id.title);
+ mUidView = findViewById(R.id.uid);
+ mIconView = findViewById(android.R.id.icon);
+ mPackagesView = findViewById(R.id.packages);
+
+ mPowerStatsDataView = findViewById(R.id.power_stats_data_view);
+ mPowerStatsDataView.setLayoutManager(new LinearLayoutManager(this));
+ mPowerStatsDataAdapter = new PowerStatsDataAdapter();
+ mPowerStatsDataView.setAdapter(mPowerStatsDataAdapter);
+
+ mLoadingView = findViewById(R.id.loading_view);
+ mEmptyView = findViewById(R.id.empty_view);
+
+ mUid = mSharedPref.getInt(PREF_SELECTED_UID, Process.INVALID_UID);
+ loadPowerStats();
+ if (mUid == Process.INVALID_UID) {
+ startAppPicker();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ periodicPowerStatsRefresh();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getMainThreadHandler().removeCallbacks(mPowerStatsRefresh);
+ }
+
+ private void startAppPicker() {
+ registerForActivityResult(AppPickerActivity.CONTRACT, this::onApplicationSelected)
+ .launch(null);
+ }
+
+ private void onApplicationSelected(int uid) {
+ if (uid == -1) {
+ if (mUid == Process.INVALID_UID) {
+ finish();
+ }
+ } else {
+ mUid = uid;
+ mSharedPref.edit().putInt(PREF_SELECTED_UID, mUid).apply();
+ mLoadingView.setVisibility(View.VISIBLE);
+ loadPowerStats();
+ }
+ }
+
+ private void periodicPowerStatsRefresh() {
+ loadPowerStats();
+ getMainThreadHandler().postDelayed(mPowerStatsRefresh, POWER_STATS_REFRESH_RATE_MILLIS);
+ }
+
+ private void loadPowerStats() {
+ Bundle args = new Bundle();
+ args.putInt(LOADER_ARG_UID, mUid);
+ LoaderManager.getInstance(this).restartLoader(0, args, new PowerStatsDataLoaderCallbacks());
+ }
+
+ private static class PowerStatsDataLoader extends AsyncLoaderCompat<PowerStatsData> {
+ private final int mUid;
+ private final BatteryStatsHelper mBatteryStatsHelper;
+ private final UserManager mUserManager;
+
+ PowerStatsDataLoader(Context context, int uid) {
+ super(context);
+ mUid = uid;
+ mUserManager = context.getSystemService(UserManager.class);
+ mBatteryStatsHelper = new BatteryStatsHelper(context,
+ false /* collectBatteryBroadcast */);
+ mBatteryStatsHelper.create((Bundle) null);
+ mBatteryStatsHelper.clearStats();
+ }
+
+ @Override
+ public PowerStatsData loadInBackground() {
+ mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
+ mUserManager.getUserProfiles());
+ return new PowerStatsData(getContext(), mBatteryStatsHelper, mUid);
+ }
+
+ @Override
+ protected void onDiscardResult(PowerStatsData result) {
+ }
+ }
+
+ private class PowerStatsDataLoaderCallbacks implements LoaderCallbacks<PowerStatsData> {
+ @NonNull
+ @Override
+ public Loader<PowerStatsData> onCreateLoader(int id, Bundle args) {
+ return new PowerStatsDataLoader(PowerStatsViewerActivity.this,
+ args.getInt(LOADER_ARG_UID, Process.INVALID_UID));
+ }
+
+ @Override
+ public void onLoadFinished(@NonNull Loader<PowerStatsData> loader,
+ PowerStatsData powerStatsData) {
+
+ AppInfoHelper.AppInfo appInfo = powerStatsData.getAppInfo();
+ mTitleView.setText(appInfo.label);
+ mUidView.setText(String.format(Locale.getDefault(), "UID: %d", appInfo.uid));
+ mIconView.setImageDrawable(appInfo.iconInfo.loadIcon(getPackageManager()));
+
+ if (appInfo.packages != null) {
+ mPackagesView.setText(appInfo.packages);
+ mPackagesView.setVisibility(View.VISIBLE);
+ } else {
+ mPackagesView.setVisibility(View.GONE);
+ }
+
+ mPowerStatsDataAdapter.setEntries(powerStatsData.getEntries());
+
+ if (powerStatsData.getEntries().isEmpty()) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ mPowerStatsDataView.setVisibility(View.GONE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ mPowerStatsDataView.setVisibility(View.VISIBLE);
+ }
+
+ mLoadingView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(@NonNull Loader<PowerStatsData> loader) {
+ }
+ }
+
+ private static class PowerStatsDataAdapter extends
+ RecyclerView.Adapter<PowerStatsDataAdapter.ViewHolder> {
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView titleTextView;
+ public TextView amountTextView;
+ public TextView percentTextView;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+
+ titleTextView = itemView.findViewById(R.id.title);
+ amountTextView = itemView.findViewById(R.id.amount);
+ percentTextView = itemView.findViewById(R.id.percent);
+ }
+ }
+
+ private List<PowerStatsData.Entry> mEntries = Collections.emptyList();
+
+ public void setEntries(List<PowerStatsData.Entry> entries) {
+ mEntries = entries;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mEntries.size();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ View itemView = layoutInflater.inflate(R.layout.power_stats_entry_layout, parent,
+ false);
+ return new ViewHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+ PowerStatsData.Entry entry = mEntries.get(position);
+ switch (entry.entryType) {
+ case POWER:
+ viewHolder.titleTextView.setText(entry.title);
+ viewHolder.amountTextView.setText(
+ String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+ break;
+ case DURATION:
+ viewHolder.titleTextView.setText(entry.title);
+ viewHolder.amountTextView.setText(
+ String.format(Locale.getDefault(), "%,d ms", (long) entry.value));
+ break;
+ }
+
+ double proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
+ viewHolder.percentTextView.setText(String.format(Locale.getDefault(), "%.1f%%",
+ proportion));
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java b/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java
index 55d186c292f6..469a4ccd05e4 100644
--- a/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java
@@ -16,16 +16,17 @@
package com.android.internal.util;
-import com.android.internal.util.CharSequences;
import static com.android.internal.util.CharSequences.forAsciiBytes;
-import junit.framework.TestCase;
+
import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
public class CharSequencesTest extends TestCase {
@SmallTest
public void testCharSequences() {
- String s = "Crazy Bob";
+ String s = "Hello Bob";
byte[] bytes = s.getBytes();
String copy = toString(forAsciiBytes(bytes));
@@ -34,11 +35,11 @@ public class CharSequencesTest extends TestCase {
copy = toString(forAsciiBytes(bytes, 0, s.length()));
assertTrue(s.equals(copy));
- String crazy = toString(forAsciiBytes(bytes, 0, 5));
- assertTrue("Crazy".equals(crazy));
+ String hello = toString(forAsciiBytes(bytes, 0, 5));
+ assertTrue("Hello".equals(hello));
- String a = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3));
- assertTrue("a".equals(a));
+ String l = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3));
+ assertTrue("l".equals(l));
String empty = toString(forAsciiBytes(bytes, 0, 3).subSequence(3, 3));
assertTrue("".equals(empty));
diff --git a/core/xsd/vts/Android.bp b/core/xsd/vts/Android.bp
index 4b43b4136076..ca655f18149c 100644
--- a/core/xsd/vts/Android.bp
+++ b/core/xsd/vts/Android.bp
@@ -40,7 +40,3 @@ cc_test {
],
test_config: "vts_permission_validate_test.xml",
}
-
-vts_config {
- name: "VtsValidatePermission",
-}
diff --git a/core/xsd/vts/AndroidTest.xml b/core/xsd/vts/AndroidTest.xml
deleted file mode 100644
index e5cc9a0f74ee..000000000000
--- a/core/xsd/vts/AndroidTest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.
--->
-<configuration description="Config for VTS VtsValidatePermission.">
- <option name="config-descriptor:metadata" key="plan" value="vts-treble" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
- <option name="abort-on-push-failure" value="false"/>
- <option name="push-group" value="HostDrivenTest.push"/>
- <option name="push" value="DATA/etc/permission.xsd->/data/local/tmp/permission.xsd"/>
- </target_preparer>
- <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
- <option name="test-module-name" value="VtsValidatePermission"/>
- <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_permission_validate_test/vts_permission_validate_test" />
- <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_permission_validate_test/vts_permission_validate_test" />
- <option name="binary-test-type" value="gtest"/>
- <option name="test-timeout" value="30s"/>
- </test>
-</configuration>
diff --git a/data/etc/com.android.storagemanager.xml b/data/etc/com.android.storagemanager.xml
index e85a82c983df..a1635fe5548b 100644
--- a/data/etc/com.android.storagemanager.xml
+++ b/data/etc/com.android.storagemanager.xml
@@ -22,5 +22,6 @@
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0a0681479278..977703dcd956 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -153,8 +153,9 @@
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
<assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
+ <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
+
<assign-permission name="android.permission.INTERNET" uid="media" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
<assign-permission name="android.permission.INTERNET" uid="shell" />
@@ -164,7 +165,6 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
@@ -175,10 +175,8 @@
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
<assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
<assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="graphics" />
<assign-permission name="android.permission.DUMP" uid="incidentd" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="incidentd" />
@@ -193,10 +191,8 @@
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="statsd" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="statsd" />
<assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" />
- <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="gpu_service" />
<split-permission name="android.permission.ACCESS_FINE_LOCATION">
<new-permission name="android.permission.ACCESS_COARSE_LOCATION" />
@@ -227,6 +223,15 @@
targetSdk="29">
<new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
</split-permission>
+ <split-permission name="android.permission.RECORD_AUDIO"
+ targetSdk="31">
+ <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
+ </split-permission>
+ <split-permission name="android.permission.CAMERA"
+ targetSdk="31">
+ <new-permission name="android.permission.BACKGROUND_CAMERA" />
+ </split-permission>
+
<!-- This is a list of all the libraries available for application
code to link against. -->
@@ -234,8 +239,7 @@
<library name="android.test.base"
file="/system/framework/android.test.base.jar" />
<library name="android.test.mock"
- file="/system/framework/android.test.mock.jar"
- dependency="android.test.base" />
+ file="/system/framework/android.test.mock.jar" />
<library name="android.test.runner"
file="/system/framework/android.test.runner.jar"
dependency="android.test.base:android.test.mock" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 102c933dd84d..4c3b36f78eec 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -168,6 +168,7 @@ applications that come with the platform
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" />
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
<permission name="android.permission.PERFORM_CDMA_PROVISIONING"/>
@@ -434,12 +435,24 @@ applications that come with the platform
<permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<!-- Permissions required for CTS test - AdbManagerTest -->
<permission name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permissions required for CTS test - TimeManagerTest -->
+ <permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+ <!-- Permissions required for CTS test - android.server.biometrics -->
+ <permission name="android.permission.USE_BIOMETRIC" />
+ <permission name="android.permission.TEST_BIOMETRIC" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
<permission name="android.permission.INTENT_FILTER_VERIFICATION_AGENT"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.traceur">
+ <!-- Permissions required to receive BUGREPORT_STARTED intent -->
+ <permission name="android.permission.DUMP"/>
+ <!-- Permissions required for quick settings tile -->
+ <permission name="android.permission.STATUS_BAR"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.tv">
<permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
<permission name="android.permission.DVB_DEVICE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 217de84cfc3e..31dae22df454 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -49,6 +49,18 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-2062338592": {
+ "message": "Looking for task of %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
+ "-2054442123": {
+ "message": "Setting Intent of %s to %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-2049725903": {
"message": "Task back pressed on root taskId=%d",
"level": "VERBOSE",
@@ -151,12 +163,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1910833551": {
+ "message": "SyncSet{%x:%d} Start for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
"-1895337367": {
"message": "Delete root task display=%d winMode=%d",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
+ "-1890326172": {
+ "message": "no-history finish of %s on new resume",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1884933373": {
"message": "enableScreenAfterBoot: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
"level": "INFO",
@@ -193,12 +217,24 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "-1861864501": {
+ "message": "resumeTopActivityLocked: Going to sleep and all paused",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1847087163": {
"message": "TRANSIT_TASK_OPEN_BEHIND, adding %s to mOpeningApps",
"level": "DEBUG",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1844540996": {
+ "message": " Initial targets: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1838803135": {
"message": "Attempted to set windowing mode to a display that does not exist: %d",
"level": "WARN",
@@ -247,6 +283,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-1768090656": {
+ "message": "Re-launching after pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1750206390": {
"message": "Exception thrown when creating surface for client %s (%s). %s",
"level": "WARN",
@@ -277,6 +319,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1704402370": {
+ "message": "resetTaskIntendedTask: calling finishActivity on %s",
+ "level": "WARN",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
+ },
"-1699018375": {
"message": "Adding activity %s to task %s callers: %s",
"level": "INFO",
@@ -289,6 +337,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1679411993": {
+ "message": "setVr2dDisplayId called for: %d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-1670695197": {
"message": "Attempted to add presentation window to a non-suitable display. Aborting.",
"level": "WARN",
@@ -301,6 +355,18 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1655805455": {
+ "message": "Enqueue pending stop if needed: %s wasStopping=%b visibleRequested=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "-1647332198": {
+ "message": "remove RecentTask %s when finishing user %d",
+ "level": "INFO",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RecentTasks.java"
+ },
"-1638958146": {
"message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
"level": "INFO",
@@ -319,6 +385,24 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "-1613096551": {
+ "message": "Top resumed state released %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
+ "-1607026519": {
+ "message": "Ready to stop: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
+ "-1601745126": {
+ "message": "Launch on display check: allow launch for owner of the display",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-1598452494": {
"message": "activityDestroyedLocked: r=%s",
"level": "DEBUG",
@@ -331,12 +415,42 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "-1587921395": {
+ "message": " Top targets: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "-1585311008": {
+ "message": "Bring to front target: %s from %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStarter.java"
+ },
+ "-1575977269": {
+ "message": "Skipping %s: mismatch root %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"-1568331821": {
"message": "Enabling listeners",
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "-1567866547": {
+ "message": "Collecting in transition %d: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "-1558137010": {
+ "message": "Config is relaunching invisible activity %s called by %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1554521902": {
"message": "showInsets(ime) was requested by different window: %s ",
"level": "WARN",
@@ -403,12 +517,36 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
+ "-1492696222": {
+ "message": "App died during pause, not stopping: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "-1480772131": {
+ "message": "No app or window is requesting an orientation, return %d for display id=%d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
+ "-1474292612": {
+ "message": "Could not find task for id: %d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-1471946192": {
"message": "Marking app token %s with replacing child windows.",
"level": "DEBUG",
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1452274694": {
+ "message": " CAN PROMOTE: promoting to parent %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1443029505": {
"message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
"level": "INFO",
@@ -427,12 +565,24 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-1432963966": {
+ "message": "Moving to DESTROYING: %s (destroy requested)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1427184084": {
"message": "addWindow: New client %s: window=%s Callers=%s",
"level": "VERBOSE",
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1419762046": {
+ "message": "moveRootTaskToDisplay: moving taskId=%d to displayId=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-1413901262": {
"message": "startRecentsActivity(): intent=%s",
"level": "DEBUG",
@@ -457,6 +607,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1387080937": {
+ "message": "SyncSet{%x:%d} Child ready, now ready=%b and waiting on %d transactions",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
+ "-1376035390": {
+ "message": "No task found",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
+ "-1375751630": {
+ "message": " --- Start combine pass ---",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1364754753": {
"message": "Task vanished taskId=%d",
"level": "VERBOSE",
@@ -475,6 +643,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1340783230": {
+ "message": "SyncSet{%x:%d} Added %s. now waiting on %d transactions",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
"-1340540100": {
"message": "Creating SnapshotStartingData",
"level": "VERBOSE",
@@ -487,12 +661,36 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "-1305966693": {
+ "message": "Sending position change to %s, onTop: %b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
+ "-1305791032": {
+ "message": "Moving to STOPPED: %s (stop complete)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1305755880": {
"message": "Initial config: %s",
"level": "VERBOSE",
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-1304806505": {
+ "message": "Starting new activity %s in new task %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStarter.java"
+ },
+ "-1295684101": {
+ "message": "Launch on display check: no caller info, skip check",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-1292329638": {
"message": "Added starting %s: startingWindow=%s startingView=%s",
"level": "VERBOSE",
@@ -547,12 +745,30 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-1198579104": {
+ "message": "Pushing next activity %s out to target's task %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
+ },
+ "-1193946201": {
+ "message": "Can't report activity position update - client not running, activityRecord=%s",
+ "level": "WARN",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1176488860": {
"message": "SURFACE isSecure=%b: %s",
"level": "INFO",
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "-1164930508": {
+ "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1156118957": {
"message": "Updated config=%s",
"level": "DEBUG",
@@ -577,6 +793,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1136139407": {
+ "message": "no-history finish of %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1130891072": {
"message": "Orientation continue waiting for draw in %s",
"level": "VERBOSE",
@@ -643,12 +865,24 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1084548141": {
+ "message": "Launch on display check: allow launch any on display",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-1076978367": {
"message": "thawRotation: mRotation=%d",
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1066383762": {
+ "message": "Sleep still waiting to pause %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1060365734": {
"message": "Attempted to add QS dialog window with bad token %s. Aborting.",
"level": "WARN",
@@ -673,12 +907,30 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1022146708": {
+ "message": "Skipping %s: mismatch activity type",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
+ "-1016578046": {
+ "message": "Moving to %s Relaunching %s callers=%s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1009117329": {
"message": "isFetchingAppTransitionSpecs=true",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "-1003060523": {
+ "message": "Finish needs to pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-993378225": {
"message": "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s",
"level": "VERBOSE",
@@ -697,6 +949,12 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/TaskOrganizerController.java"
},
+ "-937498525": {
+ "message": "Executing finish of failed to pause activity: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-930893991": {
"message": "Set sync ready, syncId=%d",
"level": "VERBOSE",
@@ -721,6 +979,18 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-926231510": {
+ "message": "State unchanged from:%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
+ "-917215012": {
+ "message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
+ "level": "WARN",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"-916108501": {
"message": "Adding %s to %s",
"level": "VERBOSE",
@@ -733,18 +1003,36 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-903853754": {
+ "message": "pauseBackStacks: stack=%s mResumedActivity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+ },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-877494781": {
+ "message": "Start pushing activity %s out to bottom task %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
+ },
"-874446906": {
"message": "showBootMessage: msg=%s always=%b mAllowBootMessages=%b mShowingBootMessages=%b mSystemBooted=%b. %s",
"level": "INFO",
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-866966979": {
+ "message": "Moving to PAUSED: %s (starting in paused state)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-861859917": {
"message": "Attempted to add window to a display that does not exist: %d. Aborting.",
"level": "WARN",
@@ -763,6 +1051,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-855366859": {
+ "message": " merging children in from %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-853404763": {
"message": "\twallpaper=%s",
"level": "DEBUG",
@@ -781,6 +1075,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-814760297": {
+ "message": "Looking for task of %s in %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"-809771899": {
"message": "findFocusedWindow: Reached focused app=%s",
"level": "VERBOSE",
@@ -811,6 +1111,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-775004869": {
+ "message": "Not a match: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"-771177730": {
"message": "Removing focused app token:%s displayId=%d",
"level": "VERBOSE",
@@ -841,18 +1147,54 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-729530161": {
+ "message": "Moving to DESTROYED: %s (no app)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-716565534": {
"message": "moveActivityStackToFront: unfocusable activity=%s",
"level": "DEBUG",
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-705939410": {
+ "message": "Waiting for pause to complete...",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
+ "-703543418": {
+ "message": " check sibling %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-694710814": {
"message": "Pausing rotation during drag",
"level": "DEBUG",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DragState.java"
},
+ "-678300709": {
+ "message": "SyncSet{%x:%d} Trying to add %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
+ "-677449371": {
+ "message": "moveTaskToRootTask: moving task=%d to rootTaskId=%d toTop=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
+ "-672228342": {
+ "message": "resumeTopActivityLocked: Top activity resumed %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-668956537": {
"message": " THUMBNAIL %s: CREATE",
"level": "INFO",
@@ -871,6 +1213,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "-650261962": {
+ "message": "Sleep needs to pause %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-639305784": {
"message": "Could not report config changes to the window token client.",
"level": "WARN",
@@ -895,12 +1243,30 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-622017164": {
+ "message": "Finish Transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"-618015844": {
"message": "performEnableScreen: mDisplayEnabled=%b mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b mOnlyCore=%b. %s",
"level": "INFO",
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-606328116": {
+ "message": "resumeTopActivityLocked: Top activity resumed (dontWaitForPause) %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "-596163537": {
+ "message": "Waiting for top state to be released by %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-593535526": {
"message": "Binding proc %s with config %s",
"level": "VERBOSE",
@@ -919,6 +1285,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "-571686216": {
+ "message": "Launch on display check: disallow launch on virtual display for not-embedded activity.",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"-561092364": {
"message": "onPointerDownOutsideFocusLocked called on %s",
"level": "INFO",
@@ -949,12 +1321,36 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowAnimator.java"
},
+ "-533690126": {
+ "message": "resumeTopActivityLocked: Resumed %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "-532081937": {
+ "message": " Commit activity becoming invisible: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "-527683022": {
+ "message": "resumeTopActivityLocked: Skip resume: some activity pausing.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-519504830": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "-509601642": {
+ "message": " checking %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-507657818": {
"message": "Window %s is already added",
"level": "WARN",
@@ -997,6 +1393,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-446752714": {
+ "message": " SKIP: sibling contains top target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-445944810": {
"message": "finish(%b): mCanceled=%b",
"level": "DEBUG",
@@ -1021,6 +1423,18 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "-427457280": {
+ "message": "App died while pausing: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "-417514857": {
+ "message": "Key dispatch not paused for screen off",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
"level": "VERBOSE",
@@ -1039,6 +1453,18 @@
"group": "WM_DEBUG_CONTAINERS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-401029526": {
+ "message": "%s: caller %d does not hold REAL_GET_TASKS; limiting output",
+ "level": "WARN",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
+ "-399343789": {
+ "message": "Skipping %s: different user",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"-395922585": {
"message": "InsetsSource setWin %s",
"level": "DEBUG",
@@ -1087,6 +1513,12 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
+ "-332679827": {
+ "message": "resumeNextFocusableActivityWhenStackIsEmpty: %s, go home",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-324085783": {
"message": "SURFACE CROP %s: %s",
"level": "INFO",
@@ -1123,12 +1555,30 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-302335479": {
+ "message": " remove from topTargets %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "-279436615": {
+ "message": "Moving to PAUSING: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-272719931": {
"message": "startLockTaskModeLocked: %s",
"level": "WARN",
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "-262984451": {
+ "message": "Relaunch failed %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-260960989": {
"message": "Removing and adding activity %s to stack at top callers=%s",
"level": "INFO",
@@ -1147,6 +1597,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-234244777": {
+ "message": "Activity config changed during resume: %s, new next: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-198463978": {
"message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
"level": "VERBOSE",
@@ -1171,6 +1627,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "-172326720": {
+ "message": "Saving icicle of %s: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-168799453": {
"message": "Allowing features %d:0x%s",
"level": "WARN",
@@ -1189,6 +1651,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "-118786523": {
+ "message": "Resume failed; resetting state to %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-116086365": {
"message": "******************** ENABLING SCREEN!",
"level": "INFO",
@@ -1249,6 +1717,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "-21399771": {
+ "message": "activity %s already destroying, skipping request with reason:%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-7343917": {
"message": "onAnimationFinished(): targetStack=%s targetActivity=%s mRestoreTargetBehindStack=%s",
"level": "DEBUG",
@@ -1309,6 +1783,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "51927339": {
+ "message": "Skipping %s: voice session",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"73987756": {
"message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
"level": "INFO",
@@ -1327,12 +1807,24 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "86989930": {
+ "message": "setTaskWindowingMode: moving task=%d to windowingMode=%d toTop=%b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"91350919": {
"message": "Attempted to set IME flag to a display that does not exist: %d",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "94402792": {
+ "message": "Moving to RESUMED: %s (in existing)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"95216706": {
"message": "hideIme target: %s ",
"level": "DEBUG",
@@ -1411,18 +1903,36 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
+ "182319432": {
+ "message": " remove from targets %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"184362060": {
"message": "screenshotTask(%d): mCanceled=%b",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "184610856": {
+ "message": "Start calculating TransitionInfo based on participants: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"186668272": {
"message": "Now changing app %s",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "189628502": {
+ "message": "Moving to STOPPING: %s (stop requested)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"194124419": {
"message": "goodToGo(): Animation finished already, canceled=%s mPendingAnimations=%d",
"level": "DEBUG",
@@ -1489,12 +1999,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "255339989": {
+ "message": "setFocusedRootTask: taskId=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"255692476": {
"message": "**** GOOD TO GO",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "259206414": {
+ "message": "Creating Transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"269576220": {
"message": "Resuming rotation after drag",
"level": "DEBUG",
@@ -1537,6 +2059,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "306524472": {
+ "message": "Stop failed; moving to STOPPED: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"309039362": {
"message": "SURFACE MATRIX [%f,%f,%f,%f]: %s",
"level": "INFO",
@@ -1609,6 +2137,12 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "397382873": {
+ "message": "Moving to PAUSED: %s %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"399841913": {
"message": "SURFACE RECOVER DESTROY: %s",
"level": "INFO",
@@ -1627,6 +2161,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "430260320": {
+ "message": " sibling is a top target with mode %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"435494046": {
"message": "Attempted to add window to a display for which the application does not have access: %d. Aborting.",
"level": "WARN",
@@ -1657,6 +2197,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "485170982": {
+ "message": "Not finishing noHistory %s on stop because we're just sleeping",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"487621047": {
"message": "DisplayArea vanished name=%s",
"level": "VERBOSE",
@@ -1681,6 +2227,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "528150092": {
+ "message": " keep as target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"531242746": {
"message": " THUMBNAIL %s: CREATE",
"level": "INFO",
@@ -1717,6 +2269,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "579298675": {
+ "message": "Moving to DESTROYED: %s (removed from history)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"585096182": {
"message": "SURFACE isColorSpaceAgnostic=%b: %s",
"level": "INFO",
@@ -1729,6 +2287,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "590184240": {
+ "message": "- NOT adding to sync: visible=%b hasListener=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowContainer.java"
+ },
"594260577": {
"message": "createWallpaperAnimations()",
"level": "DEBUG",
@@ -1765,6 +2329,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "625447638": {
+ "message": "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_RESIZE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"628276090": {
"message": "Delaying app transition for screen rotation animation to finish",
"level": "VERBOSE",
@@ -1825,6 +2395,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "674932310": {
+ "message": "Setting Intent of %s to target %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"685047360": {
"message": "Resizing window %s",
"level": "VERBOSE",
@@ -1849,18 +2425,36 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "709500946": {
+ "message": "resumeTopActivityLocked: Skip resume: need to start pausing",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"715749922": {
"message": "Allowlisting %d:%s",
"level": "WARN",
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "726205185": {
+ "message": "Moving to DESTROYED: %s (destroy skipped)",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"736692676": {
"message": "Config is relaunching %s",
"level": "VERBOSE",
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "744171317": {
+ "message": " SKIP: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"745391677": {
"message": " CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x \/ %s",
"level": "INFO",
@@ -1873,12 +2467,24 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "781471998": {
+ "message": "moveWindowTokenToDisplay: Cannot move to the original display for token: %s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"791468751": {
"message": "Pausing rotation during re-position",
"level": "DEBUG",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskPositioner.java"
},
+ "793568608": {
+ "message": " SKIP: sibling is visible but not part of transition",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"794570322": {
"message": "Now closing app %s",
"level": "VERBOSE",
@@ -1909,12 +2515,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowToken.java"
},
+ "849147756": {
+ "message": "Finish collecting in transition %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"853091290": {
"message": "Moved stack=%s behind stack=%s",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "864551564": {
+ "message": "Launch on display check: disallow activity embedding without permission.",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"869266572": {
"message": "Removing activity %s from stack, reason= %s callers=%s",
"level": "INFO",
@@ -1933,12 +2551,30 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "875196661": {
+ "message": "Skipping stack: (mismatch activity\/stack) %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/TaskDisplayArea.java"
+ },
"892244061": {
"message": "Waiting for drawn %s: removed=%b visible=%b mHasSurface=%b drawState=%d",
"level": "INFO",
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "895158150": {
+ "message": "allPausedActivitiesComplete: r=%s state=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
+ "897964776": {
+ "message": "Complete pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"898863925": {
"message": "Attempted to add QS dialog window with unknown token %s. Aborting.",
"level": "WARN",
@@ -1993,12 +2629,54 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "988389910": {
+ "message": "resumeTopActivityLocked: Pausing %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
+ "996960396": {
+ "message": "Starting Transition %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "1000601037": {
+ "message": "SyncSet{%x:%d} Set ready",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
+ "1001509841": {
+ "message": "Auto-PIP allowed, entering PIP mode directly: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1001904964": {
"message": "***** BOOT TIMEOUT: forcing display enabled",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1015542198": {
+ "message": "Launch on display check: displayId=%d callingPid=%d callingUid=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
+ "1023413388": {
+ "message": "Finish waiting for pause of: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
+ "1033274509": {
+ "message": "moveWindowTokenToDisplay: Attempted to move non-existing token: %s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1040675582": {
"message": "Can't report activity configuration update - client not running, activityRecord=%s",
"level": "WARN",
@@ -2011,6 +2689,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1047769218": {
+ "message": "Finishing activity r=%s, result=%d, data=%s, reason=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1049367566": {
"message": "Sending to proc %s new config %s",
"level": "VERBOSE",
@@ -2023,6 +2707,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1068803972": {
+ "message": "Activity paused: token=%s, timeout=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1073230342": {
"message": "startAnimation",
"level": "DEBUG",
@@ -2041,18 +2731,36 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
+ "1098891625": {
+ "message": "realStartActivityLocked: Skipping start of r=%s some activities pausing...",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"1112047265": {
"message": "finishDrawingWindow: %s mDrawState=%s",
"level": "DEBUG",
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1115248873": {
+ "message": "Calling onTransitionReady: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1115417974": {
"message": "FORCED DISPLAY SIZE: %dx%d",
"level": "INFO",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1126328412": {
+ "message": "Scheduling idle now: forceIdle=%b immediate=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1140424002": {
"message": "Finished screen turning on...",
"level": "INFO",
@@ -2065,24 +2773,36 @@
"group": "WM_DEBUG_WINDOW_ORGANIZER",
"at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
},
- "1160771501": {
- "message": "Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_RESIZE",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"1166381079": {
"message": "Execute app transition: %s, displayId: %d Callers=%s",
"level": "WARN",
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "1186730970": {
+ "message": " no common mode yet, so set it",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
+ "1192413464": {
+ "message": "Comparing existing cls=%s \/aff=%s to new cls=%s \/aff=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"1208313423": {
"message": "addWindowToken: Attempted to add token: %s for non-exiting displayId=%d",
"level": "WARN",
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1217926207": {
+ "message": "Activity not running, resuming next.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1219600119": {
"message": "addWindow: win=%s Callers=%s",
"level": "DEBUG",
@@ -2119,12 +2839,24 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1270792394": {
+ "message": "Resumed after relaunch %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1288731814": {
"message": "WindowState.hideLw: setting mFocusMayChange true",
"level": "INFO",
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1316533291": {
+ "message": "State movement: %s from:%s to:%s reason:%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1325649102": {
"message": "Bad requesting window %s",
"level": "WARN",
@@ -2167,6 +2899,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "1364126018": {
+ "message": "Resumed activity; dropping state of: %s",
+ "level": "INFO",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1364498663": {
"message": "notifyAppResumed: wasStopped=%b %s",
"level": "VERBOSE",
@@ -2251,6 +2989,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
+ "1469310004": {
+ "message": " SKIP: common mode mismatch. was %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"1495525537": {
"message": "createWallpaperAnimations()",
"level": "DEBUG",
@@ -2317,6 +3061,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "1557732761": {
+ "message": "For Intent %s bringing to top: %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"1563755163": {
"message": "Permission Denial: %s from pid=%d, uid=%d requires %s",
"level": "WARN",
@@ -2341,12 +3091,24 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
+ "1585450696": {
+ "message": "resumeTopActivityLocked: Restarting %s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1589610525": {
"message": "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: anim=%s transit=%s isEntrance=true Callers=%s",
"level": "VERBOSE",
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "1610646518": {
+ "message": "Enqueueing pending finish: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1628345525": {
"message": "Now opening app %s",
"level": "VERBOSE",
@@ -2377,6 +3139,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
+ "1648338379": {
+ "message": "Display id=%d is ignoring all orientation requests, return %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"1653210583": {
"message": "Removing app %s delayed=%b animation=%s animating=%b",
"level": "VERBOSE",
@@ -2413,6 +3181,18 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1696210756": {
+ "message": "Launch on display check: allow launch on public display",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
+ "1706082525": {
+ "message": "Stopping %s: nowVisible=%b animating=%b finishing=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"1720229827": {
"message": "Creating animation bounds layer",
"level": "INFO",
@@ -2473,6 +3253,18 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1794249572": {
+ "message": "Requesting StartTransition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
+ "1804435108": {
+ "message": "allResumedActivitiesIdle: stack=%d %s not idle",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"1822843721": {
"message": "Aborted starting %s: startingData=%s",
"level": "VERBOSE",
@@ -2509,6 +3301,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1837992242": {
+ "message": "Executing finish of activity: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1853793312": {
"message": "Notify removed startingWindow %s",
"level": "VERBOSE",
@@ -2527,12 +3325,24 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1884961873": {
+ "message": "Sleep still need to stop %d activities",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1891501279": {
"message": "cancelAnimation(): reason=%s",
"level": "DEBUG",
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1894239744": {
+ "message": "Enqueueing pending pause: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"1903353011": {
"message": "notifyAppStopped: %s",
"level": "VERBOSE",
@@ -2563,6 +3373,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "1947936538": {
+ "message": "Found matching class!",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"1964565370": {
"message": "Starting remote animation",
"level": "INFO",
@@ -2575,12 +3391,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "1975793405": {
- "message": "setFocusedStack: stackId=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"1984470582": {
"message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d",
"level": "DEBUG",
@@ -2599,6 +3409,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "2001924866": {
+ "message": "SyncSet{%x:%d} Finished. Reporting %d containers to %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
"2016061474": {
"message": "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d Callers=%s",
"level": "VERBOSE",
@@ -2635,12 +3451,24 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
+ "2034714267": {
+ "message": "Launch on display check: allow launch for caller present on the display",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityStackSupervisor.java"
+ },
"2034780299": {
"message": "CHECK_IF_BOOT_ANIMATION_FINISHED:",
"level": "INFO",
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "2039056415": {
+ "message": "Found matching affinity candidate!",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/RootWindowContainer.java"
+ },
"2045641491": {
"message": "Checking %d opening apps (frozen=%b timeout=%b)...",
"level": "VERBOSE",
@@ -2653,6 +3481,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
},
+ "2060978050": {
+ "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"2081291430": {
"message": "Focus not requested for window=%s because it has no surface",
"level": "DEBUG",
@@ -2689,6 +3523,12 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "2117696413": {
+ "message": "moveTaskToFront: moving taskId=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_TASKS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"2119122320": {
"message": "setInputMethodTarget %s",
"level": "INFO",
@@ -2772,15 +3612,27 @@
"WM_DEBUG_STARTING_WINDOW": {
"tag": "WindowManager"
},
+ "WM_DEBUG_STATES": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_SWITCH": {
"tag": "WindowManager"
},
+ "WM_DEBUG_SYNC_ENGINE": {
+ "tag": "WindowManager"
+ },
+ "WM_DEBUG_TASKS": {
+ "tag": "WindowManager"
+ },
"WM_DEBUG_WINDOW_MOVEMENT": {
"tag": "WindowManager"
},
"WM_DEBUG_WINDOW_ORGANIZER": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WINDOW_TRANSITIONS": {
+ "tag": "WindowManager"
+ },
"WM_ERROR": {
"tag": "WindowManager"
},
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3a3bea43ab86..ee5c5b0591cc 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -17,9 +17,8 @@ prebuilt_font {
src: "DroidSansMono.ttf",
required: [
// Roboto-Regular.ttf provides DroidSans.ttf as a symlink to itself
+ // Roboto-Regular.ttf provides DroidSans-Bold.ttf as a symlink to itself
"Roboto-Regular.ttf",
- // Roboto-Bold.ttf provides DroidSans-Bold.ttf as a symlink to itself
- "Roboto-Bold.ttf",
],
}
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index c992bd8f8338..bda84dd76cf5 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -18,23 +18,104 @@
prefer the former when an 800 weight is requested. Since bold spans
effectively add 300 to the weight, this ensures that 900 is the bold
paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
-->
<familyset version="23">
<!-- first font is default -->
<family name="sans-serif">
- <font weight="100" style="normal">Roboto-Thin.ttf</font>
- <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>
- <font weight="300" style="normal">Roboto-Light.ttf</font>
- <font weight="300" style="italic">Roboto-LightItalic.ttf</font>
- <font weight="400" style="normal">Roboto-Regular.ttf</font>
- <font weight="400" style="italic">Roboto-Italic.ttf</font>
- <font weight="500" style="normal">Roboto-Medium.ttf</font>
- <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>
- <font weight="900" style="normal">Roboto-Black.ttf</font>
- <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>
- <font weight="700" style="normal">Roboto-Bold.ttf</font>
- <font weight="700" style="italic">Roboto-BoldItalic.ttf</font>
- </family>
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
<!-- Note that aliases must come after the fonts they reference. -->
<alias name="sans-serif-thin" to="sans-serif" weight="100" />
@@ -47,14 +128,96 @@
<alias name="verdana" to="sans-serif" />
<family name="sans-serif-condensed">
- <font weight="300" style="normal">RobotoCondensed-Light.ttf</font>
- <font weight="300" style="italic">RobotoCondensed-LightItalic.ttf</font>
- <font weight="400" style="normal">RobotoCondensed-Regular.ttf</font>
- <font weight="400" style="italic">RobotoCondensed-Italic.ttf</font>
- <font weight="500" style="normal">RobotoCondensed-Medium.ttf</font>
- <font weight="500" style="italic">RobotoCondensed-MediumItalic.ttf</font>
- <font weight="700" style="normal">RobotoCondensed-Bold.ttf</font>
- <font weight="700" style="italic">RobotoCondensed-BoldItalic.ttf</font>
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
</family>
<alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
<alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
@@ -120,10 +283,30 @@
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
<family lang="und-Ethi">
- <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
- <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
- <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-Regular.otf</font>
- <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-Bold.otf</font>
+ <font weight="400" style="normal">NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
</family>
<family lang="und-Hebr">
<font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font>
diff --git a/data/keyboards/Vendor_2378_Product_1008.kl b/data/keyboards/Vendor_2378_Product_1008.kl
index 478da03b9a78..7b19469ab6b4 100644
--- a/data/keyboards/Vendor_2378_Product_1008.kl
+++ b/data/keyboards/Vendor_2378_Product_1008.kl
@@ -14,6 +14,10 @@
# OnLive, Inc. OnLive Wireless Controller, USB adapter
+key 164 MEDIA_PLAY_PAUSE
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
+key 208 MEDIA_FAST_FORWARD
key 304 BUTTON_A
key 305 BUTTON_B
key 307 BUTTON_X
@@ -22,6 +26,7 @@ key 310 BUTTON_L1
key 311 BUTTON_R1
key 315 BUTTON_START
key 314 BUTTON_SELECT
+key 316 BUTTON_MODE
key 317 BUTTON_THUMBL
key 318 BUTTON_THUMBR
diff --git a/data/keyboards/Vendor_2378_Product_100a.kl b/data/keyboards/Vendor_2378_Product_100a.kl
index d9cd17120464..cb2b73afee3d 100644
--- a/data/keyboards/Vendor_2378_Product_100a.kl
+++ b/data/keyboards/Vendor_2378_Product_100a.kl
@@ -14,6 +14,10 @@
# OnLive, Inc. OnLive Wireless Controller
+key 164 MEDIA_PLAY_PAUSE
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
+key 208 MEDIA_FAST_FORWARD
key 304 BUTTON_A
key 305 BUTTON_B
key 307 BUTTON_X
@@ -22,6 +26,7 @@ key 310 BUTTON_L1
key 311 BUTTON_R1
key 315 BUTTON_START
key 314 BUTTON_SELECT
+key 316 BUTTON_MODE
key 317 BUTTON_THUMBL
key 318 BUTTON_THUMBR
diff --git a/drm/jni/Android.bp b/drm/jni/Android.bp
index 1e33f0ea5094..68757d86fb89 100644
--- a/drm/jni/Android.bp
+++ b/drm/jni/Android.bp
@@ -21,6 +21,7 @@ cc_library_shared {
shared_libs: [
"libdrmframework",
+ "libdrmframeworkcommon",
"liblog",
"libutils",
"libandroid_runtime",
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/BinderIdentityChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/BinderIdentityChecker.java
new file mode 100644
index 000000000000..68477edf97d1
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/BinderIdentityChecker.java
@@ -0,0 +1,108 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.contains;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.TryTree;
+import com.sun.source.tree.VariableTree;
+
+import java.util.List;
+
+import javax.lang.model.element.Modifier;
+
+/**
+ * Binder maintains thread-local identity information about any remote caller,
+ * which can be temporarily cleared while performing operations that need to be
+ * handled as the current process. However, it's important to restore the
+ * original remote calling identity after carefully scoping this work inside a
+ * try/finally block, to avoid obscure security vulnerabilities.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkBinderIdentity",
+ summary = "Verifies that Binder.clearCallingIdentity() is always restored",
+ severity = WARNING)
+public final class BinderIdentityChecker extends BugChecker implements MethodInvocationTreeMatcher {
+ private static final Matcher<ExpressionTree> CLEAR_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("clearCallingIdentity()"));
+ private static final Matcher<ExpressionTree> RESTORE_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("restoreCallingIdentity(long)"));
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (CLEAR_CALL.matches(tree, state)) {
+ // First, make sure we're recording the token for later
+ final VariableTree token = state.findEnclosing(VariableTree.class);
+ if (token == null || !token.getModifiers().getFlags().contains(Modifier.FINAL)) {
+ return buildDescription(tree)
+ .setMessage("Must store Binder.clearCallingIdentity() token as final"
+ + " variable to support safe restore")
+ .build();
+ }
+
+ // Next, verify the very next block is try-finally; any other calls
+ // between the clearing and try risk throwing an exception without
+ // doing a safe restore
+ final Tree next = nextStatement(token, state);
+ if (next == null || next.getKind() != Kind.TRY) {
+ return buildDescription(tree)
+ .setMessage("Must immediately define a try-finally block after"
+ + " Binder.clearCallingIdentity() to support safe restore")
+ .build();
+ }
+
+ // Finally, verify that we restore inside the finally block
+ final TryTree tryTree = (TryTree) next;
+ final BlockTree finallyTree = tryTree.getFinallyBlock();
+ if (finallyTree == null
+ || !contains(ExpressionTree.class, RESTORE_CALL).matches(finallyTree, state)) {
+ return buildDescription(tree)
+ .setMessage("Must call Binder.restoreCallingIdentity() in finally"
+ + " block to support safe restore")
+ .build();
+ }
+ }
+ return Description.NO_MATCH;
+ }
+
+ private static Tree nextStatement(Tree tree, VisitorState state) {
+ final BlockTree block = state.findEnclosing(BlockTree.class);
+ if (block == null) return null;
+ final List<? extends StatementTree> siblings = block.getStatements();
+ if (siblings == null) return null;
+ final int index = siblings.indexOf(tree);
+ if (index == -1 || index + 1 >= siblings.size()) return null;
+ return siblings.get(index + 1);
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/CompatChangeChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/CompatChangeChecker.java
new file mode 100644
index 000000000000..9c84f50b76bb
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/CompatChangeChecker.java
@@ -0,0 +1,125 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.bugpatterns.android.TargetSdkChecker.binaryTreeExact;
+import static com.google.errorprone.matchers.FieldMatchers.anyFieldInClass;
+import static com.google.errorprone.matchers.FieldMatchers.staticField;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.anything;
+import static com.google.errorprone.matchers.Matchers.kindIs;
+import static com.google.errorprone.matchers.Matchers.not;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.Tree.Kind;
+
+/**
+ * Each SDK level often has dozens of different behavior changes, which can be
+ * difficult for large app developers to adjust to during preview or beta
+ * releases. For this reason, {@code android.app.compat.CompatChanges} was
+ * introduced as a new best-practice for adding behavior changes.
+ * <p>
+ * During a preview or beta release, developers can temporarily opt-out of each
+ * individual change to aid debugging. This opt-out is only available during
+ * preview of beta releases, and cannot be adjusted on finalized builds.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkCompatChange",
+ summary = "Verifies that behavior changes use the modern compatibility framework",
+ severity = WARNING)
+public final class CompatChangeChecker extends BugChecker implements BinaryTreeMatcher {
+ private static final Matcher<ExpressionTree> VERSION_CODE =
+ anyFieldInClass("android.os.Build.VERSION_CODES");
+
+ // Ship has already sailed on these SDK levels; not worth fixing
+ private static final Matcher<ExpressionTree> LEGACY_VERSION_CODE = anyOf(
+ staticField("android.os.Build.VERSION_CODES", "BASE"),
+ staticField("android.os.Build.VERSION_CODES", "BASE_1_1"),
+ staticField("android.os.Build.VERSION_CODES", "CUPCAKE"),
+ staticField("android.os.Build.VERSION_CODES", "DONUT"),
+ staticField("android.os.Build.VERSION_CODES", "ECLAIR"),
+ staticField("android.os.Build.VERSION_CODES", "ECLAIR_0_1"),
+ staticField("android.os.Build.VERSION_CODES", "ECLAIR_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "FROYO"),
+ staticField("android.os.Build.VERSION_CODES", "GINGERBREAD"),
+ staticField("android.os.Build.VERSION_CODES", "GINGERBREAD_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "HONEYCOMB"),
+ staticField("android.os.Build.VERSION_CODES", "HONEYCOMB_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "HONEYCOMB_MR2"),
+ staticField("android.os.Build.VERSION_CODES", "ICE_CREAM_SANDWICH"),
+ staticField("android.os.Build.VERSION_CODES", "ICE_CREAM_SANDWICH_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "JELLY_BEAN"),
+ staticField("android.os.Build.VERSION_CODES", "JELLY_BEAN_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "JELLY_BEAN_MR2"),
+ staticField("android.os.Build.VERSION_CODES", "KITKAT"),
+ staticField("android.os.Build.VERSION_CODES", "KITKAT_WATCH"),
+ staticField("android.os.Build.VERSION_CODES", "L"),
+ staticField("android.os.Build.VERSION_CODES", "LOLLIPOP"),
+ staticField("android.os.Build.VERSION_CODES", "LOLLIPOP_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "M"),
+ staticField("android.os.Build.VERSION_CODES", "N"),
+ staticField("android.os.Build.VERSION_CODES", "N_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "O"),
+ staticField("android.os.Build.VERSION_CODES", "O_MR1"),
+ staticField("android.os.Build.VERSION_CODES", "P"),
+ staticField("android.os.Build.VERSION_CODES", "Q"),
+ staticField("android.os.Build.VERSION_CODES", "R"));
+
+ private static final Matcher<ExpressionTree> R_VERSION_CODE =
+ staticField("android.os.Build.VERSION_CODES", "R");
+
+ private static final Matcher<ExpressionTree> CUR_DEVELOPMENT_VERSION_CODE =
+ staticField("android.os.Build.VERSION_CODES", "CUR_DEVELOPMENT");
+
+ private static final Matcher<ExpressionTree> MODERN_VERSION_CODE =
+ allOf(VERSION_CODE, not(LEGACY_VERSION_CODE), not(CUR_DEVELOPMENT_VERSION_CODE));
+
+ private static final Matcher<ExpressionTree> BOOLEAN_OPERATOR = anyOf(
+ kindIs(Kind.LESS_THAN), kindIs(Kind.LESS_THAN_EQUAL),
+ kindIs(Kind.GREATER_THAN), kindIs(Kind.GREATER_THAN_EQUAL),
+ kindIs(Kind.EQUAL_TO), kindIs(Kind.NOT_EQUAL_TO));
+
+ private static final Matcher<BinaryTree> INVALID = anyOf(
+ allOf(BOOLEAN_OPERATOR, binaryTreeExact(MODERN_VERSION_CODE, anything())),
+ allOf(BOOLEAN_OPERATOR, binaryTreeExact(anything(), MODERN_VERSION_CODE)),
+ allOf(kindIs(Kind.GREATER_THAN), binaryTreeExact(anything(), R_VERSION_CODE)),
+ allOf(kindIs(Kind.LESS_THAN), binaryTreeExact(R_VERSION_CODE, anything())));
+
+ @Override
+ public Description matchBinary(BinaryTree tree, VisitorState state) {
+ if (INVALID.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Behavior changes should use CompatChanges.isChangeEnabled() "
+ + "instead of direct SDK checks to ease developer transitions; "
+ + "see go/compat-framework for more details")
+ .build();
+
+ }
+ return Description.NO_MATCH;
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java
index 4f1af3e9bea2..3a1bc1eeb9ae 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ContextUserIdChecker.java
@@ -18,6 +18,7 @@ package com.google.errorprone.bugpatterns.android;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.bugpatterns.android.UidChecker.getFlavor;
+import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
@@ -60,8 +61,13 @@ public final class ContextUserIdChecker extends BugChecker implements MethodInvo
private static final Matcher<ExpressionTree> BINDER_CALL = methodInvocation(
instanceMethod().onDescendantOf("android.os.IInterface").withAnyName());
- private static final Matcher<ExpressionTree> GET_USER_ID_CALL = methodInvocation(
- instanceMethod().onDescendantOf("android.content.Context").named("getUserId"));
+ private static final Matcher<ExpressionTree> GET_USER_ID_CALL = methodInvocation(anyOf(
+ instanceMethod().onExactClass("android.app.admin.DevicePolicyManager")
+ .named("myUserId"),
+ instanceMethod().onExactClass("android.content.pm.ShortcutManager")
+ .named("injectMyUserId"),
+ instanceMethod().onDescendantOf("android.content.Context")
+ .named("getUserId")));
private static final Matcher<ExpressionTree> USER_ID_FIELD = new Matcher<ExpressionTree>() {
@Override
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsChecker.java
new file mode 100644
index 000000000000..c4c1ab6482ee
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsChecker.java
@@ -0,0 +1,98 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.code.Type;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Android offers several efficient alternatives to some upstream
+ * {@link Collections} containers, such as {@code SparseIntArray} instead of
+ * {@code Map<Integer, Integer>}.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkEfficientCollections",
+ summary = "Verifies efficient collections best-practices",
+ severity = WARNING)
+public final class EfficientCollectionsChecker extends BugChecker implements NewClassTreeMatcher {
+ private static final Matcher<Tree> IS_LIST = isSubtypeOf("java.util.List");
+ private static final Matcher<Tree> IS_MAP = isSubtypeOf("java.util.Map");
+
+ private static final String INTEGER = "java.lang.Integer";
+ private static final String LONG = "java.lang.Long";
+ private static final String BOOLEAN = "java.lang.Boolean";
+
+ @Override
+ public Description matchNewClass(NewClassTree tree, VisitorState state) {
+ final List<Type> types = ASTHelpers.getType(tree).getTypeArguments();
+ if (IS_LIST.matches(tree, state) && types != null && types.size() == 1) {
+ final Type first = types.get(0);
+ if (ASTHelpers.isSameType(first, state.getTypeFromString(INTEGER), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with IntArray for efficiency")
+ .build();
+ } else if (ASTHelpers.isSameType(first, state.getTypeFromString(LONG), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with LongArray for efficiency")
+ .build();
+ }
+ } else if (IS_MAP.matches(tree, state) && types != null && types.size() == 2) {
+ final Type first = types.get(0);
+ final Type second = types.get(1);
+ if (ASTHelpers.isSameType(first, state.getTypeFromString(INTEGER), state)) {
+ if (ASTHelpers.isSameType(second, state.getTypeFromString(INTEGER), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with SparseIntArray for efficiency")
+ .build();
+ } else if (ASTHelpers.isSameType(second, state.getTypeFromString(LONG), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with SparseLongArray for efficiency")
+ .build();
+ } else if (ASTHelpers.isSameType(second, state.getTypeFromString(BOOLEAN), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with SparseBooleanArray for efficiency")
+ .build();
+ } else {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with SparseArray for efficiency")
+ .build();
+ }
+ } else if (ASTHelpers.isSameType(first, state.getTypeFromString(LONG), state)) {
+ return buildDescription(tree)
+ .setMessage("Consider replacing with LongSparseArray for efficiency")
+ .build();
+ }
+ }
+ return Description.NO_MATCH;
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
new file mode 100644
index 000000000000..d5243164abdc
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceChecker.java
@@ -0,0 +1,123 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.enclosingMethod;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.methodIsNamed;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree;
+
+/**
+ * Parcelable data can be transported in many ways (some of which can be very
+ * inefficient) so this checker guides developers towards using high-performance
+ * best-practices.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkParcelablePerformance",
+ summary = "Verifies Parcelable performance best-practices",
+ severity = WARNING)
+public final class ParcelablePerformanceChecker extends BugChecker
+ implements MethodInvocationTreeMatcher {
+ private static final Matcher<Tree> INSIDE_WRITE_TO_PARCEL = allOf(
+ enclosingClass(isSubtypeOf("android.os.Parcelable")),
+ enclosingMethod(methodIsNamed("writeToParcel")));
+
+ private static final Matcher<ExpressionTree> WRITE_STRING = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeString"));
+ private static final Matcher<ExpressionTree> WRITE_STRING_ARRAY = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeStringArray"));
+
+ private static final Matcher<ExpressionTree> WRITE_VALUE = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeValue"));
+ private static final Matcher<ExpressionTree> WRITE_PARCELABLE = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeParcelable"));
+
+ private static final Matcher<ExpressionTree> WRITE_LIST = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeList"));
+ private static final Matcher<ExpressionTree> WRITE_PARCELABLE_LIST = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableList"));
+ private static final Matcher<ExpressionTree> WRITE_PARCELABLE_ARRAY = methodInvocation(
+ instanceMethod().onExactClass("android.os.Parcel").named("writeParcelableArray"));
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (INSIDE_WRITE_TO_PARCEL.matches(tree, state)) {
+ if (WRITE_STRING.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'writeString8()' to improve "
+ + "efficiency; sending as UTF-8 can double throughput")
+ .build();
+ }
+ if (WRITE_STRING_ARRAY.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'writeString8Array()' to improve "
+ + "efficiency; sending as UTF-8 can double throughput")
+ .build();
+ }
+
+ if (WRITE_VALUE.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use strongly-typed methods to improve "
+ + "efficiency; saves 4 bytes for type and overhead of "
+ + "Parcelable class name")
+ .build();
+ }
+ if (WRITE_PARCELABLE.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'item.writeToParcel()' to improve "
+ + "efficiency; saves overhead of Parcelable class name")
+ .build();
+ }
+
+ if (WRITE_LIST.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'writeTypedList()' to improve "
+ + "efficiency; saves overhead of repeated Parcelable class name")
+ .build();
+ }
+ if (WRITE_PARCELABLE_LIST.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'writeTypedList()' to improve "
+ + "efficiency; saves overhead of repeated Parcelable class name")
+ .build();
+ }
+ if (WRITE_PARCELABLE_ARRAY.matches(tree, state)) {
+ return buildDescription(tree)
+ .setMessage("Recommended to use 'writeTypedArray()' to improve "
+ + "efficiency; saves overhead of repeated Parcelable class name")
+ .build();
+ }
+ }
+ return Description.NO_MATCH;
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityChecker.java
new file mode 100644
index 000000000000..2561b41028cc
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityChecker.java
@@ -0,0 +1,81 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.FieldMatchers.staticField;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.contains;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+
+import java.util.regex.Pattern;
+
+/**
+ * Any method calls to create a PendingIntent require that one of the
+ * mutability flags, FLAG_MUTABLE or FLAG_IMMUTABLE, be explicitly specified.
+ * This checker verifies that one of these mutability flags are used when
+ * creating PendingIntents.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkPendingIntentMutability",
+ summary = "Verifies that FLAG_MUTABLE or FLAG_IMMUTABLE is always set",
+ severity = WARNING)
+public final class PendingIntentMutabilityChecker extends BugChecker
+ implements MethodInvocationTreeMatcher {
+
+ private static final Matcher<ExpressionTree> PENDING_INTENT_METHOD = methodInvocation(
+ staticMethod()
+ .onClass("android.app.PendingIntent")
+ .withNameMatching(Pattern.compile(
+ "^(getActivity|getActivityAsUser|getActivities|getActivitiesAsUser|"
+ + "getBroadcast|getBroadcastAsUser|getService|getForegroundService).*")));
+
+ private static final Matcher<ExpressionTree> VALID_FLAGS = anyOf(
+ staticField("android.app.PendingIntent", "FLAG_MUTABLE"),
+ staticField("android.app.PendingIntent", "FLAG_IMMUTABLE"));
+
+ private static final Matcher<ExpressionTree> CONTAINS_VALID_FLAGS = contains(
+ ExpressionTree.class, VALID_FLAGS);
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (PENDING_INTENT_METHOD.matches(tree, state)) {
+ final ExpressionTree arg = tree.getArguments().get(3);
+ if (!(VALID_FLAGS.matches(arg, state) || CONTAINS_VALID_FLAGS.matches(arg, state))) {
+ return buildDescription(arg)
+ .setMessage("To improve security, PendingIntents must declare one of"
+ + " FLAG_MUTABLE or FLAG_IMMUTABLE explicitly; see"
+ + " go/immutable-pendingintents for more details")
+ .build();
+ }
+ }
+ return Description.NO_MATCH;
+ }
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java
index 48123abd26cb..130b256e6622 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemChecker.java
@@ -17,11 +17,14 @@
package com.google.errorprone.bugpatterns.android;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.contains;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.throwStatement;
import static com.google.errorprone.matchers.Matchers.variableType;
@@ -29,13 +32,17 @@ import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
-import com.google.errorprone.bugpatterns.BugChecker.CatchTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.TryTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.predicates.TypePredicate;
import com.sun.source.tree.CatchTree;
+import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
+import com.sun.source.tree.TryTree;
import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.code.Type;
import java.util.List;
@@ -54,9 +61,17 @@ import java.util.List;
name = "AndroidFrameworkRethrowFromSystem",
summary = "Verifies that system_server calls use rethrowFromSystemServer()",
severity = WARNING)
-public final class RethrowFromSystemChecker extends BugChecker implements CatchTreeMatcher {
+public final class RethrowFromSystemChecker extends BugChecker implements TryTreeMatcher {
private static final Matcher<Tree> INSIDE_MANAGER =
enclosingClass(hasAnnotation("android.annotation.SystemService"));
+
+ // Purposefully exclude telephony Binder interfaces, since we know they
+ // always run under the separate AID_RADIO
+ private static final Matcher<ExpressionTree> SYSTEM_BINDER_CALL = methodInvocation(allOf(
+ instanceMethod().onDescendantOf("android.os.IInterface").withAnyName(),
+ not(instanceMethod().onClass(inPackage("com.android.internal.telephony"))),
+ not(instanceMethod().onClass(inPackage("com.android.internal.telecom")))));
+
private static final Matcher<VariableTree> REMOTE_EXCEPTION = variableType(
isSameType("android.os.RemoteException"));
private static final Matcher<StatementTree> RETHROW_FROM_SYSTEM = throwStatement(
@@ -64,17 +79,33 @@ public final class RethrowFromSystemChecker extends BugChecker implements CatchT
.named("rethrowFromSystemServer")));
@Override
- public Description matchCatch(CatchTree tree, VisitorState state) {
+ public Description matchTry(TryTree tree, VisitorState state) {
if (INSIDE_MANAGER.matches(tree, state)
- && REMOTE_EXCEPTION.matches(tree.getParameter(), state)) {
- final List<? extends StatementTree> statements = tree.getBlock().getStatements();
- if (statements.size() != 1 || !RETHROW_FROM_SYSTEM.matches(statements.get(0), state)) {
- return buildDescription(tree)
- .setMessage("Must contain single "
- + "'throw e.rethrowFromSystemServer()' statement")
- .build();
+ && contains(ExpressionTree.class, SYSTEM_BINDER_CALL)
+ .matches(tree.getBlock(), state)) {
+ for (CatchTree catchTree : tree.getCatches()) {
+ if (REMOTE_EXCEPTION.matches(catchTree.getParameter(), state)) {
+ final List<? extends StatementTree> statements = catchTree.getBlock()
+ .getStatements();
+ if (statements.size() != 1
+ || !RETHROW_FROM_SYSTEM.matches(statements.get(0), state)) {
+ return buildDescription(catchTree)
+ .setMessage("Must contain single "
+ + "'throw e.rethrowFromSystemServer()' statement")
+ .build();
+ }
+ }
}
}
return Description.NO_MATCH;
}
+
+ private static TypePredicate inPackage(final String filter) {
+ return new TypePredicate() {
+ @Override
+ public boolean apply(Type type, VisitorState state) {
+ return type.tsym.packge().fullname.toString().startsWith(filter);
+ }
+ };
+ }
}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java
index 232cf3f0d677..e1ebf42fec19 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/TargetSdkChecker.java
@@ -89,7 +89,7 @@ public final class TargetSdkChecker extends BugChecker implements BinaryTreeMatc
return Description.NO_MATCH;
}
- private static Matcher<BinaryTree> binaryTreeExact(Matcher<ExpressionTree> left,
+ static Matcher<BinaryTree> binaryTreeExact(Matcher<ExpressionTree> left,
Matcher<ExpressionTree> right) {
return new Matcher<BinaryTree>() {
@Override
diff --git a/errorprone/java/com/google/errorprone/matchers/FieldMatchers.java b/errorprone/java/com/google/errorprone/matchers/FieldMatchers.java
index 46f0fb2e534c..08969d60671a 100644
--- a/errorprone/java/com/google/errorprone/matchers/FieldMatchers.java
+++ b/errorprone/java/com/google/errorprone/matchers/FieldMatchers.java
@@ -36,7 +36,8 @@ public final class FieldMatchers {
return new FieldReferenceMatcher() {
@Override
boolean classIsAppropriate(ClassSymbol classSymbol) {
- return classSymbol.getQualifiedName().contentEquals(className);
+ return classSymbol != null
+ && classSymbol.getQualifiedName().contentEquals(className);
}
@Override
@@ -50,12 +51,14 @@ public final class FieldMatchers {
return new FieldReferenceMatcher() {
@Override
boolean classIsAppropriate(ClassSymbol classSymbol) {
- return classSymbol.getQualifiedName().contentEquals(className);
+ return classSymbol != null
+ && classSymbol.getQualifiedName().contentEquals(className);
}
@Override
boolean fieldSymbolIsAppropriate(Symbol symbol) {
- return symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
+ return symbol != null
+ && symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
}
};
}
@@ -64,12 +67,14 @@ public final class FieldMatchers {
return new FieldReferenceMatcher() {
@Override
boolean classIsAppropriate(ClassSymbol classSymbol) {
- return classSymbol.getQualifiedName().contentEquals(className);
+ return classSymbol != null
+ && classSymbol.getQualifiedName().contentEquals(className);
}
@Override
boolean fieldSymbolIsAppropriate(Symbol symbol) {
- return !symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
+ return symbol != null
+ && !symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
}
};
}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/BinderIdentityCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/BinderIdentityCheckerTest.java
new file mode 100644
index 000000000000..9448344f7abb
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/BinderIdentityCheckerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BinderIdentityCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ BinderIdentityChecker.class, getClass());
+ }
+
+ @Test
+ public void testValid() {
+ compilationHelper
+ .addSourceFile("/android/os/Binder.java")
+ .addSourceLines("FooService.java",
+ "import android.os.Binder;",
+ "public class FooService {",
+ " void bar() {",
+ " final long token = Binder.clearCallingIdentity();",
+ " try {",
+ " FooService.class.toString();",
+ " } finally {",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testInvalid() {
+ compilationHelper
+ .addSourceFile("/android/os/Binder.java")
+ .addSourceLines("FooService.java",
+ "import android.os.Binder;",
+ "public class FooService {",
+ " void noRestore() {",
+ " // BUG: Diagnostic contains:",
+ " final long token = Binder.clearCallingIdentity();",
+ " FooService.class.toString();",
+ " }",
+ " void noTry() {",
+ " // BUG: Diagnostic contains:",
+ " final long token = Binder.clearCallingIdentity();",
+ " FooService.class.toString();",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " void noImmediateTry() {",
+ " // BUG: Diagnostic contains:",
+ " final long token = Binder.clearCallingIdentity();",
+ " FooService.class.toString();",
+ " try {",
+ " FooService.class.toString();",
+ " } finally {",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " }",
+ " void noFinally() {",
+ " // BUG: Diagnostic contains:",
+ " final long token = Binder.clearCallingIdentity();",
+ " try {",
+ " FooService.class.toString();",
+ " } catch (Exception ignored) { }",
+ " }",
+ " void noFinal() {",
+ " // BUG: Diagnostic contains:",
+ " long token = Binder.clearCallingIdentity();",
+ " try {",
+ " FooService.class.toString();",
+ " } finally {",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " }",
+ " void noRecording() {",
+ " // BUG: Diagnostic contains:",
+ " Binder.clearCallingIdentity();",
+ " FooService.class.toString();",
+ " }",
+ " void noWork() {",
+ " // BUG: Diagnostic contains:",
+ " final long token = Binder.clearCallingIdentity();",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/CompatChangeCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/CompatChangeCheckerTest.java
new file mode 100644
index 000000000000..4625d43a1648
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/CompatChangeCheckerTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CompatChangeCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ CompatChangeChecker.class, getClass());
+ }
+
+ @Test
+ public void testSimple() {
+ compilationHelper
+ .addSourceFile("/android/os/Build.java")
+ .addSourceLines("Example.java",
+ "import android.os.Build;",
+ "public class Example {",
+ " void test(int targetSdkVersion) {",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion < Build.VERSION_CODES.S) { }",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion <= Build.VERSION_CODES.S) { }",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion > Build.VERSION_CODES.S) { }",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion >= Build.VERSION_CODES.S) { }",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion == Build.VERSION_CODES.S) { }",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion != Build.VERSION_CODES.S) { }",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testObscure() {
+ compilationHelper
+ .addSourceFile("/android/os/Build.java")
+ .addSourceLines("Example.java",
+ "import android.os.Build;",
+ "import static android.os.Build.VERSION_CODES.S;",
+ "public class Example {",
+ " void test(int targetSdkVersion) {",
+ " // BUG: Diagnostic contains:",
+ " boolean indirect = S >= targetSdkVersion;",
+ " // BUG: Diagnostic contains:",
+ " if (targetSdkVersion > Build.VERSION_CODES.R) { }",
+ " if (targetSdkVersion >= Build.VERSION_CODES.R) { }",
+ " if (targetSdkVersion < Build.VERSION_CODES.R) { }",
+ " if (targetSdkVersion <= Build.VERSION_CODES.R) { }",
+ " // BUG: Diagnostic contains:",
+ " if (Build.VERSION_CODES.R < targetSdkVersion) { }",
+ " if (Build.VERSION_CODES.R <= targetSdkVersion) { }",
+ " if (Build.VERSION_CODES.R > targetSdkVersion) { }",
+ " if (Build.VERSION_CODES.R >= targetSdkVersion) { }",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testIgnored() {
+ compilationHelper
+ .addSourceFile("/android/os/Build.java")
+ .addSourceLines("Example.java",
+ "import android.os.Build;",
+ "public class Example {",
+ " void test(int targetSdkVersion) {",
+ " if (targetSdkVersion < Build.VERSION_CODES.DONUT) { }",
+ " String result = \"test\" + Build.VERSION_CODES.S;",
+ " if (targetSdkVersion != Build.VERSION_CODES.CUR_DEVELOPMENT) { }",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ContextUserIdCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ContextUserIdCheckerTest.java
index c0b8cd745afc..c8772306a59b 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ContextUserIdCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ContextUserIdCheckerTest.java
@@ -92,4 +92,56 @@ public class ContextUserIdCheckerTest {
"}")
.doTest();
}
+
+ @Test
+ public void testDevicePolicyManager() {
+ compilationHelper
+ .addSourceFile("/android/annotation/SystemService.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/foo/IFooService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/RemoteException.java")
+ .addSourceLines("DevicePolicyManager.java",
+ "package android.app.admin;",
+ "import android.annotation.SystemService;",
+ "import android.content.Context;",
+ "import android.foo.IFooService;",
+ "import android.os.UserHandle;",
+ "import android.os.RemoteException;",
+ "@SystemService(\"dp\") public class DevicePolicyManager {",
+ " IFooService mService;",
+ " int myUserId() { return 0; }",
+ " void bar() throws RemoteException {",
+ " mService.baz(null, myUserId());",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testShortcutManager() {
+ compilationHelper
+ .addSourceFile("/android/annotation/SystemService.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/foo/IFooService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/RemoteException.java")
+ .addSourceLines("ShortcutManager.java",
+ "package android.content.pm;",
+ "import android.annotation.SystemService;",
+ "import android.content.Context;",
+ "import android.foo.IFooService;",
+ "import android.os.UserHandle;",
+ "import android.os.RemoteException;",
+ "@SystemService(\"shortcut\") public class ShortcutManager {",
+ " IFooService mService;",
+ " int injectMyUserId() { return 0; }",
+ " void bar() throws RemoteException {",
+ " mService.baz(null, injectMyUserId());",
+ " }",
+ "}")
+ .doTest();
+ }
}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsCheckerTest.java
new file mode 100644
index 000000000000..e128b6ad2cfd
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientCollectionsCheckerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EfficientCollectionsCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ EfficientCollectionsChecker.class, getClass());
+ }
+
+ @Test
+ public void testMap() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import java.util.HashMap;",
+ "public class Example {",
+ " public void exampleInteger() {",
+ " // BUG: Diagnostic contains:",
+ " HashMap<Integer, Integer> a = new HashMap<>();",
+ " // BUG: Diagnostic contains:",
+ " HashMap<Integer, Long> b = new HashMap<>();",
+ " // BUG: Diagnostic contains:",
+ " HashMap<Integer, Boolean> c = new HashMap<>();",
+ " // BUG: Diagnostic contains:",
+ " HashMap<Integer, String> d = new HashMap<>();",
+ " }",
+ " public void exampleLong() {",
+ " // BUG: Diagnostic contains:",
+ " HashMap<Long, String> res = new HashMap<>();",
+ " }",
+ " public void exampleOther() {",
+ " HashMap<String, String> res = new HashMap<>();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testList() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import java.util.ArrayList;",
+ "public class Example {",
+ " public void exampleInteger() {",
+ " // BUG: Diagnostic contains:",
+ " ArrayList<Integer> res = new ArrayList<>();",
+ " }",
+ " public void exampleLong() {",
+ " // BUG: Diagnostic contains:",
+ " ArrayList<Long> res = new ArrayList<>();",
+ " }",
+ " public void exampleOther() {",
+ " ArrayList<String> res = new ArrayList<>();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testErasure() {
+ compilationHelper
+ .addSourceLines("Example.java",
+ "import java.util.HashMap;",
+ "public class Example {",
+ " public void example() {",
+ " HashMap a = new HashMap();",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.java
new file mode 100644
index 000000000000..75c76e32f00e
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/ParcelablePerformanceCheckerTest.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 com.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ParcelablePerformanceCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ ParcelablePerformanceChecker.class, getClass());
+ }
+
+ @Test
+ public void testString() {
+ compilationHelper
+ .addSourceFile("/android/os/Parcel.java")
+ .addSourceFile("/android/os/Parcelable.java")
+ .addSourceLines("FooInfo.java",
+ "import android.os.Parcel;",
+ "import android.os.Parcelable;",
+ "public class FooInfo implements Parcelable {",
+ " public void writeToParcel(Parcel dest, int flags) {",
+ " // BUG: Diagnostic contains:",
+ " dest.writeString(toString());",
+ " dest.writeString8(toString());",
+ " // BUG: Diagnostic contains:",
+ " dest.writeStringArray(new String[0]);",
+ " dest.writeString8Array(new String[0]);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testSingle() {
+ compilationHelper
+ .addSourceFile("/android/os/Parcel.java")
+ .addSourceFile("/android/os/Parcelable.java")
+ .addSourceLines("FooInfo.java",
+ "import android.os.Parcel;",
+ "import android.os.Parcelable;",
+ "public class FooInfo implements Parcelable {",
+ " public void writeToParcel(Parcel dest, int flags) {",
+ " // BUG: Diagnostic contains:",
+ " dest.writeValue(this);",
+ " this.writeToParcel(dest, flags);",
+ " // BUG: Diagnostic contains:",
+ " dest.writeParcelable(this, flags);",
+ " this.writeToParcel(dest, flags);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testList() {
+ compilationHelper
+ .addSourceFile("/android/os/Parcel.java")
+ .addSourceFile("/android/os/Parcelable.java")
+ .addSourceLines("FooInfo.java",
+ "import android.os.Parcel;",
+ "import android.os.Parcelable;",
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "public class FooInfo implements Parcelable {",
+ " public void writeToParcel(Parcel dest, int flags) {",
+ " List<Parcelable> list = new ArrayList<Parcelable>();",
+ " Parcelable[] array = new Parcelable[0];",
+ " // BUG: Diagnostic contains:",
+ " dest.writeList(list);",
+ " dest.writeTypedList(list, flags);",
+ " // BUG: Diagnostic contains:",
+ " dest.writeParcelableList(list, flags);",
+ " dest.writeTypedList(list, flags);",
+ " // BUG: Diagnostic contains:",
+ " dest.writeParcelableArray(array, flags);",
+ " dest.writeTypedArray(array, flags);",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityCheckerTest.java
new file mode 100644
index 000000000000..a8badf6bdb0b
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/PendingIntentMutabilityCheckerTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PendingIntentMutabilityCheckerTest {
+ private CompilationTestHelper mCompilationHelper;
+
+ @Before
+ public void setUp() {
+ mCompilationHelper = CompilationTestHelper.newInstance(
+ PendingIntentMutabilityChecker.class, getClass());
+ }
+
+ @Test
+ public void testGetActivity() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_MUTABLE);",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_IMMUTABLE);",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getActivity(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_ONE_SHOT);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivity(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivity(context, 42, intent, 0);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetActivityAsUser() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_MUTABLE, null, null);",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_IMMUTABLE, null, null);",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " PendingIntent.getActivityAsUser(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivityAsUser(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivityAsUser(context, 42, intent, 0, null, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetActivities() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent[] intents;",
+ " void example() {",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_MUTABLE, null);",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_IMMUTABLE, null);",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null);",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null);",
+ " PendingIntent.getActivities(context, 42, intents, 0 | PendingIntent.FLAG_MUTABLE, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_ONE_SHOT, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivities(context, 42, intents, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivities(context, 42, intents, 0, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetActivitiesAsUser() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent[] intents;",
+ " void example() {",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_MUTABLE, null, null);",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_IMMUTABLE, null, null);",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, 0 | PendingIntent.FLAG_MUTABLE, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_ONE_SHOT, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE, null, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getActivitiesAsUser(context, 42, intents, 0, null, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+
+ @Test
+ public void testGetBroadcast() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_MUTABLE);",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_IMMUTABLE);",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getBroadcast(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_ONE_SHOT);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcast(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcast(context, 42, intent, 0);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetBroadcastAsUser() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_MUTABLE, null);",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_IMMUTABLE, null);",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null);",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT, null);",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_ONE_SHOT, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE, null);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getBroadcastAsUser(context, 42, intent, 0, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetService() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_MUTABLE);",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_IMMUTABLE);",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getService(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_ONE_SHOT);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getService(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getService(context, 42, intent, 0);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testGetForegroundService() {
+ mCompilationHelper
+ .addSourceFile("/android/app/PendingIntent.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceFile("/android/os/UserHandle.java")
+ .addSourceFile("/android/os/Bundle.java")
+ .addSourceLines("Example.java",
+ "import android.app.PendingIntent;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public class Example {",
+ " Context context;",
+ " Intent intent;",
+ " void example() {",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_MUTABLE);",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_IMMUTABLE);",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);",
+ " PendingIntent.getForegroundService(context, 42, intent, 0 | PendingIntent.FLAG_MUTABLE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_ONE_SHOT);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getForegroundService(context, 42, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_NO_CREATE);",
+ " // BUG: Diagnostic contains:",
+ " PendingIntent.getForegroundService(context, 42, intent, 0);",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemCheckerTest.java
index 32efbf206a45..0943bd65c06f 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RethrowFromSystemCheckerTest.java
@@ -105,4 +105,27 @@ public class RethrowFromSystemCheckerTest {
"}")
.doTest();
}
+
+ @Test
+ public void testTelephony() {
+ compilationHelper
+ .addSourceFile("/android/annotation/SystemService.java")
+ .addSourceFile("/com/android/internal/telephony/ITelephony.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceFile("/android/os/RemoteException.java")
+ .addSourceLines("TelephonyManager.java",
+ "import android.annotation.SystemService;",
+ "import com.android.internal.telephony.ITelephony;",
+ "import android.os.RemoteException;",
+ "@SystemService(\"telephony\") public class TelephonyManager {",
+ " ITelephony mService;",
+ " void bar() {",
+ " try {",
+ " mService.bar();",
+ " } catch (RemoteException ignored) {",
+ " }",
+ " }",
+ "}")
+ .doTest();
+ }
}
diff --git a/errorprone/tests/res/android/app/PendingIntent.java b/errorprone/tests/res/android/app/PendingIntent.java
new file mode 100644
index 000000000000..a0cdedfd1d7a
--- /dev/null
+++ b/errorprone/tests/res/android/app/PendingIntent.java
@@ -0,0 +1,69 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+public class PendingIntent {
+ public static final int FLAG_ONE_SHOT = 1<<30;
+ public static final int FLAG_IMMUTABLE = 1<<26;
+ public static final int FLAG_MUTABLE = 1<<25;
+ public static final int FLAG_NO_CREATE = 1<<29;
+
+ public static PendingIntent getActivity(Context context, int requestCode,
+ Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getActivityAsUser(Context context, int requestCode,
+ Intent intent, int flags, Bundle options, UserHandle user) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getActivities(Context context, int requestCode,
+ Intent[] intents, int flags, Bundle options) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getActivitiesAsUser(Context context, int requestCode,
+ Intent[] intents, int flags, Bundle options, UserHandle user) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getBroadcast(Context context, int requestCode,
+ Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
+ Intent intent, int flags, UserHandle userHandle) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getService(Context context, int requestCode,
+ Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static PendingIntent getForegroundService(Context context, int requestCode,
+ Intent intent, int flags) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/content/Intent.java b/errorprone/tests/res/android/content/Intent.java
new file mode 100644
index 000000000000..9d22d04b8cb8
--- /dev/null
+++ b/errorprone/tests/res/android/content/Intent.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public class Intent {
+}
diff --git a/errorprone/tests/res/android/os/Binder.java b/errorprone/tests/res/android/os/Binder.java
index d388587c2f58..c969108bd425 100644
--- a/errorprone/tests/res/android/os/Binder.java
+++ b/errorprone/tests/res/android/os/Binder.java
@@ -20,4 +20,12 @@ public class Binder {
public static int getCallingUid() {
throw new UnsupportedOperationException();
}
+
+ public static long clearCallingIdentity() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static void restoreCallingIdentity(long token) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/errorprone/tests/res/android/os/Build.java b/errorprone/tests/res/android/os/Build.java
index bbf7ef2172b5..2d354e2ac370 100644
--- a/errorprone/tests/res/android/os/Build.java
+++ b/errorprone/tests/res/android/os/Build.java
@@ -18,6 +18,9 @@ package android.os;
public class Build {
public static class VERSION_CODES {
+ public static final int CUR_DEVELOPMENT = 10000;
public static final int DONUT = 4;
+ public static final int R = 30;
+ public static final int S = CUR_DEVELOPMENT;
}
}
diff --git a/errorprone/tests/res/android/os/Bundle.java b/errorprone/tests/res/android/os/Bundle.java
new file mode 100644
index 000000000000..6d2f7b899502
--- /dev/null
+++ b/errorprone/tests/res/android/os/Bundle.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public class Bundle {
+}
diff --git a/errorprone/tests/res/android/os/Parcel.java b/errorprone/tests/res/android/os/Parcel.java
new file mode 100644
index 000000000000..bafa23626fb2
--- /dev/null
+++ b/errorprone/tests/res/android/os/Parcel.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import java.util.List;
+
+public class Parcel {
+ public void writeString(String val) {
+ throw new UnsupportedOperationException();
+ }
+ public void writeString8(String val) {
+ throw new UnsupportedOperationException();
+ }
+ public final void writeStringArray(String[] val) {
+ throw new UnsupportedOperationException();
+ }
+ public final void writeString8Array(String[] val) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final void writeValue(Object v) {
+ throw new UnsupportedOperationException();
+ }
+ public final void writeParcelable(Parcelable p, int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final void writeList(List val) {
+ throw new UnsupportedOperationException();
+ }
+ public final <T extends Parcelable> void writeParcelableList(List<T> val, int flags) {
+ throw new UnsupportedOperationException();
+ }
+ public <T extends Parcelable> void writeTypedList(List<T> val, int flags) {
+ throw new UnsupportedOperationException();
+ }
+ public final <T extends Parcelable> void writeParcelableArray(T[] value, int flags) {
+ throw new UnsupportedOperationException();
+ }
+ public final <T extends Parcelable> void writeTypedArray(T[] val, int flags) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/errorprone/tests/res/android/os/Parcelable.java b/errorprone/tests/res/android/os/Parcelable.java
new file mode 100644
index 000000000000..217690d69fd6
--- /dev/null
+++ b/errorprone/tests/res/android/os/Parcelable.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface Parcelable {
+ public void writeToParcel(Parcel dest, int flags);
+}
diff --git a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl b/errorprone/tests/res/com/android/internal/telephony/ITelephony.java
index 6d0fe72b9de1..61c4dd561b0b 100644
--- a/core/java/android/app/timezonedetector/ITimeZoneConfigurationListener.aidl
+++ b/errorprone/tests/res/com/android/internal/telephony/ITelephony.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.app.timezonedetector;
+package com.android.internal.telephony;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.os.RemoteException;
-/** {@hide} */
-oneway interface ITimeZoneConfigurationListener {
- void onChange();
-} \ No newline at end of file
+public interface ITelephony extends android.os.IInterface {
+ public void bar() throws RemoteException;
+}
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 4c7e960eb0a4..4c0f890eee40 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -24,26 +24,30 @@ import android.view.SurfaceControl;
*/
public final class BLASTBufferQueue {
// Note: This field is accessed by native code.
- private long mNativeObject; // BLASTBufferQueue*
+ public long mNativeObject; // BLASTBufferQueue*
- private static native long nativeCreate(long surfaceControl, long width, long height,
- boolean tripleBufferingEnabled);
+ private static native long nativeCreate(String name, long surfaceControl, long width,
+ long height, boolean tripleBufferingEnabled);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr);
private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height);
/** Create a new connection with the surface flinger. */
- public BLASTBufferQueue(SurfaceControl sc, int width, int height,
+ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
boolean tripleBufferingEnabled) {
- mNativeObject = nativeCreate(sc.mNativeObject, width, height, tripleBufferingEnabled);
+ mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, tripleBufferingEnabled);
}
public void destroy() {
nativeDestroy(mNativeObject);
+ mNativeObject = 0;
}
- public Surface getSurface() {
+ /**
+ * @return a new Surface instance from the IGraphicsBufferProducer of the adapter.
+ */
+ public Surface createSurface() {
return nativeGetSurface(mNativeObject);
}
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index bee8d5efc933..05df250fa6b9 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -18,11 +18,13 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas.VertexMode;
+import android.graphics.fonts.Font;
import android.graphics.text.MeasuredText;
import android.text.GraphicsOperations;
import android.text.MeasuredParagraph;
@@ -31,6 +33,10 @@ import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
/**
* This class is a base class for Canvas's drawing operations. Any modifications here
* should be accompanied by a similar modification to {@link BaseRecordingCanvas}.
@@ -443,6 +449,58 @@ public abstract class BaseCanvas {
paint.getNativeInstance());
}
+ /**
+ * Draw array of glyphs with specified font.
+ *
+ * @param glyphIds Array of glyph IDs. The length of array must be greater than or equal to
+ * {@code glyphStart + glyphCount}.
+ * @param glyphIdOffset Number of elements to skip before drawing in <code>glyphIds</code>
+ * array.
+ * @param positions A flattened X and Y position array. The first glyph X position must be
+ * stored at {@code positionOffset}. The first glyph Y position must be stored
+ * at {@code positionOffset + 1}, then the second glyph X position must be
+ * stored at {@code positionOffset + 2}.
+ * The length of array must be greater than or equal to
+ * {@code positionOffset + glyphCount * 2}.
+ * @param positionOffset Number of elements to skip before drawing in {@code positions}.
+ * The first glyph X position must be stored at {@code positionOffset}.
+ * The first glyph Y position must be stored at
+ * {@code positionOffset + 1}, then the second glyph X position must be
+ * stored at {@code positionOffset + 2}.
+ * @param glyphCount Number of glyphs to be drawn.
+ * @param font Font used for drawing.
+ * @param paint Paint used for drawing. The typeface set to this paint is ignored.
+ *
+ * @see android.graphics.text.TextShaper
+ * @see android.text.StyledTextShaper
+ */
+ public void drawGlyphs(
+ @NonNull int[] glyphIds,
+ @IntRange(from = 0) int glyphIdOffset,
+ @NonNull float[] positions,
+ @IntRange(from = 0) int positionOffset,
+ @IntRange(from = 0) int glyphCount,
+ @NonNull Font font,
+ @NonNull Paint paint) {
+ Objects.requireNonNull(glyphIds, "glyphIds must not be null.");
+ Objects.requireNonNull(positions, "positions must not be null.");
+ Objects.requireNonNull(font, "font must not be null.");
+ Objects.requireNonNull(paint, "paint must not be null.");
+ Preconditions.checkArgumentNonnegative(glyphCount);
+
+ if (glyphIdOffset < 0 || glyphIdOffset + glyphCount > glyphIds.length) {
+ throw new IndexOutOfBoundsException(
+ "glyphIds must have at least " + (glyphIdOffset + glyphCount) + " of elements");
+ }
+ if (positionOffset < 0 || positionOffset + glyphCount * 2 > positions.length) {
+ throw new IndexOutOfBoundsException(
+ "positions must have at least " + (positionOffset + glyphCount * 2)
+ + " of elements");
+ }
+ nDrawGlyphs(mNativeCanvasWrapper, glyphIds, positions, glyphIdOffset, positionOffset,
+ glyphCount, font.getNativePtr(), paint.getNativeInstance());
+ }
+
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
@NonNull Paint paint) {
if ((index | count | (index + count) |
@@ -734,6 +792,9 @@ public abstract class BaseCanvas {
int vertOffset, float[] texs, int texOffset, int[] colors, int colorOffset,
short[] indices, int indexOffset, int indexCount, long nativePaint);
+ private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions,
+ int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint);
+
private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
float x, float y, int flags, long nativePaint);
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 2f5214cb1df5..a8922e84514a 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -18,9 +18,11 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
+import android.graphics.fonts.Font;
import android.graphics.text.MeasuredText;
import android.text.GraphicsOperations;
import android.text.MeasuredParagraph;
@@ -29,8 +31,12 @@ import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
import dalvik.annotation.optimization.FastNative;
+import java.util.Objects;
+
/**
* This class is a base class for canvases that defer drawing operations, so all
* the draw operations can be marked @FastNative. It contains a re-implementation of
@@ -409,6 +415,34 @@ public class BaseRecordingCanvas extends Canvas {
}
@Override
+ public void drawGlyphs(
+ @NonNull int[] glyphIds,
+ @IntRange(from = 0) int glyphIdOffset,
+ @NonNull float[] positions,
+ @IntRange(from = 0) int positionOffset,
+ @IntRange(from = 0) int glyphCount,
+ @NonNull Font font,
+ @NonNull Paint paint) {
+ Objects.requireNonNull(glyphIds, "glyphIds must not be null.");
+ Objects.requireNonNull(positions, "positions must not be null.");
+ Objects.requireNonNull(font, "font must not be null.");
+ Objects.requireNonNull(paint, "paint must not be null.");
+ Preconditions.checkArgumentNonnegative(glyphCount);
+
+ if (glyphIdOffset < 0 || glyphIdOffset + glyphCount > glyphIds.length) {
+ throw new IndexOutOfBoundsException(
+ "glyphIds must have at least " + (glyphIdOffset + glyphCount) + " of elements");
+ }
+ if (positionOffset < 0 || positionOffset + glyphCount * 2 > positions.length) {
+ throw new IndexOutOfBoundsException(
+ "positions must have at least " + (positionOffset + glyphCount * 2)
+ + " of elements");
+ }
+ nDrawGlyphs(mNativeCanvasWrapper, glyphIds, positions, glyphIdOffset, positionOffset,
+ glyphCount, font.getNativePtr(), paint.getNativeInstance());
+ }
+
+ @Override
public final void drawText(@NonNull char[] text, int index, int count, float x, float y,
@NonNull Paint paint) {
if ((index | count | (index + count)
@@ -674,6 +708,10 @@ public class BaseRecordingCanvas extends Canvas {
short[] indices, int indexOffset, int indexCount, long nativePaint);
@FastNative
+ private static native void nDrawGlyphs(long nativeCanvas, int[] glyphIds, float[] positions,
+ int glyphIdStart, int positionStart, int glyphCount, long nativeFont, long nativePaint);
+
+ @FastNative
private static native void nDrawText(long nativeCanvas, char[] text, int index, int count,
float x, float y, int flags, long nativePaint);
diff --git a/graphics/java/android/graphics/BlurShader.java b/graphics/java/android/graphics/BlurShader.java
index 779a89051060..3bc811983336 100644
--- a/graphics/java/android/graphics/BlurShader.java
+++ b/graphics/java/android/graphics/BlurShader.java
@@ -16,6 +16,7 @@
package android.graphics;
+import android.annotation.NonNull;
import android.annotation.Nullable;
/**
@@ -28,6 +29,7 @@ public final class BlurShader extends Shader {
private final float mRadiusX;
private final float mRadiusY;
private final Shader mInputShader;
+ private final TileMode mEdgeTreatment;
private long mNativeInputShader = 0;
@@ -35,22 +37,42 @@ public final class BlurShader extends Shader {
* Create a {@link BlurShader} that blurs the contents of the optional input shader
* with the specified radius along the x and y axis. If no input shader is provided
* then all drawing commands issued with a {@link android.graphics.Paint} that this
- * shader is installed in will be blurred
+ * shader is installed in will be blurred.
+ *
+ * This uses a default {@link TileMode#DECAL} for edge treatment
+ *
* @param radiusX Radius of blur along the X axis
* @param radiusY Radius of blur along the Y axis
* @param inputShader Input shader that provides the content to be blurred
*/
public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader) {
+ this(radiusX, radiusY, inputShader, TileMode.DECAL);
+ }
+
+ /**
+ * Create a {@link BlurShader} that blurs the contents of the optional input shader
+ * with the specified radius along the x and y axis. If no input shader is provided
+ * then all drawing commands issued with a {@link android.graphics.Paint} that this
+ * shader is installed in will be blurred
+ * @param radiusX Radius of blur along the X axis
+ * @param radiusY Radius of blur along the Y axis
+ * @param inputShader Input shader that provides the content to be blurred
+ * @param edgeTreatment Policy for how to blur content near edges of the blur shader
+ */
+ public BlurShader(float radiusX, float radiusY, @Nullable Shader inputShader,
+ @NonNull TileMode edgeTreatment) {
mRadiusX = radiusX;
mRadiusY = radiusY;
mInputShader = inputShader;
+ mEdgeTreatment = edgeTreatment;
}
/** @hide **/
@Override
protected long createNativeInstance(long nativeMatrix) {
mNativeInputShader = mInputShader != null ? mInputShader.getNativeInstance() : 0;
- return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader);
+ return nativeCreate(nativeMatrix, mRadiusX, mRadiusY, mNativeInputShader,
+ mEdgeTreatment.nativeInt);
}
/** @hide **/
@@ -61,5 +83,5 @@ public final class BlurShader extends Shader {
}
private static native long nativeCreate(long nativeMatrix, float radiusX, float radiusY,
- long inputShader);
+ long inputShader, int edgeTreatment);
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 559571cb0271..5e07d156a06a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -19,10 +19,12 @@ package android.graphics;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.fonts.Font;
import android.graphics.text.MeasuredText;
import android.os.Build;
@@ -2048,6 +2050,43 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Draw array of glyphs with specified font.
+ *
+ * @param glyphIds Array of glyph IDs. The length of array must be greater than or equal to
+ * {@code glyphStart + glyphCount}.
+ * @param glyphIdOffset Number of elements to skip before drawing in <code>glyphIds</code>
+ * array.
+ * @param positions A flattened X and Y position array. The first glyph X position must be
+ * stored at {@code positionOffset}. The first glyph Y position must be stored
+ * at {@code positionOffset + 1}, then the second glyph X position must be
+ * stored at {@code positionOffset + 2}.
+ * The length of array must be greater than or equal to
+ * {@code positionOffset + glyphCount * 2}.
+ * @param positionOffset Number of elements to skip before drawing in {@code positions}.
+ * The first glyph X position must be stored at {@code positionOffset}.
+ * The first glyph Y position must be stored at
+ * {@code positionOffset + 1}, then the second glyph X position must be
+ * stored at {@code positionOffset + 2}.
+ * @param glyphCount Number of glyphs to be drawn.
+ * @param font Font used for drawing.
+ * @param paint Paint used for drawing. The typeface set to this paint is ignored.
+ *
+ * @see android.graphics.text.TextShaper
+ * @see android.text.StyledTextShaper
+ */
+ public void drawGlyphs(
+ @NonNull int[] glyphIds,
+ @IntRange(from = 0) int glyphIdOffset,
+ @NonNull float[] positions,
+ @IntRange(from = 0) int positionOffset,
+ @IntRange(from = 0) int glyphCount,
+ @NonNull Font font,
+ @NonNull Paint paint) {
+ super.drawGlyphs(glyphIds, glyphIdOffset, positions, positionOffset, glyphCount, font,
+ paint);
+ }
+
+ /**
* Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
* based on the Align setting in the paint.
*
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 163823fde9f7..0061ea13f647 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -40,7 +40,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public final class FrameInfo {
- public long[] frameInfo = new long[10];
+ public long[] frameInfo = new long[FRAME_INFO_SIZE];
// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;
@@ -52,6 +52,7 @@ public final class FrameInfo {
public static final long FLAG_SURFACE_CANVAS = 1 << 2;
// An invalid vsync id to be used when FRAME_TIMELINE_VSYNC_ID is unknown
+ // Needs to be in sync with android::ISurfaceComposer::INVALID_VSYNC_ID in native code
public static final long INVALID_VSYNC_ID = -1;
@LongDef(flag = true, value = {
@@ -86,14 +87,22 @@ public final class FrameInfo {
// When View:draw() started
private static final int DRAW_START = 9;
+ // When the frame needs to be ready by
+ private static final int FRAME_DEADLINE = 10;
+
+ // Must be the last one
+ private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
+
/** checkstyle */
- public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId) {
+ public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
+ long frameDeadline) {
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
frameInfo[INTENDED_VSYNC] = intendedVsync;
frameInfo[VSYNC] = usedVsync;
frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
frameInfo[NEWEST_INPUT_EVENT] = 0;
frameInfo[FLAGS] = 0;
+ frameInfo[FRAME_DEADLINE] = frameDeadline;
}
/** checkstyle */
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index 2d6848b618a1..dc785c5b0309 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -175,7 +175,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub {
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
ParcelFileDescriptor pfd = null;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mAppOps.checkPackage(uid, packageName);
PackageInfo info = mContext.getPackageManager().getPackageInfoAsUser(
@@ -214,7 +214,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub {
}
}
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
pullGraphicsStatsImpl(lastFullDay, pulledData);
} finally {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index fd5916c2158d..a7f2739153e1 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -355,7 +355,7 @@ public class HardwareRenderer {
*/
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
// TODO(b/168552873): populate vsync Id once available to Choreographer public API
- mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID);
+ mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE);
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
return this;
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 964640b106b9..28d7911c771f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -3111,7 +3111,7 @@ public class Paint {
@CriticalNative
private static native boolean nGetFillPath(long paintPtr, long src, long dst);
@CriticalNative
- private static native void nSetShader(long paintPtr, long shader);
+ private static native long nSetShader(long paintPtr, long shader);
@CriticalNative
private static native long nSetColorFilter(long paintPtr, long filter);
@CriticalNative
diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java
index f9033a53d7e6..326084924450 100644
--- a/graphics/java/android/graphics/ParcelableColorSpace.java
+++ b/graphics/java/android/graphics/ParcelableColorSpace.java
@@ -25,8 +25,6 @@ import android.os.Parcelable;
* A {@link Parcelable} {@link ColorSpace}. In order to enable parceling, the ColorSpace
* must be either a {@link ColorSpace.Named Named} ColorSpace or a {@link ColorSpace.Rgb} instance
* that has an ICC parametric transfer function as returned by {@link Rgb#getTransferParameters()}.
- * TODO: Make public
- * @hide
*/
public final class ParcelableColorSpace extends ColorSpace implements Parcelable {
private final ColorSpace mColorSpace;
@@ -75,6 +73,9 @@ public final class ParcelableColorSpace extends ColorSpace implements Parcelable
}
}
+ /**
+ * @return the backing ColorSpace that this ParcelableColorSpace is wrapping.
+ */
public @NonNull ColorSpace getColorSpace() {
return mColorSpace;
}
diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java
new file mode 100644
index 000000000000..9fc0c8eb9d90
--- /dev/null
+++ b/graphics/java/android/graphics/RenderEffect.java
@@ -0,0 +1,145 @@
+/*
+ * 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.graphics;
+
+import android.annotation.NonNull;
+import android.graphics.Shader.TileMode;
+
+import libcore.util.NativeAllocationRegistry;
+
+/**
+ * Intermediate rendering step used to render drawing commands with a corresponding
+ * visual effect
+ *
+ * @hide
+ */
+public final class RenderEffect {
+
+ private static class RenderEffectHolder {
+ public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ RenderEffect.class.getClassLoader(), nativeGetFinalizer());
+ }
+
+ /**
+ * Create a {@link RenderEffect} instance that will offset the drawing content
+ * by the provided x and y offset.
+ * @param offsetX offset along the x axis in pixels
+ * @param offsetY offset along the y axis in pixels
+ */
+ @NonNull
+ public static RenderEffect createOffsetEffect(float offsetX, float offsetY) {
+ return new RenderEffect(nativeCreateOffsetEffect(offsetX, offsetY, 0));
+ }
+
+ /**
+ * Create a {@link RenderEffect} instance with the provided x and y offset
+ * @param offsetX offset along the x axis in pixels
+ * @param offsetY offset along the y axis in pixels
+ * @param input target RenderEffect used to render in the offset coordinates.
+ */
+ @NonNull
+ public static RenderEffect createOffsetEffect(
+ float offsetX,
+ float offsetY,
+ @NonNull RenderEffect input
+ ) {
+ return new RenderEffect(nativeCreateOffsetEffect(
+ offsetX,
+ offsetY,
+ input.getNativeInstance()
+ )
+ );
+ }
+
+ /**
+ * Create a {@link RenderEffect} that blurs the contents of the optional input RenderEffect
+ * with the specified radius along the x and y axis. If no input RenderEffect is provided
+ * then all drawing commands issued with a {@link android.graphics.RenderNode} that this
+ * RenderEffect is installed in will be blurred
+ * @param radiusX Radius of blur along the X axis
+ * @param radiusY Radius of blur along the Y axis
+ * @param inputEffect Input RenderEffect that provides the content to be blurred, can be null
+ * to indicate that the drawing commands on the RenderNode are to be
+ * blurred instead of the input RenderEffect
+ * @param edgeTreatment Policy for how to blur content near edges of the blur kernel
+ */
+ @NonNull
+ public static RenderEffect createBlurEffect(
+ float radiusX,
+ float radiusY,
+ @NonNull RenderEffect inputEffect,
+ @NonNull TileMode edgeTreatment
+ ) {
+ long nativeInputEffect = inputEffect != null ? inputEffect.mNativeRenderEffect : 0;
+ return new RenderEffect(
+ nativeCreateBlurEffect(
+ radiusX,
+ radiusY,
+ nativeInputEffect,
+ edgeTreatment.nativeInt
+ )
+ );
+ }
+
+ /**
+ * Create a {@link RenderEffect} that blurs the contents of the
+ * {@link android.graphics.RenderNode} that this RenderEffect is installed on with the
+ * specified radius along hte x and y axis.
+ * @param radiusX Radius of blur along the X axis
+ * @param radiusY Radius of blur along the Y axis
+ * @param edgeTreatment Policy for how to blur content near edges of the blur kernel
+ */
+ @NonNull
+ public static RenderEffect createBlurEffect(
+ float radiusX,
+ float radiusY,
+ @NonNull TileMode edgeTreatment
+ ) {
+ return new RenderEffect(
+ nativeCreateBlurEffect(
+ radiusX,
+ radiusY,
+ 0,
+ edgeTreatment.nativeInt
+ )
+ );
+ }
+
+ private final long mNativeRenderEffect;
+
+ /* only constructed from static factory methods */
+ private RenderEffect(long nativeRenderEffect) {
+ mNativeRenderEffect = nativeRenderEffect;
+ RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation(
+ this, mNativeRenderEffect);
+ }
+
+ /**
+ * Obtain the pointer to the underlying RenderEffect to be configured
+ * on a RenderNode object via {@link RenderNode#setRenderEffect(RenderEffect)}
+ */
+ /* package */ long getNativeInstance() {
+ return mNativeRenderEffect;
+ }
+
+ private static native long nativeCreateOffsetEffect(
+ float offsetX, float offsetY, long nativeInput);
+ private static native long nativeCreateBlurEffect(
+ float radiusX, float radiusY, long nativeInput, int edgeTreatment);
+ private static native long nativeGetFinalizer();
+}
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 8aacbc7bc109..d812c1a5595d 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -850,6 +850,23 @@ public final class RenderNode {
}
/**
+ * Configure the {@link android.graphics.RenderEffect} to apply to this RenderNode. This
+ * will apply a visual effect to the end result of the contents of this RenderNode before
+ * it is drawn into the destination. For example if
+ * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
+ * is provided, the contents will be drawn in a separate layer, then this layer will
+ * be blurred when this RenderNode is drawn into the destination.
+ * @param renderEffect to be applied to the RenderNode. Passing null clears all previously
+ * configured RenderEffects
+ *
+ * @hide
+ */
+ public void setRenderEffect(@Nullable RenderEffect renderEffect) {
+ nSetRenderEffect(mNativeRenderNode,
+ renderEffect != null ? renderEffect.getNativeInstance() : 0);
+ }
+
+ /**
* Returns the translucency level of this display list.
*
* @return A value between 0.0f and 1.0f
@@ -1655,6 +1672,9 @@ public final class RenderNode {
private static native boolean nSetAlpha(long renderNode, float alpha);
@CriticalNative
+ private static native void nSetRenderEffect(long renderNode, long renderEffect);
+
+ @CriticalNative
private static native boolean nSetHasOverlappingRendering(long renderNode,
boolean hasOverlappingRendering);
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 5a0b4a9b8086..63089e2d9d98 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -34,6 +34,7 @@ public class RuntimeShader extends Shader {
}
private byte[] mUniforms;
+ private Shader[] mInputShaders;
private boolean mIsOpaque;
/**
@@ -50,13 +51,30 @@ public class RuntimeShader extends Shader {
* @param isOpaque True if all pixels have alpha 1.0f.
*/
public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque) {
- this(sksl, uniforms, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
+ this(sksl, uniforms, null, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
}
- private RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms, boolean isOpaque,
- ColorSpace colorSpace) {
+ /**
+ * Creates a new RuntimeShader.
+ *
+ * @param sksl The text of SKSL program to run on the GPU.
+ * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
+ * on number of uniforms declared by sksl.
+ * @param shaderInputs Array of shaders passed to the SKSL shader. Array size depends
+ * on the number of input shaders declared in the sksl
+ * @param isOpaque True if all pixels have alpha 1.0f.
+ */
+ public RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms,
+ @Nullable Shader[] shaderInputs, boolean isOpaque) {
+ this(sksl, uniforms, shaderInputs, isOpaque, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+
+ private RuntimeShader(@NonNull String sksl, @Nullable byte[] uniforms,
+ @Nullable Shader[] shaderInputs, boolean isOpaque,
+ ColorSpace colorSpace) {
super(colorSpace);
mUniforms = uniforms;
+ mInputShaders = shaderInputs;
mIsOpaque = isOpaque;
mNativeInstanceRuntimeShaderFactory = nativeCreateShaderFactory(sksl);
NoImagePreloadHolder.sRegistry.registerNativeAllocation(this,
@@ -74,15 +92,31 @@ public class RuntimeShader extends Shader {
discardNativeInstance();
}
+ /**
+ * Sets new values for the shaders that serve as inputs to this shader.
+ *
+ * @param shaderInputs Array of Shaders passed into the SKSL shader. Array size depends
+ * on number of input shaders declared by sksl.
+ */
+ public void updateInputShaders(@Nullable Shader[] shaderInputs) {
+ mInputShaders = shaderInputs;
+ discardNativeInstance();
+ }
+
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix) {
+ long[] nativeShaders = mInputShaders.length > 0 ? new long[mInputShaders.length] : null;
+ for (int i = 0; i < mInputShaders.length; i++) {
+ nativeShaders[i] = mInputShaders[i].getNativeInstance();
+ }
+
return nativeCreate(mNativeInstanceRuntimeShaderFactory, nativeMatrix, mUniforms,
- colorSpace().getNativeInstance(), mIsOpaque);
+ nativeShaders, colorSpace().getNativeInstance(), mIsOpaque);
}
private static native long nativeCreate(long shaderFactory, long matrix, byte[] inputs,
- long colorSpaceHandle, boolean isOpaque);
+ long[] shaderInputs, long colorSpaceHandle, boolean isOpaque);
private static native long nativeCreateShaderFactory(String sksl);
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 8154ebf1e508..d71ff1138b25 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -95,7 +95,11 @@ public class Shader {
* repeat the shader's image horizontally and vertically, alternating
* mirror images so that adjacent images always seam
*/
- MIRROR (2);
+ MIRROR(2),
+ /**
+ * Only draw within the original domain, return transparent-black everywhere else
+ */
+ DECAL(3);
TileMode(int nativeInt) {
this.nativeInt = nativeInt;
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index b09082e65ca4..97cd8ab6cae9 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -22,13 +22,19 @@ import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.RectF;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.LongSparseArray;
import android.util.TypedValue;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
@@ -37,6 +43,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
@@ -53,6 +60,15 @@ public final class Font {
private static final int STYLE_ITALIC = 1;
private static final int STYLE_NORMAL = 0;
+ private static final Object MAP_LOCK = new Object();
+ // We need to have mapping from native ptr to Font object for later accessing from TextShape
+ // result since Typeface doesn't have reference to Font object and it is not always created from
+ // Font object. Sometimes Typeface is created in native layer only and there might not be Font
+ // object in Java layer. So, if not found in this cache, create new Font object for API user.
+ @GuardedBy("MAP_LOCK")
+ private static final LongSparseArray<WeakReference<Font>> FONT_PTR_MAP =
+ new LongSparseArray<>();
+
/**
* A builder class for creating new Font.
*/
@@ -63,6 +79,7 @@ public final class Font {
private @Nullable ByteBuffer mBuffer;
private @Nullable File mFile;
+ private @Nullable Font mFont;
private @NonNull String mLocaleList = "";
private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED;
private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED;
@@ -204,6 +221,22 @@ public final class Font {
}
/**
+ * Constructs a builder from existing Font instance.
+ *
+ * @param font the font instance.
+ */
+ public Builder(@NonNull Font font) {
+ mFont = font;
+ // Copies all parameters as a default value.
+ mBuffer = font.getBuffer();
+ mWeight = font.getStyle().getWeight();
+ mItalic = font.getStyle().getSlant();
+ mAxes = font.getAxes();
+ mFile = font.getFile();
+ mTtcIndex = font.getTtcIndex();
+ }
+
+ /**
* Creates a buffer containing font data using the assetManager and other
* provided inputs.
*
@@ -430,8 +463,13 @@ public final class Font {
}
final ByteBuffer readonlyBuffer = mBuffer.asReadOnlyBuffer();
final String filePath = mFile == null ? "" : mFile.getAbsolutePath();
- final long ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic,
- mTtcIndex);
+
+ long ptr;
+ if (mFont == null) {
+ ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic, mTtcIndex);
+ } else {
+ ptr = nClone(mFont.getNativePtr(), builderPtr, mWeight, italic, mTtcIndex);
+ }
final Font font = new Font(ptr, readonlyBuffer, mFile,
new FontStyle(mWeight, slant), mTtcIndex, mAxes, mLocaleList);
sFontRegistry.registerNativeAllocation(font, ptr);
@@ -449,6 +487,10 @@ public final class Font {
boolean italic, int ttcIndex);
@CriticalNative
private static native long nGetReleaseNativeFont();
+
+ @FastNative
+ private static native long nClone(long fontPtr, long builderPtr, int weight,
+ boolean italic, int ttcIndex);
}
private final long mNativePtr; // address of the shared ptr of minikin::Font
@@ -472,6 +514,10 @@ public final class Font {
mTtcIndex = ttcIndex;
mAxes = axes;
mLocaleList = localeList;
+
+ synchronized (MAP_LOCK) {
+ FONT_PTR_MAP.append(nGetNativeFontPtr(mNativePtr), new WeakReference<>(this));
+ }
}
/**
@@ -538,6 +584,40 @@ public final class Font {
return LocaleList.forLanguageTags(mLocaleList);
}
+ /**
+ * Retrieve the glyph horizontal advance and bounding box.
+ *
+ * Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
+ *
+ * @param glyphId a glyph ID
+ * @param paint a paint object used for resolving glyph style
+ * @param outBoundingBox a nullable destination object. If null is passed, this function just
+ * return the horizontal advance. If non-null is passed, this function
+ * fills bounding box information to this object.
+ * @return the amount of horizontal advance in pixels
+ */
+ public float getGlyphBounds(@IntRange(from = 0) int glyphId, @NonNull Paint paint,
+ @Nullable RectF outBoundingBox) {
+ return nGetGlyphBounds(mNativePtr, glyphId, paint.getNativeInstance(), outBoundingBox);
+ }
+
+ /**
+ * Retrieve the font metrics information.
+ *
+ * Note that {@link android.graphics.Typeface} in {@link android.graphics.Paint} is ignored.
+ *
+ * @param paint a paint object used for retrieving font metrics.
+ * @param outMetrics a nullable destination object. If null is passed, this function only
+ * retrieve recommended interline spacing. If non-null is passed, this function
+ * fills to font metrics to it.
+ *
+ * @see Paint#getFontMetrics()
+ * @see Paint#getFontMetricsInt()
+ */
+ public void getMetrics(@NonNull Paint paint, @Nullable Paint.FontMetrics outMetrics) {
+ nGetFontMetrics(mNativePtr, paint.getNativeInstance(), outMetrics);
+ }
+
/** @hide */
public long getNativePtr() {
return mNativePtr;
@@ -573,4 +653,75 @@ public final class Font {
+ ", buffer=" + mBuffer
+ "}";
}
+
+ /**
+ * Lookup Font object from native pointer or create new one if not found.
+ * @hide
+ */
+ public static Font findOrCreateFontFromNativePtr(long ptr) {
+ // First, lookup from known mapps.
+ synchronized (MAP_LOCK) {
+ WeakReference<Font> fontRef = FONT_PTR_MAP.get(ptr);
+ if (fontRef != null) {
+ Font font = fontRef.get();
+ if (font != null) {
+ return font;
+ }
+ }
+
+ // If not found, create Font object from native object for Java API users.
+ ByteBuffer buffer = NativeFontBufferHelper.refByteBuffer(ptr);
+ long packed = nGetFontInfo(ptr);
+ int weight = (int) (packed & 0x0000_0000_0000_FFFFL);
+ boolean italic = (packed & 0x0000_0000_0001_0000L) != 0;
+ int ttcIndex = (int) ((packed & 0x0000_FFFF_0000_0000L) >> 32);
+ int axisCount = (int) ((packed & 0xFFFF_0000_0000_0000L) >> 48);
+ FontVariationAxis[] axes = new FontVariationAxis[axisCount];
+ char[] charBuffer = new char[4];
+ for (int i = 0; i < axisCount; ++i) {
+ long packedAxis = nGetAxisInfo(ptr, i);
+ float value = Float.intBitsToFloat((int) (packedAxis & 0x0000_0000_FFFF_FFFFL));
+ charBuffer[0] = (char) ((packedAxis & 0xFF00_0000_0000_0000L) >> 56);
+ charBuffer[1] = (char) ((packedAxis & 0x00FF_0000_0000_0000L) >> 48);
+ charBuffer[2] = (char) ((packedAxis & 0x0000_FF00_0000_0000L) >> 40);
+ charBuffer[3] = (char) ((packedAxis & 0x0000_00FF_0000_0000L) >> 32);
+ axes[i] = new FontVariationAxis(new String(charBuffer), value);
+ }
+ String path = nGetFontPath(ptr);
+ File file = (path == null) ? null : new File(path);
+ Font.Builder builder = new Font.Builder(buffer, file, "")
+ .setWeight(weight)
+ .setSlant(italic ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
+ .setTtcIndex(ttcIndex)
+ .setFontVariationSettings(axes);
+
+ Font newFont = null;
+ try {
+ newFont = builder.build();
+ FONT_PTR_MAP.append(ptr, new WeakReference<>(newFont));
+ } catch (IOException e) {
+ // This must not happen since the buffer was already created once.
+ Log.e("Font", "Failed to create font object from existing buffer.", e);
+ }
+ return newFont;
+ }
+ }
+
+ @CriticalNative
+ private static native long nGetFontInfo(long ptr);
+
+ @CriticalNative
+ private static native long nGetAxisInfo(long ptr, int i);
+
+ @FastNative
+ private static native String nGetFontPath(long ptr);
+
+ @FastNative
+ private static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);
+
+ @FastNative
+ private static native float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics);
+
+ @CriticalNative
+ private static native long nGetNativeFontPtr(long ptr);
}
diff --git a/graphics/java/android/graphics/fonts/NativeFontBufferHelper.java b/graphics/java/android/graphics/fonts/NativeFontBufferHelper.java
new file mode 100644
index 000000000000..5655e7fafc1b
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/NativeFontBufferHelper.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.graphics.fonts;
+
+import android.annotation.NonNull;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This is a helper class for showing native allocated buffer in Java API.
+ *
+ * @hide
+ */
+public class NativeFontBufferHelper {
+ private NativeFontBufferHelper() {}
+
+ private static final NativeAllocationRegistry REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ ByteBuffer.class.getClassLoader(), nGetReleaseFunc());
+
+ /**
+ * Wrap native buffer with ByteBuffer with adding reference to it.
+ */
+ public static @NonNull ByteBuffer refByteBuffer(long fontPtr) {
+ long refPtr = nRefFontBuffer(fontPtr);
+ ByteBuffer buffer = nWrapByteBuffer(refPtr);
+
+ // Releasing native object so that decreasing shared pointer ref count when the byte buffer
+ // is GCed.
+ REGISTRY.registerNativeAllocation(buffer, refPtr);
+
+ return buffer;
+ }
+
+ @CriticalNative
+ private static native long nRefFontBuffer(long fontPtr);
+
+ @FastNative
+ private static native ByteBuffer nWrapByteBuffer(long refPtr);
+
+ @CriticalNative
+ private static native long nGetReleaseFunc();
+}
diff --git a/graphics/java/android/graphics/text/GlyphStyle.java b/graphics/java/android/graphics/text/GlyphStyle.java
new file mode 100644
index 000000000000..cc8c4d26fb5e
--- /dev/null
+++ b/graphics/java/android/graphics/text/GlyphStyle.java
@@ -0,0 +1,234 @@
+/*
+ * 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.graphics.text;
+
+import android.annotation.ColorInt;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.graphics.Paint;
+
+import java.util.Objects;
+
+/**
+ * Represents subset of Paint parameters such as font size, scaleX that is used to draw a glyph.
+ *
+ * Glyph is a most primitive unit of text drawing.
+ *
+ */
+public class GlyphStyle {
+ private @ColorInt int mColor;
+ private float mFontSize;
+ private float mScaleX;
+ private float mSkewX;
+ private int mFlags;
+
+ /**
+ * @param color a color.
+ * @param fontSize a font size in pixels.
+ * @param scaleX a horizontal scale factor.
+ * @param skewX a horizontal skew factor
+ * @param flags paint flags
+ *
+ * @see Paint#getFlags()
+ * @see Paint#setFlags(int)
+ */
+ public GlyphStyle(
+ @ColorInt int color,
+ @FloatRange(from = 0) float fontSize,
+ @FloatRange(from = 0) float scaleX,
+ @FloatRange(from = 0) float skewX,
+ int flags) {
+ mColor = color;
+ mFontSize = fontSize;
+ mScaleX = scaleX;
+ mSkewX = skewX;
+ mFlags = flags;
+ }
+
+ /**
+ * Create glyph style from Paint
+ *
+ * @param paint a paint
+ */
+ public GlyphStyle(@NonNull Paint paint) {
+ setFromPaint(paint);
+ }
+
+ /**
+ * Gets the color.
+ *
+ * @return a color
+ * @see Paint#getColor()
+ * @see Paint#setColor(int)
+ */
+ public @ColorInt int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Sets the color.
+ *
+ * @param color a color
+ * @see Paint#getColor()
+ * @see Paint#setColor(int)
+ */
+ public void setColor(@ColorInt int color) {
+ mColor = color;
+ }
+
+ /**
+ * Gets the font size in pixels.
+ *
+ * @return font size
+ * @see Paint#getTextSize()
+ * @see Paint#setTextSize(float)
+ */
+ public @FloatRange(from = 0) float getFontSize() {
+ return mFontSize;
+ }
+
+ /**
+ * Sets the font size in pixels.
+ *
+ * @param fontSize font size in pixel
+ * @see Paint#getTextSize()
+ * @see Paint#setTextSize(float)
+ */
+ public void setFontSize(@FloatRange(from = 0) float fontSize) {
+ mFontSize = fontSize;
+ }
+
+ /**
+ * Return the horizontal scale factor
+ *
+ * @return a horizontal scale factor
+ * @see Paint#getTextScaleX()
+ * @see Paint#setTextScaleX(float)
+ */
+ public @FloatRange(from = 0) float getScaleX() {
+ return mScaleX;
+ }
+
+ /**
+ * Set the horizontal scale factor
+ *
+ * @param scaleX a horizontal scale factor
+ * @see Paint#getTextScaleX()
+ * @see Paint#setTextScaleX(float)
+ */
+ public void setScaleX(@FloatRange(from = 0) float scaleX) {
+ mScaleX = scaleX;
+ }
+
+ /**
+ * Return the horizontal skew factor
+ *
+ * @return a horizontal skew factor
+ * @see Paint#getTextSkewX()
+ * @see Paint#setTextSkewX(float)
+ */
+ public @FloatRange(from = 0) float getSkewX() {
+ return mSkewX;
+ }
+
+ /**
+ * Set the horizontal skew factor
+ *
+ * @param skewX a horizontal skew factor
+ * @see Paint#getTextSkewX()
+ * @see Paint#setTextSkewX(float)
+ */
+ public void setSkewX(@FloatRange(from = 0) float skewX) {
+ mSkewX = skewX;
+ }
+
+ /**
+ * Returns the Paint flags.
+ *
+ * @return a paint flags
+ * @see Paint#getFlags()
+ * @see Paint#setFlags(int)
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Set the Paint flags.
+ *
+ * @param flags a paint flags
+ * @see Paint#getFlags()
+ * @see Paint#setFlags(int)
+ */
+ public void setFlags(int flags) {
+ mFlags = flags;
+ }
+
+ /**
+ * Applies glyph style to the paint object.
+ *
+ * @param paint a paint object
+ */
+ public void applyToPaint(@NonNull Paint paint) {
+ paint.setColor(mColor);
+ paint.setTextSize(mFontSize);
+ paint.setTextScaleX(mScaleX);
+ paint.setTextSkewX(mSkewX);
+ paint.setFlags(mFlags);
+ }
+
+ /**
+ * Copy parameters from a Paint object.
+ *
+ * @param paint a paint object
+ */
+ public void setFromPaint(@NonNull Paint paint) {
+ mColor = paint.getColor();
+ mFontSize = paint.getTextSize();
+ mScaleX = paint.getTextScaleX();
+ mSkewX = paint.getTextSkewX();
+ mFlags = paint.getFlags();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof GlyphStyle)) return false;
+ GlyphStyle that = (GlyphStyle) o;
+ return that.mColor == mColor
+ && Float.compare(that.mFontSize, mFontSize) == 0
+ && Float.compare(that.mScaleX, mScaleX) == 0
+ && Float.compare(that.mSkewX, mSkewX) == 0
+ && mFlags == that.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mColor, mFontSize, mScaleX, mSkewX, mFlags);
+ }
+
+ @Override
+ public String toString() {
+ return "GlyphStyle{"
+ + "mColor=" + mColor
+ + ", mFontSize=" + mFontSize
+ + ", mScaleX=" + mScaleX
+ + ", mSkewX=" + mSkewX
+ + ", mFlags=" + mFlags
+ + '}';
+ }
+}
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
new file mode 100644
index 000000000000..7364d545a452
--- /dev/null
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -0,0 +1,277 @@
+/*
+ * 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.graphics.text;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Text shaping result object for single style text.
+ *
+ * You can get text shaping result by
+ * {@link TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)} and
+ * {@link TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}.
+ *
+ * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ */
+public final class PositionedGlyphs {
+ private static final NativeAllocationRegistry REGISTRY =
+ NativeAllocationRegistry.createMalloced(
+ Typeface.class.getClassLoader(), nReleaseFunc());
+
+ private final long mLayoutPtr;
+ private final float mXOffset;
+ private final float mYOffset;
+ private final GlyphStyle mGlyphStyle;
+ private final ArrayList<Font> mFonts;
+
+ /**
+ * Returns the total amount of advance consumed by this positioned glyphs.
+ *
+ * The advance is an amount of width consumed by the glyph. The total amount of advance is
+ * a total amount of advance consumed by this series of glyphs. In other words, if another
+ * glyph is placed next to this series of glyphs, it's X offset should be shifted this amount
+ * of width.
+ *
+ * @return total amount of advance
+ */
+ public float getTotalAdvance() {
+ return nGetTotalAdvance(mLayoutPtr);
+ }
+
+ /**
+ * Effective ascent value of this positioned glyphs.
+ *
+ * If two or more font files are used in this series of glyphs, the effective ascent will be
+ * the minimum ascent value across the all font files.
+ *
+ * @return effective ascent value
+ */
+ public float getAscent() {
+ return nGetAscent(mLayoutPtr);
+ }
+
+ /**
+ * Effective descent value of this positioned glyphs.
+ *
+ * If two or more font files are used in this series of glyphs, the effective descent will be
+ * the maximum descent value across the all font files.
+ *
+ * @return effective descent value
+ */
+ public float getDescent() {
+ return nGetDescent(mLayoutPtr);
+ }
+
+ /**
+ * Returns the glyph style used for drawing the glyph at the given index.
+ *
+ * @return A glyph style
+ */
+ @NonNull
+ public GlyphStyle getStyle() {
+ return mGlyphStyle;
+ }
+
+ /**
+ * Returns the amount of X offset added to glyph position.
+ *
+ * @return The X offset added to glyph position.
+ */
+ public float getOriginX() {
+ return mXOffset;
+ }
+
+ /**
+ * Returns the amount of Y offset added to glyph position.
+ *
+ * @return The Y offset added to glyph position.
+ */
+ public float getOriginY() {
+ return mYOffset;
+ }
+
+ /**
+ * Returns the number of glyphs stored.
+ *
+ * @return the number of glyphs
+ */
+ @IntRange(from = 0)
+ public int glyphCount() {
+ return nGetGlyphCount(mLayoutPtr);
+ }
+
+ /**
+ * Returns the font object used for drawing the glyph at the given index.
+ *
+ * @param index the glyph index
+ * @return the font object used for drawing the glyph at the given index
+ */
+ @NonNull
+ public Font getFont(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return mFonts.get(index);
+ }
+
+ /**
+ * Returns the glyph ID used for drawing the glyph at the given index.
+ *
+ * @param index the glyph index
+ * @return A font object
+ */
+ @IntRange(from = 0)
+ public int getGlyphId(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return nGetGlyphId(mLayoutPtr, index);
+ }
+
+ /**
+ * Returns the x coordinate of the glyph position at the given index.
+ *
+ * @param index the glyph index
+ * @return A X offset in pixels
+ */
+ public float getPositionX(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return nGetX(mLayoutPtr, index) + mXOffset;
+ }
+
+ /**
+ * Returns the y coordinate of the glyph position at the given index.
+ *
+ * @param index the glyph index
+ * @return A Y offset in pixels.
+ */
+ public float getPositionY(@IntRange(from = 0) int index) {
+ Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
+ return nGetY(mLayoutPtr, index) + mYOffset;
+ }
+
+ /**
+ * Create single style layout from native result.
+ *
+ * @hide
+ *
+ * @param layoutPtr the address of native layout object.
+ * @param paint a paint object
+ */
+ public PositionedGlyphs(long layoutPtr, @NonNull Paint paint, float xOffset, float yOffset) {
+ mLayoutPtr = layoutPtr;
+ mGlyphStyle = new GlyphStyle(paint);
+ int glyphCount = nGetGlyphCount(layoutPtr);
+ mFonts = new ArrayList<>(glyphCount);
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+
+ long prevPtr = 0;
+ Font prevFont = null;
+ for (int i = 0; i < glyphCount; ++i) {
+ long ptr = nGetFont(layoutPtr, i);
+ if (prevPtr != ptr) {
+ prevPtr = ptr;
+ prevFont = Font.findOrCreateFontFromNativePtr(ptr);
+ }
+ mFonts.add(prevFont);
+ }
+
+ REGISTRY.registerNativeAllocation(this, layoutPtr);
+ }
+
+ @CriticalNative
+ private static native int nGetGlyphCount(long minikinLayout);
+ @CriticalNative
+ private static native float nGetTotalAdvance(long minikinLayout);
+ @CriticalNative
+ private static native float nGetAscent(long minikinLayout);
+ @CriticalNative
+ private static native float nGetDescent(long minikinLayout);
+ @CriticalNative
+ private static native int nGetGlyphId(long minikinLayout, int i);
+ @CriticalNative
+ private static native float nGetX(long minikinLayout, int i);
+ @CriticalNative
+ private static native float nGetY(long minikinLayout, int i);
+ @CriticalNative
+ private static native long nGetFont(long minikinLayout, int i);
+ @CriticalNative
+ private static native long nReleaseFunc();
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PositionedGlyphs)) return false;
+ PositionedGlyphs that = (PositionedGlyphs) o;
+
+ if (!mGlyphStyle.equals(that.mGlyphStyle)) return false;
+ if (mXOffset != that.mXOffset || mYOffset != that.mYOffset) return false;
+ if (glyphCount() != that.glyphCount()) return false;
+
+ for (int i = 0; i < glyphCount(); ++i) {
+ if (getGlyphId(i) != that.getGlyphId(i)) return false;
+ if (getPositionX(i) != that.getPositionX(i)) return false;
+ if (getPositionY(i) != that.getPositionY(i)) return false;
+ // Intentionally using reference equality since font equality is heavy due to buffer
+ // compare.
+ if (getFont(i) != that.getFont(i)) return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Objects.hash(mXOffset, mYOffset, mGlyphStyle);
+ for (int i = 0; i < glyphCount(); ++i) {
+ hashCode = Objects.hash(hashCode,
+ getGlyphId(i), getPositionX(i), getPositionY(i), getFont(i));
+ }
+ return hashCode;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("[");
+ for (int i = 0; i < glyphCount(); ++i) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append("[ ID = " + getGlyphId(i) + ","
+ + " pos = (" + getPositionX(i) + "," + getPositionY(i) + ")"
+ + " font = " + getFont(i) + " ]");
+ }
+ sb.append("]");
+ return "PositionedGlyphs{"
+ + "glyphs = " + sb.toString()
+ + ", mXOffset=" + mXOffset
+ + ", mYOffset=" + mYOffset
+ + ", mGlyphStyle=" + mGlyphStyle
+ + '}';
+ }
+}
diff --git a/graphics/java/android/graphics/text/TextShaper.java b/graphics/java/android/graphics/text/TextShaper.java
new file mode 100644
index 000000000000..f40ed8f8f653
--- /dev/null
+++ b/graphics/java/android/graphics/text/TextShaper.java
@@ -0,0 +1,125 @@
+/*
+ * 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.graphics.text;
+
+import android.annotation.NonNull;
+import android.graphics.Paint;
+import android.text.TextDirectionHeuristic;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.FastNative;
+
+/**
+ * Provides conversion from a text into glyph array.
+ *
+ * Text shaping is a preprocess for drawing text into canvas with glyphs. The glyph is a most
+ * primitive unit of the text drawing, consist of glyph identifier in the font file and its position
+ * and style. You can draw the shape result to Canvas by calling Canvas#drawGlyphs.
+
+ *
+ * @see TextShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)
+ * @see TextShaper#shapeTextRun(char[], int, int, int, int, float, float, boolean, Paint)
+ * @see android.text.StyledTextShaper#shapeText(CharSequence, int, int, TextDirectionHeuristic,
+ * TextPaint)
+ */
+public class TextShaper {
+ private TextShaper() {} // Do not instantiate
+
+ /**
+ * Shape non-styled text.
+ *
+ * This function shapes the text of the given range under the context of given context range.
+ * Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or
+ * surrounding characters.
+ *
+ * @param text a text buffer to be shaped
+ * @param start a start index of shaping target in the buffer.
+ * @param count a length of shaping target in the buffer.
+ * @param contextStart a start index of context used for shaping in the buffer.
+ * @param contextCount a length of context used for shaping in the buffer.
+ * @param xOffset an additional amount of x offset of the result glyphs.
+ * @param yOffset an additional amount of y offset of the result glyphs.
+ * @param isRtl true if this text is shaped for RTL direction, false otherwise.
+ * @param paint a paint used for shaping text.
+ * @return a shape result.
+ */
+ @NonNull
+ public static PositionedGlyphs shapeTextRun(
+ @NonNull char[] text, int start, int count, int contextStart, int contextCount,
+ float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(paint);
+ return new PositionedGlyphs(
+ nativeShapeTextRun(text, start, count, contextStart, contextCount, isRtl,
+ paint.getNativeInstance()),
+ paint, xOffset, yOffset);
+ }
+
+ /**
+ * Shape non-styled text.
+ *
+ * This function shapes the text of the given range under the context of given context range.
+ * Some script, e.g. Arabic or Devanagari, changes letter shape based on its location or
+ * surrounding characters.
+ *
+ * @param text a text buffer to be shaped. Any styled spans stored in this text are ignored.
+ * @param start a start index of shaping target in the buffer.
+ * @param count a length of shaping target in the buffer.
+ * @param contextStart a start index of context used for shaping in the buffer.
+ * @param contextCount a length of context used for shaping in the buffer.
+ * @param xOffset an additional amount of x offset of the result glyphs.
+ * @param yOffset an additional amount of y offset of the result glyphs.
+ * @param isRtl true if this text is shaped for RTL direction, false otherwise.
+ * @param paint a paint used for shaping text.
+ * @return a shape result
+ */
+ @NonNull
+ public static PositionedGlyphs shapeTextRun(
+ @NonNull CharSequence text, int start, int count, int contextStart, int contextCount,
+ float xOffset, float yOffset, boolean isRtl, @NonNull Paint paint) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(paint);
+ if (text instanceof String) {
+ return new PositionedGlyphs(
+ nativeShapeTextRun(
+ (String) text, start, count, contextStart, contextCount, isRtl,
+ paint.getNativeInstance()),
+ paint, xOffset, yOffset);
+ } else {
+ char[] buf = new char[contextCount];
+ TextUtils.getChars(text, contextStart, contextStart + contextCount, buf, 0);
+ return new PositionedGlyphs(
+ nativeShapeTextRun(
+ buf, start - contextStart, count,
+ 0, contextCount, isRtl, paint.getNativeInstance()),
+ paint, xOffset, yOffset);
+ }
+ }
+
+ @FastNative
+ private static native long nativeShapeTextRun(
+ char[] text, int start, int count, int contextStart, int contextCount,
+ boolean isRtl, long nativePaint);
+
+ @FastNative
+ private static native long nativeShapeTextRun(
+ String text, int start, int count, int contextStart, int contextCount,
+ boolean isRtl, long nativePaint);
+
+}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1591b0616262..0defbd6451fe 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -23,7 +23,18 @@ java_library {
filegroup {
name: "wm_shell-sources",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ ],
+ path: "src",
+}
+
+// TODO(b/168581922) protologtool do not support kotlin(*.kt)
+filegroup {
+ name: "wm_shell-sources-kt",
+ srcs: [
+ "src/**/*.kt",
+ ],
path: "src",
}
@@ -97,15 +108,23 @@ android_library {
name: "WindowManager-Shell",
srcs: [
":wm_shell_protolog_src",
+ // TODO(b/168581922) protologtool do not support kotlin(*.kt)
+ ":wm_shell-sources-kt",
"src/**/I*.aidl",
],
resource_dirs: [
"res",
],
static_libs: [
+ "androidx.dynamicanimation_dynamicanimation",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
"protolog-lib",
"WindowManager-Shell-proto",
"androidx.appcompat_appcompat",
],
+ kotlincflags: ["-Xjvm-default=enable"],
manifest: "AndroidManifest.xml",
+
+ min_sdk_version: "26",
} \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/dismiss_circle_background.xml b/libs/WindowManager/Shell/res/drawable/dismiss_circle_background.xml
index 7809c8398c2d..7809c8398c2d 100644
--- a/packages/SystemUI/res/drawable/dismiss_circle_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/dismiss_circle_background.xml
diff --git a/packages/SystemUI/res/drawable/ic_skip_next_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_next_white.xml
index 040c7e642241..040c7e642241 100644
--- a/packages/SystemUI/res/drawable/ic_skip_next_white.xml
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_next_white.xml
diff --git a/packages/SystemUI/res/drawable/ic_skip_previous_white.xml b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_previous_white.xml
index b9b94b73a00f..b9b94b73a00f 100644
--- a/packages/SystemUI/res/drawable/ic_skip_previous_white.xml
+++ b/libs/WindowManager/Shell/res/drawable/pip_ic_skip_previous_white.xml
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml
index 72287c144bed..727ac3412a25 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_control_button.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Layout for {@link com.android.systemui.pip.tv.PipControlButtonView}. -->
+<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlButtonView}. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView android:id="@+id/button"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml b/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml
index 22e0452d620d..d2f235e273d5 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_controls.xml
@@ -14,17 +14,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Layout for {@link com.android.systemui.pip.tv.PipControlsView}. -->
+<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.systemui.pip.tv.PipControlButtonView
+ <com.android.wm.shell.pip.tv.PipControlButtonView
android:id="@+id/full_button"
android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_fullscreen_white"
android:text="@string/pip_fullscreen" />
- <com.android.systemui.pip.tv.PipControlButtonView
+ <com.android.wm.shell.pip.tv.PipControlButtonView
android:id="@+id/close_button"
android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
@@ -32,7 +32,7 @@
android:src="@drawable/pip_ic_close_white"
android:text="@string/pip_close" />
- <com.android.systemui.pip.tv.PipControlButtonView
+ <com.android.wm.shell.pip.tv.PipControlButtonView
android:id="@+id/play_pause_button"
android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml b/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml
index e6cd1122ca77..452f2cd5ccb6 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_custom_control.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.pip.tv.PipControlButtonView
+<com.android.wm.shell.pip.tv.PipControlButtonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index a049787b40b9..d8474b865a36 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -15,15 +15,15 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
- android:paddingTop="350dp"
- android:background="#CC000000"
- android:gravity="top|center_horizontal"
- android:clipChildren="false">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingTop="350dp"
+ android:background="#CC000000"
+ android:gravity="top|center_horizontal"
+ android:clipChildren="false">
- <com.android.systemui.pip.tv.PipControlsView
+ <com.android.wm.shell.pip.tv.PipControlsView
android:id="@+id/pip_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index a13e98c0d1ad..44744bc227a9 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -1,12 +1,36 @@
{
"version": "1.0.0",
"messages": {
+ "-1823823103": {
+ "message": "Add listener for types=%s listener=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
+ "-1683614271": {
+ "message": "Existing task: id=%d component=%s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
+ "-1534364071": {
+ "message": "onTransitionReady %s: %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
+ },
"-1501874464": {
"message": "Fullscreen Task Appeared: #%d",
"level": "VERBOSE",
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
},
+ "-1480787369": {
+ "message": "Transition requested: type=%d %s",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
+ },
"-1340279385": {
"message": "Remove listener=%s",
"level": "VERBOSE",
@@ -25,11 +49,11 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
},
- "-242812822": {
- "message": "Add listener for modes=%s listener=%s",
+ "-191422040": {
+ "message": "Transition animations finished, notifying core %s",
"level": "VERBOSE",
- "group": "WM_SHELL_TASK_ORG",
- "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ "group": "WM_SHELL_TRANSITIONS",
+ "at": "com\/android\/wm\/shell\/Transitions.java"
},
"157713005": {
"message": "Task info changed taskId=%d",
@@ -43,6 +67,12 @@
"group": "WM_SHELL_TASK_ORG",
"at": "com\/android\/wm\/shell\/FullscreenTaskListener.java"
},
+ "580605218": {
+ "message": "Registering organizer",
+ "level": "VERBOSE",
+ "group": "WM_SHELL_TASK_ORG",
+ "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
+ },
"980952660": {
"message": "Task root back pressed taskId=%d",
"level": "VERBOSE",
@@ -53,6 +83,9 @@
"groups": {
"WM_SHELL_TASK_ORG": {
"tag": "WindowManagerShell"
+ },
+ "WM_SHELL_TRANSITIONS": {
+ "tag": "WindowManagerShell"
}
}
}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 63b0f6ffbec3..e99350b264b9 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -32,4 +32,8 @@
<!-- Allow one handed to enable round corner -->
<bool name="config_one_handed_enable_round_corner">true</bool>
+
+ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
+ when the PIP menu is shown in center. -->
+ <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 7fb641a4b06e..a9917a6b07da 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -57,6 +57,9 @@
<dimen name="pip_resize_handle_margin">4dp</dimen>
<dimen name="pip_resize_handle_padding">0dp</dimen>
+ <dimen name="dismiss_target_x_size">24dp</dimen>
+ <dimen name="floating_dismiss_bottom_margin">50dp</dimen>
+
<!-- How high we lift the divider when touching -->
<dimen name="docked_stack_divider_lift_elevation">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b6668fbe4872..da5965dab71a 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -93,4 +93,8 @@
<string name="one_handed_tutorial_title">Using one-handed mode</string>
<!-- One-Handed Tutorial description [CHAR LIMIT=NONE] -->
<string name="one_handed_tutorial_description">To exit, swipe up from the bottom of the screen or tap anywhere above the app</string>
+ <!-- Accessibility description for start one-handed mode [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_start_one_handed">Start one-handed mode</string>
+ <!-- Accessibility description for stop one-handed mode [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_stop_one_handed">Exit one-handed mode</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 9047b71253da..9d6271bca426 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -22,18 +22,18 @@ import android.util.Slog;
import android.view.SurfaceControl;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskOrg";
- private final TransactionPool mTransactionPool;
+ private final SyncTransactionQueue mSyncQueue;
private final ArraySet<Integer> mTasks = new ArraySet<>();
- FullscreenTaskListener(TransactionPool transactionPool) {
- mTransactionPool = transactionPool;
+ FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ mSyncQueue = syncQueue;
}
@Override
@@ -42,18 +42,22 @@ class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
if (mTasks.contains(taskInfo.taskId)) {
throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId);
}
- mTasks.add(taskInfo.taskId);
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d",
taskInfo.taskId);
- // Reset several properties back to fullscreen (PiP, for example, leaves all these
- // properties in a bad state).
- t.setPosition(leash, 0, 0);
- t.setWindowCrop(leash, null);
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
- t.apply();
+ mTasks.add(taskInfo.taskId);
+ mSyncQueue.runInSync(t -> {
+ // Reset several properties back to fullscreen (PiP, for example, leaves all these
+ // properties in a bad state).
+ t.setPosition(leash, 0, 0);
+ t.setWindowCrop(leash, null);
+ // TODO(shell-transitions): Eventually set everything in transition so there's no
+ // SF Transaction here.
+ if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 2d82fb1d3a21..d87de5a06c55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,23 +16,35 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import android.annotation.IntDef;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.WindowingMode;
import android.util.Log;
-import android.util.Pair;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Unified task organizer for all components in the shell.
@@ -40,6 +52,23 @@ import java.util.Arrays;
*/
public class ShellTaskOrganizer extends TaskOrganizer {
+ // Intentionally using negative numbers here so the positive numbers can be used
+ // for task id specific listeners that will be added later.
+ public static final int TASK_LISTENER_TYPE_UNDEFINED = -1;
+ public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2;
+ public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3;
+ public static final int TASK_LISTENER_TYPE_PIP = -4;
+ public static final int TASK_LISTENER_TYPE_SPLIT_SCREEN = -5;
+
+ @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = {
+ TASK_LISTENER_TYPE_UNDEFINED,
+ TASK_LISTENER_TYPE_FULLSCREEN,
+ TASK_LISTENER_TYPE_MULTI_WINDOW,
+ TASK_LISTENER_TYPE_PIP,
+ TASK_LISTENER_TYPE_SPLIT_SCREEN,
+ })
+ public @interface TaskListenerType {}
+
private static final String TAG = "ShellTaskOrganizer";
/**
@@ -52,49 +81,63 @@ public class ShellTaskOrganizer extends TaskOrganizer {
default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
}
- private final SparseArray<ArrayList<TaskListener>> mListenersByWindowingMode =
- new SparseArray<>();
+ private final SparseArray<TaskListener> mTaskListenersByType = new SparseArray<>();
// Keeps track of all the tasks reported to this organizer (changes in windowing mode will
// require us to report to both old and new listeners)
- private final SparseArray<Pair<RunningTaskInfo, SurfaceControl>> mTasks = new SparseArray<>();
+ private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>();
+
+ // TODO(shell-transitions): move to a more "global" Shell location as this isn't only for Tasks
+ private final Transitions mTransitions;
- public ShellTaskOrganizer(TransactionPool transactionPool) {
- super();
- addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN);
+ public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+ ShellExecutor mainExecutor, ShellExecutor animExecutor) {
+ this(null, syncQueue, transactionPool, mainExecutor, animExecutor);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
- TransactionPool transactionPool) {
+ SyncTransactionQueue syncQueue, TransactionPool transactionPool,
+ ShellExecutor mainExecutor, ShellExecutor animExecutor) {
super(taskOrganizerController);
- addListener(new FullscreenTaskListener(transactionPool), WINDOWING_MODE_FULLSCREEN);
+ addListener(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN);
+ mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
+ }
+
+ @Override
+ public List<TaskAppearedInfo> registerOrganizer() {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Registering organizer");
+ final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
+ for (int i = 0; i < taskInfos.size(); i++) {
+ final TaskAppearedInfo info = taskInfos.get(i);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s",
+ info.getTaskInfo().taskId, info.getTaskInfo().baseIntent);
+ onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
+ return taskInfos;
}
/**
- * Adds a listener for tasks in a specific windowing mode.
+ * Adds a listener for tasks with given types.
*/
- public void addListener(TaskListener listener, int... windowingModes) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Add listener for modes=%s listener=%s",
- Arrays.toString(windowingModes), listener);
- for (int winMode : windowingModes) {
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(winMode);
- if (listeners == null) {
- listeners = new ArrayList<>();
- mListenersByWindowingMode.put(winMode, listeners);
+ public void addListener(TaskListener listener, @TaskListenerType int... taskListenerTypes) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Add listener for types=%s listener=%s",
+ Arrays.toString(taskListenerTypes), listener);
+ for (int listenerType : taskListenerTypes) {
+ if (mTaskListenersByType.get(listenerType) != null) {
+ throw new IllegalArgumentException("Listener for listenerType=" + listenerType
+ + " already exists");
}
- if (listeners.contains(listener)) {
- Log.w(TAG, "Listener already exists");
- return;
- }
- listeners.add(listener);
+ mTaskListenersByType.put(listenerType, listener);
- // Notify the listener of all existing tasks in that windowing mode
+ // Notify the listener of all existing tasks with the given type.
for (int i = mTasks.size() - 1; i >= 0; i--) {
- Pair<RunningTaskInfo, SurfaceControl> data = mTasks.valueAt(i);
- int taskWinMode = data.first.configuration.windowConfiguration.getWindowingMode();
- if (taskWinMode == winMode) {
- listener.onTaskAppeared(data.first, data.second);
+ TaskAppearedInfo data = mTasks.valueAt(i);
+ final @TaskListenerType int taskListenerType = getTaskListenerType(
+ data.getTaskInfo());
+ if (taskListenerType == listenerType) {
+ listener.onTaskAppeared(data.getTaskInfo(), data.getLeash());
}
}
}
@@ -105,22 +148,22 @@ public class ShellTaskOrganizer extends TaskOrganizer {
*/
public void removeListener(TaskListener listener) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Remove listener=%s", listener);
- for (int i = 0; i < mListenersByWindowingMode.size(); i++) {
- mListenersByWindowingMode.valueAt(i).remove(listener);
+ final int index = mTaskListenersByType.indexOfValue(listener);
+ if (index == -1) {
+ Log.w(TAG, "No registered listener found");
+ return;
}
+ mTaskListenersByType.removeAt(index);
}
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task appeared taskId=%d",
taskInfo.taskId);
- mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, leash));
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(
- getWindowingMode(taskInfo));
- if (listeners != null) {
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onTaskAppeared(taskInfo, leash);
- }
+ mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, leash));
+ final TaskListener listener = mTaskListenersByType.get(getTaskListenerType(taskInfo));
+ if (listener != null) {
+ listener.onTaskAppeared(taskInfo, leash);
}
}
@@ -128,32 +171,26 @@ public class ShellTaskOrganizer extends TaskOrganizer {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task info changed taskId=%d",
taskInfo.taskId);
- Pair<RunningTaskInfo, SurfaceControl> data = mTasks.get(taskInfo.taskId);
- int winMode = getWindowingMode(taskInfo);
- int prevWinMode = getWindowingMode(data.first);
- mTasks.put(taskInfo.taskId, new Pair<>(taskInfo, data.second));
- if (prevWinMode != -1 && prevWinMode != winMode) {
- // TODO: We currently send vanished/appeared as the task moves between win modes, but
+ final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
+ final @TaskListenerType int listenerType = getTaskListenerType(taskInfo);
+ final @TaskListenerType int prevListenerType = getTaskListenerType(data.getTaskInfo());
+ mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
+ if (prevListenerType != listenerType) {
+ // TODO: We currently send vanished/appeared as the task moves between types, but
// we should consider adding a different mode-changed callback
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(prevWinMode);
- if (listeners != null) {
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onTaskVanished(taskInfo);
- }
+ TaskListener listener = mTaskListenersByType.get(prevListenerType);
+ if (listener != null) {
+ listener.onTaskVanished(taskInfo);
}
- listeners = mListenersByWindowingMode.get(winMode);
- if (listeners != null) {
- SurfaceControl leash = data.second;
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onTaskAppeared(taskInfo, leash);
- }
+ listener = mTaskListenersByType.get(listenerType);
+ if (listener != null) {
+ SurfaceControl leash = data.getLeash();
+ listener.onTaskAppeared(taskInfo, leash);
}
} else {
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(winMode);
- if (listeners != null) {
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onTaskInfoChanged(taskInfo);
- }
+ final TaskListener listener = mTaskListenersByType.get(listenerType);
+ if (listener != null) {
+ listener.onTaskInfoChanged(taskInfo);
}
}
}
@@ -162,12 +199,9 @@ public class ShellTaskOrganizer extends TaskOrganizer {
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task root back pressed taskId=%d",
taskInfo.taskId);
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(
- getWindowingMode(taskInfo));
- if (listeners != null) {
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onBackPressedOnTaskRoot(taskInfo);
- }
+ final TaskListener listener = mTaskListenersByType.get(getTaskListenerType(taskInfo));
+ if (listener != null) {
+ listener.onBackPressedOnTaskRoot(taskInfo);
}
}
@@ -175,17 +209,38 @@ public class ShellTaskOrganizer extends TaskOrganizer {
public void onTaskVanished(RunningTaskInfo taskInfo) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Task vanished taskId=%d",
taskInfo.taskId);
- int prevWinMode = getWindowingMode(mTasks.get(taskInfo.taskId).first);
+ final @TaskListenerType int prevListenerType =
+ getTaskListenerType(mTasks.get(taskInfo.taskId).getTaskInfo());
mTasks.remove(taskInfo.taskId);
- ArrayList<TaskListener> listeners = mListenersByWindowingMode.get(prevWinMode);
- if (listeners != null) {
- for (int i = listeners.size() - 1; i >= 0; i--) {
- listeners.get(i).onTaskVanished(taskInfo);
- }
+ final TaskListener listener = mTaskListenersByType.get(prevListenerType);
+ if (listener != null) {
+ listener.onTaskVanished(taskInfo);
+ }
+ }
+
+ @TaskListenerType
+ private static int getTaskListenerType(RunningTaskInfo runningTaskInfo) {
+ // Right now it's N:1 mapping but in the future different task listerners
+ // may be triggered by one windowing mode depending on task parameters.
+ switch (getWindowingMode(runningTaskInfo)) {
+ case WINDOWING_MODE_FULLSCREEN:
+ return TASK_LISTENER_TYPE_FULLSCREEN;
+ case WINDOWING_MODE_MULTI_WINDOW:
+ return TASK_LISTENER_TYPE_MULTI_WINDOW;
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+ case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+ return TASK_LISTENER_TYPE_SPLIT_SCREEN;
+ case WINDOWING_MODE_PINNED:
+ return TASK_LISTENER_TYPE_PIP;
+ case WINDOWING_MODE_FREEFORM:
+ case WINDOWING_MODE_UNDEFINED:
+ default:
+ return TASK_LISTENER_TYPE_UNDEFINED;
}
}
- private int getWindowingMode(RunningTaskInfo taskInfo) {
+ @WindowingMode
+ private static int getWindowingMode(RunningTaskInfo taskInfo) {
return taskInfo.configuration.windowConfiguration.getWindowingMode();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
new file mode 100644
index 000000000000..36e49d9fd770
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/Transitions.java
@@ -0,0 +1,181 @@
+/*
+ * 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.wm.shell;
+
+import static android.window.TransitionInfo.TRANSIT_CLOSE;
+import static android.window.TransitionInfo.TRANSIT_HIDE;
+import static android.window.TransitionInfo.TRANSIT_OPEN;
+import static android.window.TransitionInfo.TRANSIT_SHOW;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+import android.window.TransitionInfo;
+import android.window.WindowOrganizer;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+
+/** Plays transition animations */
+public class Transitions extends ITransitionPlayer.Stub {
+ private static final String TAG = "ShellTransitions";
+
+ /** Set to {@code true} to enable shell transitions. */
+ public static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.debug.shell_transit", false);
+
+ private final WindowOrganizer mOrganizer;
+ private final TransactionPool mTransactionPool;
+ private final ShellExecutor mMainExecutor;
+ private final ShellExecutor mAnimExecutor;
+
+ /** Keeps track of currently tracked transitions and all the animations associated with each */
+ private final ArrayMap<IBinder, ArrayList<Animator>> mActiveTransitions = new ArrayMap<>();
+
+ Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ mOrganizer = organizer;
+ mTransactionPool = pool;
+ mMainExecutor = mainExecutor;
+ mAnimExecutor = animExecutor;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
+ boolean show) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mMainExecutor.execute(() -> {
+ mActiveTransitions.get(transition).remove(va);
+ onFinish(transition);
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ mActiveTransitions.get(transition).add(va);
+ mAnimExecutor.execute(va::start);
+ }
+
+ private static boolean isOpeningType(@WindowManager.TransitionType int legacyType) {
+ // TODO(shell-transitions): consider providing and using z-order vs the global type for
+ // this determination.
+ return legacyType == WindowManager.TRANSIT_TASK_OPEN
+ || legacyType == WindowManager.TRANSIT_TASK_TO_FRONT
+ || legacyType == WindowManager.TRANSIT_TASK_OPEN_BEHIND
+ || legacyType == WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transitionToken, TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
+ transitionToken, info);
+ // start task
+ mMainExecutor.execute(() -> {
+ if (!mActiveTransitions.containsKey(transitionToken)) {
+ Slog.e(TAG, "Got transitionReady for non-active transition " + transitionToken
+ + " expecting one of " + mActiveTransitions.keySet());
+ }
+ if (mActiveTransitions.get(transitionToken) != null) {
+ throw new IllegalStateException("Got a duplicate onTransitionReady call for "
+ + transitionToken);
+ }
+ mActiveTransitions.put(transitionToken, new ArrayList<>());
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final SurfaceControl leash = info.getChanges().get(i).getLeash();
+ final int mode = info.getChanges().get(i).getMode();
+ if (mode == TRANSIT_OPEN || mode == TRANSIT_SHOW) {
+ t.show(leash);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ if (isOpeningType(info.getType())) {
+ t.setAlpha(leash, 0.f);
+ startExampleAnimation(transitionToken, leash, true /* show */);
+ } else {
+ t.setAlpha(leash, 1.f);
+ }
+ } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_HIDE) {
+ if (!isOpeningType(info.getType())) {
+ startExampleAnimation(transitionToken, leash, false /* show */);
+ }
+ }
+ }
+ t.apply();
+ onFinish(transitionToken);
+ });
+ }
+
+ @MainThread
+ private void onFinish(IBinder transition) {
+ if (!mActiveTransitions.get(transition).isEmpty()) return;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Transition animations finished, notifying core %s", transition);
+ mActiveTransitions.remove(transition);
+ mOrganizer.finishTransition(transition, null, null);
+ }
+
+ @Override
+ public void requestStartTransition(int type, @NonNull IBinder transitionToken) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
+ type, transitionToken);
+ mMainExecutor.execute(() -> {
+ if (mActiveTransitions.containsKey(transitionToken)) {
+ throw new RuntimeException("Transition already started " + transitionToken);
+ }
+ IBinder transition = mOrganizer.startTransition(type, transitionToken, null /* wct */);
+ mActiveTransitions.put(transition, null);
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java
new file mode 100644
index 000000000000..acb9a5dae78c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.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 com.android.wm.shell;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.WindowConfiguration;
+import android.os.RemoteException;
+import android.view.WindowManagerGlobal;
+
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+
+/**
+ * The singleton wrapper to communicate between WindowManagerService and WMShell features
+ * (e.g: PIP, SplitScreen, Bubble, OneHandedMode...etc)
+ */
+public class WindowManagerShellWrapper {
+ private static final String TAG = WindowManagerShellWrapper.class.getSimpleName();
+
+ public static final int WINDOWING_MODE_PINNED = WindowConfiguration.WINDOWING_MODE_PINNED;
+
+ /**
+ * Forwarder to which we can add multiple pinned stack listeners. Each listener will receive
+ * updates from the window manager service.
+ */
+ private PinnedStackListenerForwarder mPinnedStackListenerForwarder =
+ new PinnedStackListenerForwarder();
+
+ /**
+ * Adds a pinned stack listener, which will receive updates from the window manager service
+ * along with any other pinned stack listeners that were added via this method.
+ */
+ public void addPinnedStackListener(PinnedStackListenerForwarder.PinnedStackListener listener)
+ throws
+ RemoteException {
+ mPinnedStackListenerForwarder.addListener(listener);
+ WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
+ DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
+ }
+
+ /**
+ * Removes a pinned stack listener.
+ */
+ public void removePinnedStackListener(
+ PinnedStackListenerForwarder.PinnedStackListener listener) {
+ mPinnedStackListenerForwarder.removeListener(listener);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FloatProperties.kt
index a284a747da21..d4f82829aa52 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FloatProperties.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.animation
+package com.android.wm.shell.animation
import android.graphics.Rect
import android.graphics.RectF
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index b794b91568fc..416ada739aa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -24,6 +24,16 @@ import android.view.animation.PathInterpolator;
*/
public class Interpolators {
/**
+ * Interpolator for alpha in animation.
+ */
+ public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+ /**
+ * Interpolator for alpha out animation.
+ */
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+
+ /**
* Interpolator for fast out linear in animation.
*/
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 2a5424ce4ef7..5cd660a2caa5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util.animation
+package com.android.wm.shell.animation
import android.os.Looper
import android.util.ArrayMap
@@ -26,7 +26,7 @@ import androidx.dynamicanimation.animation.FlingAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
+import com.android.wm.shell.animation.PhysicsAnimator.Companion.getInstance
import java.lang.ref.WeakReference
import java.util.WeakHashMap
import kotlin.math.abs
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
index c50eeac80d7a..86eb8da952f1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimatorTestUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -13,16 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.util.animation
+package com.android.wm.shell.animation
import android.os.Handler
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.ArrayDeque
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.prepareForTest
+import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
+import kotlin.collections.ArrayList
+import kotlin.collections.HashMap
+import kotlin.collections.HashSet
+import kotlin.collections.Set
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.drop
+import kotlin.collections.forEach
+import kotlin.collections.getOrPut
+import kotlin.collections.set
+import kotlin.collections.toList
+import kotlin.collections.toTypedArray
typealias UpdateMatcher = (PhysicsAnimator.AnimationUpdate) -> Boolean
typealias UpdateFramesPerProperty<T> =
@@ -84,7 +96,7 @@ object PhysicsAnimatorTestUtils {
*/
@JvmStatic
fun setBlockTimeout(timeoutMs: Long) {
- this.timeoutMs = timeoutMs
+ PhysicsAnimatorTestUtils.timeoutMs = timeoutMs
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
new file mode 100644
index 000000000000..96b9f86673fc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AnimationThread.java
@@ -0,0 +1,61 @@
+/*
+ * 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.wm.shell.common;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
+import android.annotation.NonNull;
+import android.os.HandlerThread;
+import android.util.Singleton;
+
+/**
+ * A singleton thread for Shell to run animations on.
+ */
+public class AnimationThread extends HandlerThread {
+ private ShellExecutor mExecutor;
+
+ private AnimationThread() {
+ super("wmshell.anim", THREAD_PRIORITY_DISPLAY);
+ }
+
+ /** Get the singleton instance of this thread */
+ public static AnimationThread instance() {
+ return sAnimationThreadSingleton.get();
+ }
+
+ /**
+ * @return a shared {@link ShellExecutor} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public ShellExecutor getExecutor() {
+ if (mExecutor == null) {
+ mExecutor = new HandlerExecutor(getThreadHandler());
+ }
+ return mExecutor;
+ }
+
+ private static final Singleton<AnimationThread> sAnimationThreadSingleton =
+ new Singleton<AnimationThread>() {
+ @Override
+ protected AnimationThread create() {
+ final AnimationThread animThread = new AnimationThread();
+ animThread.start();
+ return animThread;
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
index 8946c97a4b58..976fba52b9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DismissCircleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util;
+package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.Configuration;
@@ -23,7 +23,7 @@ import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
/**
* Circular view with a semitransparent, circular background with an 'X' inside it.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index c18e9ce76153..d810fb8257a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -43,6 +43,7 @@ import android.view.animation.PathInterpolator;
import com.android.internal.view.IInputMethodManager;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
/**
* Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
@@ -62,15 +63,21 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
private static final int FLOATING_IME_BOTTOM_INSET = -80;
protected final IWindowManager mWmService;
- protected final Handler mHandler;
+ protected final Executor mExecutor;
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
+ @Deprecated
public DisplayImeController(IWindowManager wmService, DisplayController displayController,
Handler mainHandler, TransactionPool transactionPool) {
- mHandler = mainHandler;
+ this(wmService, displayController, mainHandler::post, transactionPool);
+ }
+
+ public DisplayImeController(IWindowManager wmService, DisplayController displayController,
+ Executor mainExecutor, TransactionPool transactionPool) {
+ mExecutor = mainExecutor;
mWmService = wmService;
mTransactionPool = transactionPool;
mDisplayController = displayController;
@@ -197,7 +204,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Override
public void insetsChanged(InsetsState insetsState) {
- mHandler.post(() -> {
+ mExecutor.execute(() -> {
if (mInsetsState.equals(insetsState)) {
return;
}
@@ -224,15 +231,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
continue;
}
if (activeControl.getType() == InsetsState.ITYPE_IME) {
- mHandler.post(() -> {
+ mExecutor.execute(() -> {
final Point lastSurfacePosition = mImeSourceControl != null
? mImeSourceControl.getSurfacePosition() : null;
+ final boolean positionChanged =
+ !activeControl.getSurfacePosition().equals(lastSurfacePosition);
+ final boolean leashChanged =
+ !haveSameLeash(mImeSourceControl, activeControl);
mImeSourceControl = activeControl;
- if (!activeControl.getSurfacePosition().equals(lastSurfacePosition)
- && mAnimation != null) {
- startAnimation(mImeShowing, true /* forceRestart */);
- } else if (!mImeShowing) {
- removeImeSurface();
+ if (mAnimation != null) {
+ if (positionChanged) {
+ startAnimation(mImeShowing, true /* forceRestart */);
+ }
+ } else {
+ if (leashChanged) {
+ applyVisibilityToLeash();
+ }
+ if (!mImeShowing) {
+ removeImeSurface();
+ }
}
});
}
@@ -240,13 +257,27 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
+ private void applyVisibilityToLeash() {
+ SurfaceControl leash = mImeSourceControl.getLeash();
+ if (leash != null) {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ if (mImeShowing) {
+ t.show(leash);
+ } else {
+ t.hide(leash);
+ }
+ t.apply();
+ mTransactionPool.release(t);
+ }
+ }
+
@Override
public void showInsets(int types, boolean fromIme) {
if ((types & WindowInsets.Type.ime()) == 0) {
return;
}
if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
- mHandler.post(() -> startAnimation(true /* show */, false /* forceRestart */));
+ mExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
}
@Override
@@ -255,7 +286,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return;
}
if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
- mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */));
+ mExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
}
@Override
@@ -495,4 +526,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
return IInputMethodManager.Stub.asInterface(
ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
}
+
+ private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ if (a.getLeash() == b.getLeash()) {
+ return true;
+ }
+ if (a.getLeash() == null || b.getLeash() == null) {
+ return false;
+ }
+ return a.getLeash().isSameSurface(b.getLeash());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt
index bcfb2afeeda1..d5d072a8d449 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/FloatingContentCoordinator.kt
@@ -1,9 +1,24 @@
-package com.android.systemui.util
+/*
+ * 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.wm.shell.common
import android.graphics.Rect
import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
+import com.android.wm.shell.common.FloatingContentCoordinator.FloatingContent
import java.util.HashMap
/** Tag for debug logging. */
@@ -20,7 +35,6 @@ private const val TAG = "FloatingCoordinator"
* no longer visible.
*/
-@SysUISingleton
class FloatingContentCoordinator constructor() {
/**
* Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
@@ -260,14 +274,18 @@ class FloatingContentCoordinator constructor() {
// Lazily calculate the closest possible new tops for the content, above and below its
// current location.
- val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow(
- contentRect,
- exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
- findAbove = true) }
- val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow(
- contentRect,
- exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
- findAbove = false) }
+ val newContentBoundsAbove by lazy {
+ findAreaForContentAboveOrBelow(
+ contentRect,
+ exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
+ findAbove = true)
+ }
+ val newContentBoundsBelow by lazy {
+ findAreaForContentAboveOrBelow(
+ contentRect,
+ exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
+ findAbove = false)
+ }
val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) }
val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) }
@@ -347,4 +365,4 @@ class FloatingContentCoordinator constructor() {
(r1.right <= r2.right && r1.right >= r2.left)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
new file mode 100644
index 000000000000..cd75840b8c71
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * 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.wm.shell.common;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+/** Executor implementation which is backed by a Handler. */
+public class HandlerExecutor implements ShellExecutor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void executeDelayed(@NonNull Runnable r, long delayMillis) {
+ if (!mHandler.postDelayed(r, delayMillis)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+
+ @Override
+ public void removeCallbacks(@NonNull Runnable r) {
+ mHandler.removeCallbacks(r);
+ }
+
+ @Override
+ public void execute(@NonNull Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RuntimeException(mHandler + " is probably exiting");
+ }
+ }
+}
diff --git a/libs/hwui/shader/BitmapShader.h b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index ed6a6e6802d1..aafe2407a1ea 100644
--- a/libs/hwui/shader/BitmapShader.h
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -14,26 +14,22 @@
* limitations under the License.
*/
-#pragma once
+package com.android.wm.shell.common;
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
+import java.util.concurrent.Executor;
/**
- * Shader implementation that renders a Bitmap as either a SkShader or SkImageFilter
+ * Super basic Executor interface that adds support for delayed execution and removing callbacks.
+ * Intended to wrap Handler while better-supporting testing.
*/
-class BitmapShader : public Shader {
-public:
- BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
- const SkTileMode tileModeY, const SkMatrix* matrix);
- ~BitmapShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
+public interface ShellExecutor extends Executor {
+ /**
+ * See {@link android.os.Handler#postDelayed(Runnable, long)}.
+ */
+ void executeDelayed(Runnable r, long delayMillis);
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer \ No newline at end of file
+ /**
+ * See {@link android.os.Handler#removeCallbacks}.
+ */
+ void removeCallbacks(Runnable r);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index b4620e27e68c..84b98f9e8047 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -271,14 +271,14 @@ public class SystemWindows {
}
@Override
- public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, ClientWindowFrames outFrames,
MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize,
SurfaceControl outBLASTSurfaceControl) {
- int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight,
+ int res = super.relayout(window, attrs, requestedWidth, requestedHeight,
viewVisibility, flags, frameNumber, outFrames,
mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSurfaceSize, outBLASTSurfaceControl);
@@ -365,10 +365,6 @@ public class SystemWindows {
public void updatePointerIcon(float x, float y) {}
@Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
- int localValue, int localChanges) {}
-
- @Override
public void dispatchWindowShown() {}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index f441049feefb..b4d738712893 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.util.magnetictarget
+package com.android.wm.shell.common.magnetictarget
import android.annotation.SuppressLint
import android.content.Context
@@ -31,7 +31,7 @@ import android.view.ViewConfiguration
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
-import com.android.systemui.util.animation.PhysicsAnimator
+import com.android.wm.shell.animation.PhysicsAnimator
import kotlin.math.abs
import kotlin.math.hypot
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index 9c78fc5e57b8..9bb709f9a82a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -27,11 +27,6 @@ import java.io.PrintWriter;
*/
public interface OneHanded {
/**
- * Return whether the device has one handed feature or not.
- */
- boolean hasOneHandedFeature();
-
- /**
* Return one handed settings enabled or not.
*/
boolean isOneHandedEnabled();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index d060f6444463..7d039d4d92f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -30,9 +30,10 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.util.Log;
+import android.util.Slog;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.common.DisplayChangeController;
@@ -54,7 +55,6 @@ public class OneHandedController implements OneHanded {
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
- private final boolean mHasOneHandedFeature;
private boolean mIsOneHandedEnabled;
private boolean mIsSwipeToNotificationEnabled;
private boolean mTaskChangeToExit;
@@ -77,7 +77,7 @@ public class OneHandedController implements OneHanded {
private final DisplayChangeController.OnDisplayChangingListener mRotationController =
(display, fromRotation, toRotation, wct) -> {
if (mDisplayAreaOrganizer != null) {
- mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation);
+ mDisplayAreaOrganizer.onRotateDisplay(fromRotation, toRotation, wct);
}
};
@@ -160,10 +160,16 @@ public class OneHandedController implements OneHanded {
};
/**
- * The static constructor method to create OneHnadedController.
+ * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
+ @Nullable
public static OneHandedController create(
Context context, DisplayController displayController) {
+ if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
+ Slog.w(TAG, "Device doesn't support OneHanded feature");
+ return null;
+ }
+
OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context);
OneHandedAnimationController animationController =
new OneHandedAnimationController(context);
@@ -186,43 +192,30 @@ public class OneHandedController implements OneHanded {
OneHandedTutorialHandler tutorialHandler,
OneHandedGestureHandler gestureHandler,
IOverlayManager overlayManager) {
- mHasOneHandedFeature = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
- if (!mHasOneHandedFeature) {
- Log.i(TAG, "Device config SUPPORT_ONE_HANDED_MODE off");
- mContext = null;
- mDisplayAreaOrganizer = null;
- mDisplayController = null;
- mTouchHandler = null;
- mTutorialHandler = null;
- mGestureHandler = null;
- mTimeoutHandler = null;
- mOverlayManager = null;
- } else {
- mContext = context;
- mDisplayAreaOrganizer = displayAreaOrganizer;
- mDisplayController = displayController;
- mTouchHandler = touchHandler;
- mTutorialHandler = tutorialHandler;
- mGestureHandler = gestureHandler;
- mOverlayManager = overlayManager;
-
- mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50)
- / 100.0f;
- mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
- context.getContentResolver());
- mIsSwipeToNotificationEnabled =
- OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
- context.getContentResolver());
- mTimeoutHandler = OneHandedTimeoutHandler.get();
-
- mDisplayController.addDisplayChangingController(mRotationController);
-
- setupCallback();
- setupSettingObservers();
- setupTimeoutListener();
- setupGesturalOverlay();
- updateSettings();
- }
+ mContext = context;
+ mDisplayAreaOrganizer = displayAreaOrganizer;
+ mDisplayController = displayController;
+ mTouchHandler = touchHandler;
+ mTutorialHandler = tutorialHandler;
+ mGestureHandler = gestureHandler;
+ mOverlayManager = overlayManager;
+
+ mOffSetFraction = SystemProperties.getInt(ONE_HANDED_MODE_OFFSET_PERCENTAGE, 50)
+ / 100.0f;
+ mIsOneHandedEnabled = OneHandedSettingsUtil.getSettingsOneHandedModeEnabled(
+ context.getContentResolver());
+ mIsSwipeToNotificationEnabled =
+ OneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
+ context.getContentResolver());
+ mTimeoutHandler = OneHandedTimeoutHandler.get();
+
+ mDisplayController.addDisplayChangingController(mRotationController);
+
+ setupCallback();
+ setupSettingObservers();
+ setupTimeoutListener();
+ setupGesturalOverlay();
+ updateSettings();
}
/**
@@ -249,11 +242,6 @@ public class OneHandedController implements OneHanded {
}
@Override
- public boolean hasOneHandedFeature() {
- return mHasOneHandedFeature;
- }
-
- @Override
public boolean isOneHandedEnabled() {
return mIsOneHandedEnabled;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 9954618134e8..17418f934691 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
@@ -42,7 +43,6 @@ import com.android.wm.shell.common.DisplayController;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -76,7 +76,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private int mEnterExitAnimationDurationMs;
@VisibleForTesting
- HashMap<DisplayAreaInfo, SurfaceControl> mDisplayAreaMap = new HashMap();
+ ArrayMap<DisplayAreaInfo, SurfaceControl> mDisplayAreaMap = new ArrayMap();
private DisplayController mDisplayController;
private OneHandedAnimationController mAnimationController;
private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -117,12 +117,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
private Handler.Callback mUpdateCallback = (msg) -> {
SomeArgs args = (SomeArgs) msg.obj;
final Rect currentBounds = args.arg1 != null ? (Rect) args.arg1 : mDefaultDisplayBounds;
+ final WindowContainerTransaction wctFromRotate = (WindowContainerTransaction) args.arg2;
final int yOffset = args.argi2;
final int direction = args.argi3;
switch (msg.what) {
case MSG_RESET_IMMEDIATE:
- resetWindowsOffset();
+ resetWindowsOffset(wctFromRotate);
mDefaultDisplayBounds.set(currentBounds);
mLastVisualDisplayBounds.set(currentBounds);
finishOffset(0, TRANSITION_DIRECTION_EXIT);
@@ -165,47 +166,44 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
@NonNull SurfaceControl leash) {
Objects.requireNonNull(displayAreaInfo, "displayAreaInfo must not be null");
Objects.requireNonNull(leash, "leash must not be null");
-
- if (displayAreaInfo.featureId != FEATURE_ONE_HANDED) {
- Log.w(TAG, "Bypass onDisplayAreaAppeared()! displayAreaInfo=" + displayAreaInfo);
- return;
+ synchronized (this) {
+ if (mDisplayAreaMap.get(displayAreaInfo) == null) {
+ // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
+ mDefaultDisplayBounds.set(getDisplayBounds());
+ mDisplayAreaMap.put(displayAreaInfo, leash);
+ }
}
- // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
- mDefaultDisplayBounds.set(getDisplayBounds());
- mDisplayAreaMap.put(displayAreaInfo, leash);
}
@Override
public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
Objects.requireNonNull(displayAreaInfo,
"Requires valid displayArea, and displayArea must not be null");
-
- if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
- Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
- return;
+ synchronized (this) {
+ if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
+ Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
+ return;
+ }
+ mDisplayAreaMap.remove(displayAreaInfo);
}
- mDisplayAreaMap.remove(displayAreaInfo);
}
@Override
public void unregisterOrganizer() {
super.unregisterOrganizer();
- resetWindowsOffset();
-
- // Ensure all cached instance are cleared after resetWindowsOffset
- mUpdateHandler.post(() -> {
- if (mDisplayAreaMap != null && !mDisplayAreaMap.isEmpty()) {
- mDisplayAreaMap.clear();
- }
- });
+ mUpdateHandler.post(() -> resetWindowsOffset(null));
}
/**
* Handler for display rotation changes by below policy which
- * handles 90 degree display rotation changes {@link Surface.Rotation}
+ * handles 90 degree display rotation changes {@link Surface.Rotation}.
*
+ * @param fromRotation starting rotation of the display.
+ * @param toRotation target rotation of the display (after rotating).
+ * @param wct A task transaction {@link WindowContainerTransaction} from
+ * {@link DisplayChangeController} to populate.
*/
- public void onRotateDisplay(int fromRotation, int toRotation) {
+ public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
// Stop one handed without animation and reset cropped size immediately
final Rect newBounds = new Rect(mDefaultDisplayBounds);
final boolean isOrientationDiff = Math.abs(fromRotation - toRotation) % 2 == 1;
@@ -214,6 +212,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
newBounds.set(newBounds.left, newBounds.top, newBounds.bottom, newBounds.right);
SomeArgs args = SomeArgs.obtain();
args.arg1 = newBounds;
+ args.arg2 = wct;
args.argi1 = 0 /* xOffset */;
args.argi2 = 0 /* yOffset */;
args.argi3 = TRANSITION_DIRECTION_EXIT;
@@ -239,13 +238,19 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
throw new RuntimeException("Callers should call scheduleOffset() instead of this "
+ "directly");
}
- mDisplayAreaMap.forEach(
- (key, leash) -> animateWindows(leash, fromBounds, toBounds, direction,
- durationMs));
+ synchronized (this) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mDisplayAreaMap.forEach(
+ (key, leash) -> {
+ animateWindows(leash, fromBounds, toBounds, direction, durationMs);
+ wct.setBounds(key.token, toBounds);
+ });
+ applyTransaction(wct);
+ }
}
- private void resetWindowsOffset() {
- mUpdateHandler.post(() -> {
+ private void resetWindowsOffset(WindowContainerTransaction wct) {
+ synchronized (this) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
mDisplayAreaMap.forEach(
@@ -257,9 +262,13 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
tx.setPosition(leash, 0, 0)
.setWindowCrop(leash, -1/* reset */, -1/* reset */);
+ // DisplayRotationController will applyTransaction() after finish rotating
+ if (wct != null) {
+ wct.setBounds(key.token, null/* reset */);
+ }
});
tx.apply();
- });
+ }
}
private void animateWindows(SurfaceControl leash, Rect fromBounds, Rect toBounds,
@@ -330,8 +339,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
@VisibleForTesting
- Handler getUpdateHandler() {
- return mUpdateHandler;
+ void setUpdateHandler(Handler updateHandler) {
+ mUpdateHandler = updateHandler;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index b15b5154c2a4..b6b518d69c55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -29,6 +29,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -50,12 +52,16 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
private static final int MAX_TUTORIAL_SHOW_COUNT = 2;
private final Rect mLastUpdatedBounds = new Rect();
private final WindowManager mWindowManager;
+ private final AccessibilityManager mAccessibilityManager;
+ private final String mPackageName;
private View mTutorialView;
private Point mDisplaySize = new Point();
private Handler mUpdateHandler;
private ContentResolver mContentResolver;
private boolean mCanShowTutorial;
+ private String mStartOneHandedDescription;
+ private String mStopOneHandedDescription;
/**
* Container of the tutorial panel showing at outside region when one handed starting
@@ -72,9 +78,12 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
public OneHandedTutorialHandler(Context context) {
context.getDisplay().getRealSize(mDisplaySize);
+ mPackageName = context.getPackageName();
mContentResolver = context.getContentResolver();
mUpdateHandler = new Handler();
mWindowManager = context.getSystemService(WindowManager.class);
+ mAccessibilityManager = (AccessibilityManager)
+ context.getSystemService(Context.ACCESSIBILITY_SERVICE);
mTargetViewContainer = new FrameLayout(context);
mTargetViewContainer.setClipChildren(false);
mTutorialAreaHeight = Math.round(mDisplaySize.y
@@ -84,6 +93,10 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
mCanShowTutorial = (Settings.Secure.getInt(mContentResolver,
Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, 0) >= MAX_TUTORIAL_SHOW_COUNT)
? false : true;
+ mStartOneHandedDescription = context.getResources().getString(
+ R.string.accessibility_action_start_one_handed);
+ mStopOneHandedDescription = context.getResources().getString(
+ R.string.accessibility_action_stop_one_handed);
if (mCanShowTutorial) {
createOrUpdateTutorialTarget();
}
@@ -94,13 +107,16 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
mUpdateHandler.post(() -> {
updateFinished(View.VISIBLE, 0f);
updateTutorialCount();
+ announcementForScreenReader(true);
});
}
@Override
public void onStopFinished(Rect bounds) {
- mUpdateHandler.post(() -> updateFinished(
- View.INVISIBLE, -mTargetViewContainer.getHeight()));
+ mUpdateHandler.post(() -> {
+ updateFinished(View.INVISIBLE, -mTargetViewContainer.getHeight());
+ announcementForScreenReader(false);
+ });
}
private void updateFinished(int visible, float finalPosition) {
@@ -121,6 +137,17 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
Settings.Secure.ONE_HANDED_TUTORIAL_SHOW_COUNT, showCount);
}
+ private void announcementForScreenReader(boolean isStartOneHanded) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
+ final AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setPackageName(mPackageName);
+ event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ event.getText().add(isStartOneHanded
+ ? mStartOneHandedDescription : mStopOneHandedDescription);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
/**
* Adds the tutorial target view to the WindowManager and update its layout, so it's ready
* to be animated in.
@@ -157,9 +184,8 @@ public class OneHandedTutorialHandler implements OneHandedTransitionCallback {
mDisplaySize.x, mTutorialAreaHeight, 0, 0,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
lp.gravity = Gravity.TOP | Gravity.LEFT;
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setFitInsetsTypes(0 /* types */);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index 2091baaaf8a1..993e0e7ed016 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.wm.shell.pip;
import android.app.RemoteAction;
import android.content.ComponentName;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
new file mode 100644
index 000000000000..488f9092b7da
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -0,0 +1,262 @@
+/*
+ * 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.wm.shell.pip;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.media.session.MediaController;
+
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.pip.tv.PipController;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+/**
+ * Interface to engage picture in picture feature.
+ */
+public interface Pip {
+ /**
+ * Registers {@link com.android.wm.shell.pip.tv.PipController.Listener} that gets called.
+ * whenever receiving notification on changes in PIP.
+ */
+ default void addListener(PipController.Listener listener) {
+ }
+
+ /**
+ * Registers a {@link PipController.MediaListener} to PipController.
+ */
+ default void addMediaListener(PipController.MediaListener listener) {
+ }
+
+ /**
+ * Closes PIP (PIPed activity and PIP system UI).
+ */
+ default void closePip() {
+ }
+
+ /**
+ * Dump the current state and information if need.
+ *
+ * @param pw The stream to dump information to.
+ */
+ default void dump(PrintWriter pw) {
+ }
+
+ /**
+ * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
+ */
+ default void expandPip() {
+ }
+
+ /**
+ * Get current play back state. (e.g: Used in TV)
+ *
+ * @return The state of defined in PipController.
+ */
+ default int getPlaybackState() {
+ return -1;
+ }
+
+ /**
+ * Get the touch handler which manages all the touch handling for PIP on the Phone,
+ * including moving, dismissing and expanding the PIP. (Do not used in TV)
+ *
+ * @return
+ */
+ default @Nullable PipTouchHandler getPipTouchHandler() {
+ return null;
+ }
+
+ /**
+ * Get MediaController.
+ *
+ * @return The MediaController instance.
+ */
+ default MediaController getMediaController() {
+ return null;
+ }
+
+ /**
+ * Hides the PIP menu.
+ */
+ void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback);
+
+ /**
+ * Returns {@code true} if PIP is shown.
+ */
+ default boolean isPipShown() {
+ return false;
+ }
+
+ /**
+ * Moves the PIPed activity to the fullscreen and closes PIP system UI.
+ */
+ default void movePipToFullscreen() {
+ }
+
+ /**
+ * Called whenever an Activity is moved to the pinned stack from another stack.
+ */
+ default void onActivityPinned(String packageName) {
+ }
+
+ /**
+ * Called whenever an Activity is moved from the pinned stack to another stack
+ */
+ default void onActivityUnpinned(ComponentName topActivity) {
+ }
+
+ /**
+ * Called whenever IActivityManager.startActivity is called on an activity that is already
+ * running, but the task is either brought to the front or a new Intent is delivered to it.
+ *
+ * @param task information about the task the activity was relaunched into
+ * @param clearedTask whether or not the launch activity also cleared the task as a part of
+ * starting
+ */
+ default void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean clearedTask) {
+ }
+
+ /**
+ * Called when display size or font size of settings changed
+ */
+ default void onDensityOrFontScaleChanged() {
+ }
+
+ /**
+ * Called when overlay package change invoked.
+ */
+ default void onOverlayChanged() {
+ }
+
+ /**
+ * Registers the session listener for the current user.
+ */
+ default void registerSessionListenerForCurrentUser() {
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI state valid or not.
+ * @param flag Current SysUI state.
+ */
+ default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+ }
+
+ /**
+ * Called when task stack changed.
+ */
+ default void onTaskStackChanged() {
+ }
+
+ /**
+ * Removes a {@link PipController.Listener} from PipController.
+ */
+ default void removeListener(PipController.Listener listener) {
+ }
+
+ /**
+ * Removes a {@link PipController.MediaListener} from PipController.
+ */
+ default void removeMediaListener(PipController.MediaListener listener) {
+ }
+
+ /**
+ * Resize the Pip to the appropriate size for the input state.
+ *
+ * @param state In Pip state also used to determine the new size for the Pip.
+ */
+ default void resizePinnedStack(int state) {
+ }
+
+ /**
+ * Resumes resizing operation on the Pip that was previously suspended.
+ *
+ * @param reason The reason resizing operations on the Pip was suspended.
+ */
+ default void resumePipResizing(int reason) {
+ }
+
+ /**
+ * Sets both shelf visibility and its height.
+ *
+ * @param visible visibility of shelf.
+ * @param height to specify the height for shelf.
+ */
+ default void setShelfHeight(boolean visible, int height) {
+ }
+
+ /**
+ * Registers the pinned stack animation listener.
+ *
+ * @param callback The callback of pinned stack animation.
+ */
+ default void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+ }
+
+ /**
+ * Set the pinned stack with {@link PipAnimationController.AnimationType}
+ *
+ * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
+ */
+ default void setPinnedStackAnimationType(int animationType) {
+ }
+
+ /**
+ * Called when showing Pip menu.
+ */
+ void showPictureInPictureMenu();
+
+ /**
+ * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
+ *
+ * @param reason The reason for suspending resizing operations on the Pip.
+ */
+ default void suspendPipResizing(int reason) {
+ }
+
+ /**
+ * Called by Launcher when swiping an auto-pip enabled Activity to home starts
+ * @param componentName {@link ComponentName} represents the Activity entering PiP
+ * @param activityInfo {@link ActivityInfo} tied to the Activity
+ * @param pictureInPictureParams {@link PictureInPictureParams} tied to the Activity
+ * @param launcherRotation Rotation Launcher is in
+ * @param shelfHeight Shelf height when landing PiP window onto Launcher
+ * @return Destination bounds of PiP window based on the parameters passed in
+ */
+ default Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) {
+ return null;
+ }
+
+ /**
+ * Called by Launcher when swiping an auto-pip enable Activity to home finishes
+ * @param componentName {@link ComponentName} represents the Activity entering PiP
+ * @param destinationBounds Destination bounds of PiP window
+ */
+ default void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ return;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 419d8d590124..d82946269ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -26,7 +26,7 @@ import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Interpolators;
+import com.android.wm.shell.animation.Interpolators;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -86,12 +86,13 @@ public class PipAnimationController {
return handler;
});
- PipAnimationController(PipSurfaceTransactionHelper helper) {
+ public PipAnimationController(PipSurfaceTransactionHelper helper) {
mSurfaceTransactionHelper = helper;
}
@SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash,
+ @VisibleForTesting
+ public PipTransitionAnimator getAnimator(SurfaceControl leash,
Rect destinationBounds, float alphaStart, float alphaEnd) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
@@ -108,7 +109,8 @@ public class PipAnimationController {
}
@SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
+ @VisibleForTesting
+ public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
@@ -219,7 +221,7 @@ public class PipAnimationController {
public void onAnimationEnd(Animator animation) {
mCurrentValue = mEndValue;
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
- onEndTransaction(mLeash, tx);
+ onEndTransaction(mLeash, tx, mTransitionDirection);
if (mPipAnimationCallback != null) {
mPipAnimationCallback.onPipAnimationEnd(tx, this);
}
@@ -234,20 +236,23 @@ public class PipAnimationController {
@Override public void onAnimationRepeat(Animator animation) {}
- @AnimationType int getAnimationType() {
+ @VisibleForTesting
+ @AnimationType public int getAnimationType() {
return mAnimationType;
}
- PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
+ @VisibleForTesting
+ public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) {
mPipAnimationCallback = callback;
return this;
}
-
- @TransitionDirection int getTransitionDirection() {
+ @VisibleForTesting
+ @TransitionDirection public int getTransitionDirection() {
return mTransitionDirection;
}
- PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
+ @VisibleForTesting
+ public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) {
if (direction != TRANSITION_DIRECTION_SAME) {
mTransitionDirection = direction;
}
@@ -258,7 +263,8 @@ public class PipAnimationController {
return mStartValue;
}
- T getEndValue() {
+ @VisibleForTesting
+ public T getEndValue() {
return mEndValue;
}
@@ -295,7 +301,7 @@ public class PipAnimationController {
* animation. In which case we can update the end bounds and keep the existing animation
* running instead of cancelling it.
*/
- void updateEndValue(T endValue) {
+ public void updateEndValue(T endValue) {
mEndValue = endValue;
}
@@ -304,7 +310,7 @@ public class PipAnimationController {
}
@VisibleForTesting
- void setSurfaceControlTransactionFactory(
+ public void setSurfaceControlTransactionFactory(
PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
mSurfaceControlTransactionFactory = factory;
}
@@ -319,7 +325,8 @@ public class PipAnimationController {
void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
- void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {}
+ void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
+ @TransitionDirection int transitionDirection) {}
abstract void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction);
@@ -352,7 +359,7 @@ public class PipAnimationController {
}
@Override
- void updateEndValue(Float endValue) {
+ public void updateEndValue(Float endValue) {
super.updateEndValue(endValue);
mStartValue = mCurrentValue;
}
@@ -426,17 +433,24 @@ public class PipAnimationController {
}
@Override
- void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
+ void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
+ int transitionDirection) {
// NOTE: intentionally does not apply the transaction here.
// this end transaction should get executed synchronously with the final
// WindowContainerTransaction in task organizer
- getSurfaceTransactionHelper()
- .resetScale(tx, leash, getDestinationBounds())
- .crop(tx, leash, getDestinationBounds());
+ final Rect destBounds = getDestinationBounds();
+ getSurfaceTransactionHelper().resetScale(tx, leash, destBounds);
+ if (transitionDirection == TRANSITION_DIRECTION_LEAVE_PIP) {
+ // Leaving to fullscreen, reset crop to null.
+ tx.setPosition(leash, destBounds.left, destBounds.top);
+ tx.setWindowCrop(leash, 0, 0);
+ } else {
+ getSurfaceTransactionHelper().crop(tx, leash, destBounds);
+ }
}
@Override
- void updateEndValue(Rect endValue) {
+ public void updateEndValue(Rect endValue) {
super.updateEndValue(endValue);
if (mStartValue != null && mCurrentValue != null) {
mStartValue.set(mCurrentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java
index 89b5c38d94a7..de3261baf9b7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -39,7 +39,6 @@ import android.view.DisplayInfo;
import android.view.Gravity;
import android.window.WindowContainerTransaction;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.wm.shell.common.DisplayLayout;
import java.io.PrintWriter;
@@ -48,7 +47,6 @@ import java.io.PrintWriter;
* Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
* state changes originated from Window Manager and is the source of truth for PiP window bounds.
*/
-@SysUISingleton
public class PipBoundsHandler {
private static final String TAG = PipBoundsHandler.class.getSimpleName();
@@ -254,7 +252,7 @@ public class PipBoundsHandler {
/**
* See {@link #getDestinationBounds(ComponentName, float, Rect, Size, boolean)}
*/
- Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+ public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
Size minimalSize) {
return getDestinationBounds(componentName, aspectRatio, bounds, minimalSize,
false /* useCurrentMinEdgeSize */);
@@ -263,7 +261,7 @@ public class PipBoundsHandler {
/**
* @return {@link Rect} of the destination PiP window bounds.
*/
- Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+ public Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
Size minimalSize, boolean useCurrentMinEdgeSize) {
if (!componentName.equals(mLastPipComponentName)) {
onResetReentryBoundsUnchecked();
@@ -288,7 +286,7 @@ public class PipBoundsHandler {
return destinationBounds;
}
- float getDefaultAspectRatio() {
+ public float getDefaultAspectRatio() {
return mDefaultAspectRatio;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
new file mode 100644
index 000000000000..10e5c3d33def
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -0,0 +1,49 @@
+/*
+ * 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.wm.shell.pip;
+
+import android.annotation.NonNull;
+import android.graphics.Rect;
+
+import java.io.PrintWriter;
+
+/**
+ * Singleton source of truth for the current state of PIP bounds.
+ */
+public final class PipBoundsState {
+ private static final String TAG = PipBoundsState.class.getSimpleName();
+
+ private final @NonNull Rect mBounds = new Rect();
+
+ void setBounds(@NonNull Rect bounds) {
+ mBounds.set(bounds);
+ }
+
+ @NonNull
+ public Rect getBounds() {
+ return new Rect(mBounds);
+ }
+
+ /**
+ * Dumps internal state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mBounds=" + mBounds);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
index 5d23e4207c33..820930c463f2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSnapAlgorithm.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import android.content.Context;
import android.content.res.Resources;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3e98169c5b2b..b9a5536de743 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import android.content.Context;
import android.content.res.Resources;
@@ -23,17 +23,13 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.view.SurfaceControl;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.wm.shell.R;
/**
* Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
*/
-@SysUISingleton
-public class PipSurfaceTransactionHelper implements ConfigurationController.ConfigurationListener {
+public class PipSurfaceTransactionHelper {
- private final Context mContext;
private final boolean mEnableCornerRadius;
private int mCornerRadius;
@@ -44,24 +40,28 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
private final RectF mTmpDestinationRectF = new RectF();
private final Rect mTmpDestinationRect = new Rect();
- public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
+ public PipSurfaceTransactionHelper(Context context) {
final Resources res = context.getResources();
- mContext = context;
mEnableCornerRadius = res.getBoolean(R.bool.config_pipEnableRoundCorner);
- configController.addCallback(this);
}
- @Override
- public void onDensityOrFontScaleChanged() {
- final Resources res = mContext.getResources();
- mCornerRadius = res.getDimensionPixelSize(R.dimen.pip_corner_radius);
+ /**
+ * Called when display size or font size of settings changed
+ *
+ * @param context the current context
+ */
+ public void onDensityOrFontScaleChanged(Context context) {
+ if (mEnableCornerRadius) {
+ final Resources res = context.getResources();
+ mCornerRadius = res.getDimensionPixelSize(R.dimen.pip_corner_radius);
+ }
}
/**
* Operates the alpha on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
float alpha) {
tx.setAlpha(leash, alpha);
return this;
@@ -71,7 +71,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
* Operates the crop (and position) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect destinationBounds) {
tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
.setPosition(leash, destinationBounds.left, destinationBounds.top);
@@ -82,7 +82,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
* Operates the scale (setMatrix) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRectF.set(destinationBounds);
@@ -96,7 +96,8 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
* Operates the scale (setMatrix) on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
+ SurfaceControl leash,
Rect sourceBounds, Rect destinationBounds, Rect insets) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
@@ -117,9 +118,11 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
/**
* Resets the scale (setMatrix) on a given transaction and leash if there's any
+ *
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper resetScale(SurfaceControl.Transaction tx,
+ SurfaceControl leash,
Rect destinationBounds) {
tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
.setPosition(leash, destinationBounds.left, destinationBounds.top);
@@ -130,7 +133,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
* Operates the round corner radius on a given transaction and leash
* @return same {@link PipSurfaceTransactionHelper} instance for method chaining
*/
- PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
+ public PipSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash,
boolean applyCornerRadius) {
if (mEnableCornerRadius) {
tx.setCornerRadius(leash, applyCornerRadius ? mCornerRadius : 0);
@@ -138,7 +141,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
return this;
}
- interface SurfaceControlTransactionFactory {
+ public interface SurfaceControlTransactionFactory {
SurfaceControl.Transaction getTransaction();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b6334c562a02..3485c7ae8ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -14,23 +14,24 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
-import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.systemui.pip.PipAnimationController.isInPipDirection;
-import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
+import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,13 +59,15 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.os.SomeArgs;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.pip.phone.PipMenuActivityController;
-import com.android.systemui.pip.phone.PipUpdateThread;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipUpdateThread;
+import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.PrintWriter;
@@ -85,10 +88,9 @@ import java.util.function.Consumer;
* and files a final {@link WindowContainerTransaction} at the end of the transition.
*
* This class is also responsible for general resize/offset PiP operations within SysUI component,
- * see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
+ * see also {@link PipMotionHelper}.
*/
-@SysUISingleton
-public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganizer.TaskListener,
+public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
DisplayController.OnDisplaysChangedListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -104,7 +106,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
UNDEFINED(0),
TASK_APPEARED(1),
ENTERING_PIP(2),
- EXITING_PIP(3);
+ ENTERED_PIP(3),
+ EXITING_PIP(4);
private final int mStateValue;
@@ -129,14 +132,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
}
}
- private final Context mContext;
private final Handler mMainHandler;
private final Handler mUpdateHandler;
+ private final PipBoundsState mPipBoundsState;
private final PipBoundsHandler mPipBoundsHandler;
private final PipAnimationController mPipAnimationController;
private final PipUiEventLogger mPipUiEventLoggerLogger;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
- private final Rect mLastReportedBounds = new Rect();
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
@@ -150,15 +152,25 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
new PipAnimationController.PipAnimationCallback() {
@Override
public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) {
- sendOnPipTransitionStarted(animator.getTransitionDirection());
+ final int direction = animator.getTransitionDirection();
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ InteractionJankMonitor.getInstance().begin(
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
+ }
+ sendOnPipTransitionStarted(direction);
}
@Override
public void onPipAnimationEnd(SurfaceControl.Transaction tx,
PipAnimationController.PipTransitionAnimator animator) {
- finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection(),
+ final int direction = animator.getTransitionDirection();
+ finishResize(tx, animator.getDestinationBounds(), direction,
animator.getAnimationType());
- sendOnPipTransitionFinished(animator.getTransitionDirection());
+ sendOnPipTransitionFinished(direction);
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ InteractionJankMonitor.getInstance().end(
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
+ }
}
@Override
@@ -242,15 +254,24 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
*/
private boolean mShouldDeferEnteringPip;
- public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
+ /**
+ * If set to {@code true}, no entering PiP transition would be kicked off and most likely
+ * it's due to the fact that Launcher is handling the transition directly when swiping
+ * auto PiP-able Activity to home.
+ * See also {@link #startSwipePipToHome(ComponentName, ActivityInfo, PictureInPictureParams)}.
+ */
+ private boolean mShouldIgnoreEnteringPipTransition;
+
+ public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
+ @NonNull PipBoundsHandler boundsHandler,
@NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer) {
- mContext = context;
mMainHandler = new Handler(Looper.getMainLooper());
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
+ mPipBoundsState = pipBoundsState;
mPipBoundsHandler = boundsHandler;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
@@ -260,25 +281,26 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
- mTaskOrganizer.addListener(this, WINDOWING_MODE_PINNED);
- displayController.addDisplayWindowListener(this);
+
+ if (!PipUtils.hasSystemFeature(context)) {
+ Log.w(TAG, "Device not support PIP feature");
+ } else {
+ mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_PIP);
+ displayController.addDisplayWindowListener(this);
+ }
}
public Handler getUpdateHandler() {
return mUpdateHandler;
}
- public Rect getLastReportedBounds() {
- return new Rect(mLastReportedBounds);
- }
-
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
return new Rect(animator.getDestinationBounds());
}
- return getLastReportedBounds();
+ return mPipBoundsState.getBounds();
}
public boolean isInPip() {
@@ -306,6 +328,27 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
}
/**
+ * Callback when Launcher starts swipe-pip-to-home operation.
+ * @return {@link Rect} for destination bounds.
+ */
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams) {
+ mShouldIgnoreEnteringPipTransition = true;
+ mState = State.ENTERING_PIP;
+ return mPipBoundsHandler.getDestinationBounds(componentName,
+ getAspectRatioOrDefault(pictureInPictureParams),
+ null /* bounds */, getMinimalSize(activityInfo));
+ }
+
+ /**
+ * Callback when launcher finishes swipe-pip-to-home operation.
+ * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
+ */
+ public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ mPipBoundsState.setBounds(destinationBounds);
+ }
+
+ /**
* Expands PiP to the previous bounds, this is done in two phases using
* {@link WindowContainerTransaction}
* - setActivityWindowingMode to either fullscreen or split-secondary at beginning of the
@@ -321,9 +364,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
return;
}
+ final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
+ if (initialConfig == null) {
+ Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken);
+ return;
+ }
mPipUiEventLoggerLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
- final Configuration initialConfig = mInitialState.remove(mToken.asBinder());
final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
!= mPipBoundsHandler.getDisplayRotation();
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -345,7 +392,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
- mLastReportedBounds);
+ mPipBoundsState.getBounds());
tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
// We set to fullscreen here for now, but later it will be set to UNDEFINED for
// the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
@@ -359,9 +406,9 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
- scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- getValidSourceHintRect(mTaskInfo, destinationBounds), direction,
- animationDurationMs, null /* updateBoundsCallback */);
+ scheduleAnimateResizePip(mPipBoundsState.getBounds(),
+ destinationBounds, getValidSourceHintRect(mTaskInfo, destinationBounds),
+ direction, animationDurationMs, null /* updateBoundsCallback */);
mState = State.EXITING_PIP;
}
});
@@ -392,7 +439,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
// removePipImmediately is expected when the following animation finishes.
mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, mLastReportedBounds, 1f, 0f)
+ .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
.setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
@@ -408,7 +455,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
wct.setBounds(mToken, null);
mTaskOrganizer.applyTransaction(wct);
- ActivityTaskManager.getService().removeStacksInWindowingModes(
+ ActivityTaskManager.getService().removeRootTasksInWindowingModes(
new int[]{ WINDOWING_MODE_PINNED });
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove PiP", e);
@@ -428,6 +475,16 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
+ if (mShouldIgnoreEnteringPipTransition) {
+ // Animation has been finished together with Recents, directly apply the sync
+ // transaction to PiP here.
+ applyEnterPipSyncTransaction(mPipBoundsState.getBounds(), () -> {
+ mState = State.ENTERED_PIP;
+ });
+ mShouldIgnoreEnteringPipTransition = false;
+ return;
+ }
+
if (mShouldDeferEnteringPip) {
if (DEBUG) Log.d(TAG, "Defer entering PiP animation, fixed rotation is ongoing");
// if deferred, hide the surface till fixed rotation is completed
@@ -482,6 +539,20 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
mSurfaceControlTransactionFactory.getTransaction();
tx.setAlpha(mLeash, 0f);
tx.apply();
+ applyEnterPipSyncTransaction(destinationBounds, () -> {
+ mUpdateHandler.post(() -> mPipAnimationController
+ .getAnimator(mLeash, destinationBounds, 0f, 1f)
+ .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(durationMs)
+ .start());
+ // mState is set right after the animation is kicked off to block any resize
+ // requests such as offsetPip that may have been called prior to the transition.
+ mState = State.ENTERING_PIP;
+ });
+ }
+
+ private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
@@ -490,22 +561,16 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
- mUpdateHandler.post(() -> mPipAnimationController
- .getAnimator(mLeash, destinationBounds, 0f, 1f)
- .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
- .setPipAnimationCallback(mPipAnimationCallback)
- .setDuration(durationMs)
- .start());
- // mState is set right after the animation is kicked off to block any resize
- // requests such as offsetPip that may have been called prior to the transition.
- mState = State.ENTERING_PIP;
+ if (runnable != null) {
+ runnable.run();
+ }
}
});
}
private void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
- final Rect pipBounds = new Rect(mLastReportedBounds);
+ final Rect pipBounds = mPipBoundsState.getBounds();
runOnMainHandler(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -516,6 +581,9 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
private void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ mState = State.ENTERED_PIP;
+ }
runOnMainHandler(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -544,23 +612,24 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
/**
* Setup the ViewHost and attach the provided menu view to the ViewHost.
+ * @return The input token belonging to the PipMenuView.
*/
- public void attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
+ public IBinder attachPipMenuViewHost(View menuView, WindowManager.LayoutParams lp) {
if (mPipMenuSurface != null) {
Log.e(TAG, "PIP Menu View already created and attached.");
- return;
+ return null;
}
if (mLeash == null) {
Log.e(TAG, "PiP Leash is not yet ready.");
- return;
+ return null;
}
if (Looper.getMainLooper() != Looper.myLooper()) {
throw new RuntimeException("PipMenuView needs to be attached on the main thread.");
}
-
- mPipViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
+ final Context context = menuView.getContext();
+ mPipViewHost = new SurfaceControlViewHost(context, context.getDisplay(),
(android.os.Binder) null);
mPipMenuSurface = mPipViewHost.getSurfacePackage().getSurfaceControl();
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
@@ -569,6 +638,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
transaction.setRelativeLayer(mPipMenuSurface, mLeash, 1);
transaction.apply();
mPipViewHost.setView(menuView, lp);
+
+ return mPipViewHost.getSurfacePackage().getInputToken();
}
@@ -596,7 +667,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
/**
* Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
* Meanwhile this callback is invoked whenever the task is removed. For instance:
- * - as a result of removeStacksInWindowingModes from WM
+ * - as a result of removeRootTasksInWindowingModes from WM
* - activity itself is died
* Nevertheless, we simply update the internal state here as all the heavy lifting should
* have been done in WM.
@@ -628,7 +699,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
}
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
info.topActivity, getAspectRatioOrDefault(newParams),
- mLastReportedBounds, getMinimalSize(info.topActivityInfo),
+ mPipBoundsState.getBounds(), getMinimalSize(info.topActivityInfo),
true /* userCurrentMinEdgeSize */);
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
@@ -653,6 +724,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
}
/**
+ * Called when display size or font size of settings changed
+ */
+ public void onDensityOrFontScaleChanged(Context context) {
+ mSurfaceTransactionHelper.onDensityOrFontScaleChanged(context);
+ }
+
+ /**
* TODO(b/152809058): consolidate the display info handling logic in SysUI
*
* @param destinationBoundsOut the current destination bounds will be populated to this param
@@ -679,7 +757,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
sendOnPipTransitionCancelled(direction);
sendOnPipTransitionFinished(direction);
}
- mLastReportedBounds.set(destinationBoundsOut);
+ mPipBoundsState.setBounds(destinationBoundsOut);
// Create a reset surface transaction for the new bounds and update the window
// container transaction
@@ -694,8 +772,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
destinationBoundsOut.set(animator.getDestinationBounds());
}
} else {
- if (!mLastReportedBounds.isEmpty()) {
- destinationBoundsOut.set(mLastReportedBounds);
+ if (!mPipBoundsState.getBounds().isEmpty()) {
+ destinationBoundsOut.set(mPipBoundsState.getBounds());
}
}
}
@@ -747,7 +825,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
return;
}
- scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
+ scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, null /* sourceHintRect */,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
@@ -883,7 +961,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
Log.w(TAG, "Abort animation, invalid leash");
return;
}
- mLastReportedBounds.set(destinationBounds);
+ mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
@@ -919,7 +997,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
+ "directly");
}
- mLastReportedBounds.set(destinationBounds);
+ mPipBoundsState.setBounds(destinationBounds);
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
return;
@@ -1061,7 +1139,6 @@ public class PipTaskOrganizer extends TaskOrganizer implements ShellTaskOrganize
pw.println(innerPrefix + "mState=" + mState);
pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
- pw.println(innerPrefix + "mLastReportedBounds=" + mLastReportedBounds);
pw.println(innerPrefix + "mInitialState:");
for (Map.Entry<IBinder, Configuration> e : mInitialState.entrySet()) {
pw.println(innerPrefix + " binder=" + e.getKey()
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index 22adbb77d70a..de3bb2950c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import android.app.TaskInfo;
import android.content.pm.PackageManager;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.dagger.SysUISingleton;
/**
* Helper class that ends PiP log to UiEvent, see also go/uievent
*/
-@SysUISingleton
public class PipUiEventLogger {
private static final int INVALID_PACKAGE_UID = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
index a13318990f40..18b6922f3067 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
@@ -27,9 +28,10 @@ import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
-import com.android.systemui.pip.PipSnapAlgorithm;
-import com.android.systemui.pip.PipTaskOrganizer;
import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
import java.util.List;
@@ -50,6 +52,7 @@ public class PipAccessibilityInteractionConnection
private Context mContext;
private Handler mHandler;
+ private final @NonNull PipBoundsState mPipBoundsState;
private PipMotionHelper mMotionHelper;
private PipTaskOrganizer mTaskOrganizer;
private PipSnapAlgorithm mSnapAlgorithm;
@@ -62,12 +65,14 @@ public class PipAccessibilityInteractionConnection
private final Rect mExpandedMovementBounds = new Rect();
private Rect mTmpBounds = new Rect();
- public PipAccessibilityInteractionConnection(Context context, PipMotionHelper motionHelper,
+ public PipAccessibilityInteractionConnection(Context context,
+ @NonNull PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer taskOrganizer, PipSnapAlgorithm snapAlgorithm,
AccessibilityCallbacks callbacks, Runnable updateMovementBoundCallback,
Handler handler) {
mContext = context;
mHandler = handler;
+ mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
mTaskOrganizer = taskOrganizer;
mSnapAlgorithm = snapAlgorithm;
@@ -148,7 +153,7 @@ public class PipAccessibilityInteractionConnection
private void setToExpandedBounds() {
float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
- new Rect(mTaskOrganizer.getLastReportedBounds()), mNormalMovementBounds);
+ mPipBoundsState.getBounds(), mNormalMovementBounds);
mSnapAlgorithm.applySnapFraction(mExpandedBounds, mExpandedMovementBounds,
savedSnapFraction);
mTaskOrganizer.scheduleFinishResizePip(mExpandedBounds, (Rect bounds) -> {
@@ -159,7 +164,7 @@ public class PipAccessibilityInteractionConnection
private void setToNormalBounds() {
float savedSnapFraction = mSnapAlgorithm.getSnapFraction(
- new Rect(mTaskOrganizer.getLastReportedBounds()), mExpandedMovementBounds);
+ mPipBoundsState.getBounds(), mExpandedMovementBounds);
mSnapAlgorithm.applySnapFraction(mNormalBounds, mNormalMovementBounds, savedSnapFraction);
mTaskOrganizer.scheduleFinishResizePip(mNormalBounds, (Rect bounds) -> {
mMotionHelper.synchronizePinnedStackBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
index 7dfd99c2110d..6b6b5211b10a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5bb179484e5d..d191c980375c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,69 +14,50 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
+import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.IActivityManager;
+import android.app.PictureInPictureParams;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
-import android.util.Pair;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
import android.window.WindowContainerTransaction;
-import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
+import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
-@SysUISingleton
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
private static final String TAG = "PipController";
private Context mContext;
- private IActivityManager mActivityManager;
private Handler mHandler = new Handler();
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
@@ -85,13 +66,14 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
protected final Rect mReentryBounds = new Rect();
private DisplayController mDisplayController;
- private InputConsumerController mInputConsumerController;
private PipAppOpsListener mAppOpsListener;
private PipBoundsHandler mPipBoundsHandler;
+ private @NonNull PipBoundsState mPipBoundsState;
private PipMediaController mMediaController;
private PipTouchHandler mTouchHandler;
- private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private IPinnedStackAnimationListener mPinnedStackAnimationRecentsListener;
+ private Consumer<Boolean> mPinnedStackAnimationRecentsCallback;
+ private WindowManagerShellWrapper mWindowManagerShellWrapper;
+
private boolean mIsInFixedRotation;
protected PipMenuActivityController mMenuController;
@@ -110,7 +92,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
// If there is an animation running (ie. from a shelf offset), then ensure that we calculate
// the bounds for the next orientation using the destination bounds of the animation
- // TODO: Techincally this should account for movement animation bounds as well
+ // TODO: Technically this should account for movement animation bounds as well
Rect currentBounds = mPipTaskOrganizer.getCurrentOrAnimatingBounds();
final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mContext,
mTmpNormalBounds, currentBounds, mTmpInsetBounds, displayId, fromRotation,
@@ -119,7 +101,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
// If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
// movement bounds
mTouchHandler.adjustBoundsForRotation(mTmpNormalBounds,
- mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds);
+ mPipBoundsState.getBounds(), mTmpInsetBounds);
// The bounds are being applied to a specific snap fraction, so reset any known offsets
// for the previous orientation before updating the movement bounds.
@@ -158,50 +140,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
};
/**
- * Handler for system task stack changes.
- */
- private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- mTouchHandler.onActivityPinned();
- mMediaController.onActivityPinned();
- mMenuController.onActivityPinned();
- mAppOpsListener.onActivityPinned(packageName);
-
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(true);
- });
- }
-
- @Override
- public void onActivityUnpinned() {
- final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPipActivity(
- mContext, mActivityManager);
- final ComponentName topActivity = topPipActivityInfo.first;
- mMenuController.onActivityUnpinned();
- mTouchHandler.onActivityUnpinned(topActivity);
- mAppOpsListener.onActivityUnpinned();
-
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(topActivity != null);
- });
- }
-
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_PINNED) {
- return;
- }
- mTouchHandler.getMotionHelper().expandLeavePip(clearedTask /* skipAnimation */);
- }
- };
-
- /**
* Handler for messages from the PIP controller.
*/
- private class PipControllerPinnedStackListener extends PinnedStackListener {
+ private class PipControllerPinnedStackListener extends
+ PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onListenerRegistered(IPinnedStackController controller) {
mHandler.post(() -> mTouchHandler.setPinnedStackController(controller));
@@ -239,7 +181,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
@Override
public void onConfigurationChanged() {
- mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged(mContext));
+ mHandler.post(() -> {
+ mPipBoundsHandler.onConfigurationChanged(mContext);
+ mTouchHandler.onConfigurationChanged();
+ });
}
@Override
@@ -251,54 +196,38 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
- public ConfigurationController.ConfigurationListener mOverlayChangedListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onOverlayChanged() {
- mHandler.post(() -> {
- mPipBoundsHandler.onOverlayChanged(mContext, mContext.getDisplay());
- updateMovementBounds(null /* toBounds */,
- false /* fromRotation */, false /* fromImeAdjustment */,
- false /* fromShelfAdjustment */,
- null /* windowContainerTransaction */);
- });
- }
- };
-
- public PipController(Context context, BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
+ public PipController(Context context,
DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
+ PipAppOpsListener pipAppOpsListener,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ @NonNull PipBoundsState pipBoundsState,
+ PipMediaController pipMediaController,
+ PipMenuActivityController pipMenuActivityController,
PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
+ PipTouchHandler pipTouchHandler,
+ WindowManagerShellWrapper windowManagerShellWrapper
+ ) {
mContext = context;
- mActivityManager = ActivityManager.getService();
-
- PackageManager pm = context.getPackageManager();
- boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
- if (supportsPip) {
- initController(context, broadcastDispatcher, configController, deviceConfig,
- displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
- pipSurfaceTransactionHelper, pipTaskOrganizer, pipUiEventLogger);
+
+ if (PipUtils.hasSystemFeature(mContext)) {
+ initController(context, displayController, pipAppOpsListener, pipBoundsHandler,
+ pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer,
+ pipTouchHandler, windowManagerShellWrapper);
} else {
Log.w(TAG, "Device not support PIP feature");
}
}
- private void initController(Context context, BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
+ private void initController(Context context,
DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
+ PipAppOpsListener pipAppOpsListener,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ @NonNull PipBoundsState pipBoundsState,
+ PipMediaController pipMediaController,
+ PipMenuActivityController pipMenuActivityController,
PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
+ PipTouchHandler pipTouchHandler,
+ WindowManagerShellWrapper windowManagerShellWrapper) {
// Ensure that we are the primary user's SystemUI.
final int processUser = UserManager.get(context).getUserHandle();
@@ -306,28 +235,16 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
throw new IllegalStateException("Non-primary Pip component not currently supported.");
}
- try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(
- new PipControllerPinnedStackListener());
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
- }
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
-
+ mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsHandler = pipBoundsHandler;
- mPipSurfaceTransactionHelper = pipSurfaceTransactionHelper;
+ mPipBoundsState = pipBoundsState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTaskOrganizer.registerPipTransitionCallback(this);
- mInputConsumerController = InputConsumerController.getPipInputConsumer();
- mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
- mMenuController = new PipMenuActivityController(context,
- mMediaController, mInputConsumerController, mPipTaskOrganizer);
- mTouchHandler = new PipTouchHandler(context, mActivityManager,
- mMenuController, mInputConsumerController, mPipBoundsHandler, mPipTaskOrganizer,
- floatingContentCoordinator, deviceConfig, sysUiState, pipUiEventLogger);
- mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
- mTouchHandler.getMotionHelper());
+ mMediaController = pipMediaController;
+ mMenuController = pipMenuActivityController;
+ mTouchHandler = pipTouchHandler;
+ mAppOpsListener = pipAppOpsListener;
displayController.addDisplayChangingController(mRotationController);
displayController.addDisplayWindowListener(mFixedRotationListener);
@@ -337,26 +254,69 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
context.getDisplay().getDisplayInfo(displayInfo);
mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
- configController.addCallback(mOverlayChangedListener);
-
try {
- RootTaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
- WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
- if (taskInfo != null) {
- // If SystemUI restart, and it already existed a pinned stack,
- // register the pip input consumer to ensure touch can send to it.
- mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
- }
- } catch (RemoteException | UnsupportedOperationException e) {
- e.printStackTrace();
+ mWindowManagerShellWrapper.addPinnedStackListener(
+ new PipControllerPinnedStackListener());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
}
}
- /**
- * Updates the PIP per configuration changed.
- */
- public void onConfigurationChanged(Configuration newConfig) {
- mTouchHandler.onConfigurationChanged();
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ mHandler.post(() -> {
+ mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
+ });
+ }
+
+ @Override
+ public void onActivityPinned(String packageName) {
+ mHandler.post(() -> {
+ mTouchHandler.onActivityPinned();
+ mMediaController.onActivityPinned();
+ mMenuController.onActivityPinned();
+ mAppOpsListener.onActivityPinned(packageName);
+ });
+ }
+
+ @Override
+ public void onActivityUnpinned(ComponentName topActivity) {
+ mHandler.post(() -> {
+ mMenuController.onActivityUnpinned();
+ mTouchHandler.onActivityUnpinned(topActivity);
+ mAppOpsListener.onActivityUnpinned();
+ });
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean clearedTask) {
+ if (task.configuration.windowConfiguration.getWindowingMode()
+ != WINDOWING_MODE_PINNED) {
+ return;
+ }
+ mTouchHandler.getMotionHelper().expandLeavePip(clearedTask /* skipAnimation */);
+ }
+
+ @Override
+ public void onOverlayChanged() {
+ mHandler.post(() -> {
+ mPipBoundsHandler.onOverlayChanged(mContext, mContext.getDisplay());
+ updateMovementBounds(null /* toBounds */,
+ false /* fromRotation */, false /* fromImeAdjustment */,
+ false /* fromShelfAdjustment */,
+ null /* windowContainerTransaction */);
+ });
+ }
+
+ @Override
+ public void registerSessionListenerForCurrentUser() {
+ mMediaController.registerSessionListenerForCurrentUser();
+ }
+
+ @Override
+ public void onSystemUiStateChanged(boolean isValidState, int flag) {
+ mTouchHandler.onSystemUiStateChanged(isValidState);
}
/**
@@ -367,6 +327,11 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
}
+ @Override
+ public PipTouchHandler getPipTouchHandler() {
+ return mTouchHandler;
+ }
+
/**
* Hides the PIP menu.
*/
@@ -394,16 +359,18 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
*/
@Override
public void setShelfHeight(boolean visible, int height) {
- mHandler.post(() -> {
- final int shelfHeight = visible ? height : 0;
- final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
- if (changed) {
- mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
- updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
- false /* fromRotation */, false /* fromImeAdjustment */,
- true /* fromShelfAdjustment */, null /* windowContainerTransaction */);
- }
- });
+ mHandler.post(() -> setShelfHeightLocked(visible, height));
+ }
+
+ private void setShelfHeightLocked(boolean visible, int height) {
+ final int shelfHeight = visible ? height : 0;
+ final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
+ if (changed) {
+ mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
+ updateMovementBounds(mPipBoundsState.getBounds(),
+ false /* fromRotation */, false /* fromImeAdjustment */,
+ true /* fromShelfAdjustment */, null /* windowContainerTransaction */);
+ }
}
@Override
@@ -412,8 +379,23 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
@Override
- public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- mHandler.post(() -> mPinnedStackAnimationRecentsListener = listener);
+ public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+ mHandler.post(() -> mPinnedStackAnimationRecentsCallback = callback);
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) {
+ setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
+ mPipBoundsHandler.onDisplayRotationChangedNotInPip(mContext, launcherRotation);
+ return mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
+ pictureInPictureParams);
+ }
+
+ @Override
+ public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
}
@Override
@@ -425,12 +407,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
// Disable touches while the animation is running
mTouchHandler.setTouchEnabled(false);
- if (mPinnedStackAnimationRecentsListener != null) {
- try {
- mPinnedStackAnimationRecentsListener.onPinnedStackAnimationStarted();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to callback recents", e);
- }
+ if (mPinnedStackAnimationRecentsCallback != null) {
+ mPinnedStackAnimationRecentsCallback.accept(true);
}
}
@@ -481,10 +459,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
public void dump(PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG);
- mInputConsumerController.dump(pw, innerPrefix);
mMenuController.dump(pw, innerPrefix);
mTouchHandler.dump(pw, innerPrefix);
mPipBoundsHandler.dump(pw, innerPrefix);
mPipTaskOrganizer.dump(pw, innerPrefix);
+ mPipBoundsState.dump(pw, innerPrefix);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
new file mode 100644
index 000000000000..bebe5f965251
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -0,0 +1,295 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipUiEventLogger;
+
+import kotlin.Unit;
+
+/**
+ * Handler of all Magnetized Object related code for PiP.
+ */
+public class PipDismissTargetHandler {
+
+ /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
+ /** Duration of the dismiss scrim fading in/out. */
+ private static final int DISMISS_TRANSITION_DURATION_MS = 200;
+
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private final MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private final ViewGroup mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private final DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private final MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ /** PhysicsAnimator instance for animating the dismiss target in/out. */
+ private final PhysicsAnimator<View> mMagneticTargetAnimator;
+
+ /** Default configuration to use for springing the dismiss target in/out. */
+ private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
+ new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
+ /**
+ * Runnable that can be posted delayed to show the target. This needs to be saved as a member
+ * variable so we can pass it to removeCallbacks.
+ */
+ private Runnable mShowTargetAction = this::showDismissTargetMaybe;
+
+ // Allow dragging the PIP to a location to close it
+ private final boolean mEnableDismissDragToEdge;
+
+ private int mDismissAreaHeight;
+
+ private final Context mContext;
+ private final PipMotionHelper mMotionHelper;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final WindowManager mWindowManager;
+ private final Handler mHandler;
+
+ public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
+ PipMotionHelper motionHelper, Handler handler) {
+ mContext = context;
+ mPipUiEventLogger = pipUiEventLogger;
+ mMotionHelper = motionHelper;
+ mHandler = handler;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+
+ Resources res = context.getResources();
+ mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ mTargetView = new DismissCircleView(context);
+ mTargetViewContainer = new FrameLayout(context);
+ mTargetViewContainer.setBackgroundDrawable(
+ context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
+ mTargetViewContainer.setClipChildren(false);
+ mTargetViewContainer.addView(mTargetView);
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ updateMagneticTargetSize();
+
+ mMagnetizedPip.setAnimateStuckToTarget(
+ (target, velX, velY, flung, after) -> {
+ if (mEnableDismissDragToEdge) {
+ mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+ }
+ return Unit.INSTANCE;
+ });
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ // Show the dismiss target, in case the initial touch event occurred within the
+ // magnetic field radius.
+ if (mEnableDismissDragToEdge) {
+ showDismissTargetMaybe();
+ }
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+ hideDismissTargetMaybe();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mMotionHelper.notifyDismissalPending();
+
+ handler.post(() -> {
+ mMotionHelper.animateDismiss();
+ hideDismissTargetMaybe();
+ });
+
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+ }
+ });
+
+ mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
+ }
+
+ /**
+ * Potentially start consuming future motion events if PiP is currently near the magnetized
+ * object.
+ */
+ public boolean maybeConsumeMotionEvent(MotionEvent ev) {
+ return mMagnetizedPip.maybeConsumeMotionEvent(ev);
+ }
+
+ /**
+ * Update the magnet size.
+ */
+ public void updateMagneticTargetSize() {
+ if (mTargetView == null) {
+ return;
+ }
+
+ final Resources res = mContext.getResources();
+ final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+ final FrameLayout.LayoutParams newParams =
+ new FrameLayout.LayoutParams(targetSize, targetSize);
+ newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+ newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.floating_dismiss_bottom_margin);
+ mTargetView.setLayoutParams(newParams);
+
+ // Set the magnetic field radius equal to the target size from the center of the target
+ mMagneticTarget.setMagneticFieldRadiusPx(
+ (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ }
+
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ public void createOrUpdateDismissTarget() {
+ if (!mTargetViewContainer.isAttachedToWindow()) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator.cancel();
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+
+ try {
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+ } catch (IllegalStateException e) {
+ // This shouldn't happen, but if the target is already added, just update its layout
+ // params.
+ mWindowManager.updateViewLayout(
+ mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ } else {
+ mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ }
+
+ /** Returns layout params for the dismiss target, using the latest display metrics. */
+ private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ mDismissAreaHeight,
+ 0, windowSize.y - mDismissAreaHeight,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ return lp;
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ public void showDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+
+ createOrUpdateDismissTarget();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+
+ mTargetView.setTranslationY(mTargetViewContainer.getHeight());
+ mTargetViewContainer.setVisibility(View.VISIBLE);
+
+ // Cancel in case we were in the middle of animating it out.
+ mMagneticTargetAnimator.cancel();
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
+ .start();
+
+ ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
+ DISMISS_TRANSITION_DURATION_MS);
+ }
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ public void hideDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+
+ mHandler.removeCallbacks(mShowTargetAction);
+ mMagneticTargetAnimator
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mTargetViewContainer.getHeight(),
+ mTargetSpringConfig)
+ .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
+ .start();
+
+ ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
+ DISMISS_TRANSITION_DURATION_MS);
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ public void cleanUpDismissTarget() {
+ mHandler.removeCallbacks(mShowTargetAction);
+
+ if (mTargetViewContainer.isAttachedToWindow()) {
+ mWindowManager.removeViewImmediate(mTargetViewContainer);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMediaController.java
index 361aafacdf76..4a8db6b42b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMediaController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -26,19 +26,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener;
import android.media.session.PlaybackState;
import android.os.UserHandle;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.wm.shell.R;
import java.util.ArrayList;
import java.util.Collections;
@@ -51,10 +46,10 @@ import java.util.List;
*/
public class PipMediaController {
- private static final String ACTION_PLAY = "com.android.systemui.pip.phone.PLAY";
- private static final String ACTION_PAUSE = "com.android.systemui.pip.phone.PAUSE";
- private static final String ACTION_NEXT = "com.android.systemui.pip.phone.NEXT";
- private static final String ACTION_PREV = "com.android.systemui.pip.phone.PREV";
+ private static final String ACTION_PLAY = "com.android.wm.shell.pip.phone.PLAY";
+ private static final String ACTION_PAUSE = "com.android.wm.shell.pip.phone.PAUSE";
+ private static final String ACTION_NEXT = "com.android.wm.shell.pip.phone.NEXT";
+ private static final String ACTION_PREV = "com.android.wm.shell.pip.phone.PREV";
/**
* A listener interface to receive notification on changes to the media actions.
@@ -93,25 +88,20 @@ public class PipMediaController {
}
};
- private final MediaController.Callback mPlaybackChangedListener = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- notifyActionsChanged();
- }
- };
+ private final MediaController.Callback mPlaybackChangedListener =
+ new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ notifyActionsChanged();
+ }
+ };
private final MediaSessionManager.OnActiveSessionsChangedListener mSessionsChangedListener =
- new OnActiveSessionsChangedListener() {
- @Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- resolveActiveMediaController(controllers);
- }
- };
+ controllers -> resolveActiveMediaController(controllers);
private ArrayList<ActionListener> mListeners = new ArrayList<>();
- public PipMediaController(Context context, IActivityManager activityManager,
- BroadcastDispatcher broadcastDispatcher) {
+ public PipMediaController(Context context, IActivityManager activityManager) {
mContext = context;
mActivityManager = activityManager;
IntentFilter mediaControlFilter = new IntentFilter();
@@ -119,16 +109,12 @@ public class PipMediaController {
mediaControlFilter.addAction(ACTION_PAUSE);
mediaControlFilter.addAction(ACTION_NEXT);
mediaControlFilter.addAction(ACTION_PREV);
- broadcastDispatcher.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter);
+ mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter,
+ UserHandle.USER_ALL);
createMediaActions();
mMediaSessionManager =
(MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- // The media session listener needs to be re-registered when switching users
- UserInfoController userInfoController = Dependency.get(UserInfoController.class);
- userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
- registerSessionListenerForCurrentUser());
}
/**
@@ -195,32 +181,32 @@ public class PipMediaController {
String pauseDescription = mContext.getString(R.string.pip_pause);
mPauseAction = new RemoteAction(Icon.createWithResource(mContext,
R.drawable.pip_ic_pause_white), pauseDescription, pauseDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
- FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PAUSE),
+ FLAG_UPDATE_CURRENT));
String playDescription = mContext.getString(R.string.pip_play);
mPlayAction = new RemoteAction(Icon.createWithResource(mContext,
R.drawable.pip_ic_play_arrow_white), playDescription, playDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
- FLAG_UPDATE_CURRENT));
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PLAY),
+ FLAG_UPDATE_CURRENT));
String nextDescription = mContext.getString(R.string.pip_skip_to_next);
mNextAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_skip_next_white), nextDescription, nextDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT),
- FLAG_UPDATE_CURRENT));
+ R.drawable.pip_ic_skip_next_white), nextDescription, nextDescription,
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_NEXT),
+ FLAG_UPDATE_CURRENT));
String prevDescription = mContext.getString(R.string.pip_skip_to_prev);
mPrevAction = new RemoteAction(Icon.createWithResource(mContext,
- R.drawable.ic_skip_previous_white), prevDescription, prevDescription,
- PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV),
- FLAG_UPDATE_CURRENT));
+ R.drawable.pip_ic_skip_previous_white), prevDescription, prevDescription,
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_PREV),
+ FLAG_UPDATE_CURRENT));
}
/**
* Re-registers the session listener for the current user.
*/
- private void registerSessionListenerForCurrentUser() {
+ public void registerSessionListenerForCurrentUser() {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
UserHandle.USER_CURRENT, null);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java
index 5b07db6c91a1..cd47d55da7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuActivityController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
@@ -27,14 +27,15 @@ import android.content.pm.ParceledListSlice;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.phone.PipMediaController.ActionListener;
-import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.phone.PipMediaController.ActionListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -86,7 +87,6 @@ public class PipMenuActivityController {
private Context mContext;
private PipTaskOrganizer mPipTaskOrganizer;
private PipMediaController mMediaController;
- private InputConsumerController mInputConsumerController;
private ArrayList<Listener> mListeners = new ArrayList<>();
private ParceledListSlice<RemoteAction> mAppActions;
@@ -94,6 +94,7 @@ public class PipMenuActivityController {
private int mMenuState;
private PipMenuView mPipMenuView;
+ private IBinder mPipMenuInputToken;
private ActionListener mMediaActionListener = new ActionListener() {
@Override
@@ -104,12 +105,9 @@ public class PipMenuActivityController {
};
public PipMenuActivityController(Context context,
- PipMediaController mediaController, InputConsumerController inputConsumerController,
- PipTaskOrganizer pipTaskOrganizer
- ) {
+ PipMediaController mediaController, PipTaskOrganizer pipTaskOrganizer) {
mContext = context;
mMediaController = mediaController;
- mInputConsumerController = inputConsumerController;
mPipTaskOrganizer = pipTaskOrganizer;
}
@@ -119,14 +117,13 @@ public class PipMenuActivityController {
public void onActivityPinned() {
attachPipMenuView();
- mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
}
public void onActivityUnpinned() {
hideMenu();
- mInputConsumerController.unregisterInputConsumer();
mPipTaskOrganizer.detachPipMenuViewHost();
mPipMenuView = null;
+ mPipMenuInputToken = null;
}
public void onPinnedStackAnimationEnded() {
@@ -140,7 +137,13 @@ public class PipMenuActivityController {
mPipMenuView = new PipMenuView(mContext, this);
}
- mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView, getPipMenuLayoutParams(0, 0));
+
+ // If we haven't gotten the input toekn, that means we haven't had a success attempt
+ // yet at attaching the PipMenuView
+ if (mPipMenuInputToken == null) {
+ mPipMenuInputToken = mPipTaskOrganizer.attachPipMenuViewHost(mPipMenuView,
+ getPipMenuLayoutParams(0, 0));
+ }
}
/**
@@ -153,20 +156,6 @@ public class PipMenuActivityController {
}
/**
- * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
- */
- public void setDismissFraction(float fraction) {
- final boolean isMenuVisible = isMenuVisible();
- if (DEBUG) {
- Log.d(TAG, "setDismissFraction() isMenuVisible=" + isMenuVisible
- + " fraction=" + fraction);
- }
- if (isMenuVisible) {
- mPipMenuView.updateDismissFraction(fraction);
- }
- }
-
- /**
* Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
* upon PiP window transition is finished.
*/
@@ -254,7 +243,9 @@ public class PipMenuActivityController {
if (isMenuVisible()) {
// If the menu is visible in either the closed or full state, then hide the menu and
// trigger the animation trigger afterwards
- onStartCallback.run();
+ if (onStartCallback != null) {
+ onStartCallback.run();
+ }
mPipMenuView.hideMenu(onEndCallback);
}
}
@@ -357,6 +348,13 @@ public class PipMenuActivityController {
// the menu actions to be updated again.
mMediaController.removeListener(mMediaActionListener);
}
+
+ try {
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mPipMenuInputToken, menuState != MENU_STATE_NONE /* grantFocus */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update focus as menu appears/disappears", e);
+ }
}
mMenuState = menuState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
index 6cfed070198b..985cd0f1fa19 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuIconsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuIconsAlgorithm.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import android.content.Context;
import android.graphics.Rect;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index c66f442c4c0d..51951409f76c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -23,9 +23,9 @@ import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTR
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -59,8 +59,8 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
import java.util.ArrayList;
import java.util.List;
@@ -154,10 +154,6 @@ public class PipMenuView extends FrameLayout {
expandPip();
}
});
- // TODO (b/161710689): Remove this once focusability for Windowless window is working
- findViewById(R.id.expand_button).setFocusable(false);
- mDismissButton.setFocusable(false);
- mSettingsButton.setFocusable(false);
mResizeHandle = findViewById(R.id.resize_handle);
mResizeHandle.setAlpha(0);
@@ -316,7 +312,7 @@ public class PipMenuView extends FrameLayout {
}
void hideMenu(Runnable animationEndCallback) {
- hideMenu(animationEndCallback, true /* notifyMenuVisibility */, false);
+ hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */);
}
private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
@@ -394,8 +390,10 @@ public class PipMenuView extends FrameLayout {
// TODO: Check if the action drawable has changed before we reload it
action.getIcon().loadDrawableAsync(mContext, d -> {
- d.setTint(Color.WHITE);
- actionView.setImageDrawable(d);
+ if (d != null) {
+ d.setTint(Color.WHITE);
+ actionView.setImageDrawable(d);
+ }
}, mHandler);
actionView.setContentDescription(action.getContentDescription());
if (action.isEnabled()) {
@@ -431,25 +429,6 @@ public class PipMenuView extends FrameLayout {
}
}
- void updateDismissFraction(float fraction) {
- int alpha;
- final float menuAlpha = 1 - fraction;
- if (mMenuState == MENU_STATE_FULL) {
- mMenuContainer.setAlpha(menuAlpha);
- mSettingsButton.setAlpha(menuAlpha);
- mDismissButton.setAlpha(menuAlpha);
- final float interpolatedAlpha =
- MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
- alpha = (int) (interpolatedAlpha * 255);
- } else {
- if (mMenuState == MENU_STATE_CLOSE) {
- mDismissButton.setAlpha(menuAlpha);
- }
- alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
- }
- mBackgroundDrawable.setAlpha(alpha);
- }
-
private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) {
mMenuState = menuState;
mController.onMenuStateChanged(menuState, resize, callback);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index e24121928808..b5fa03082401 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,16 +28,18 @@ import android.os.Looper;
import android.util.Log;
import android.view.Choreographer;
+import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.AnimationHandler;
import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler;
import androidx.dynamicanimation.animation.SpringForce;
-import com.android.systemui.pip.PipSnapAlgorithm;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.FloatProperties;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.FloatProperties;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipTaskOrganizer;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -65,6 +67,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private final Context mContext;
private final PipTaskOrganizer mPipTaskOrganizer;
+ private final @NonNull PipBoundsState mPipBoundsState;
private PipMenuActivityController mMenuController;
private PipSnapAlgorithm mSnapAlgorithm;
@@ -177,11 +180,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public void onPipTransitionCanceled(ComponentName activity, int direction) {}
};
- public PipMotionHelper(Context context, PipTaskOrganizer pipTaskOrganizer,
- PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm,
- FloatingContentCoordinator floatingContentCoordinator) {
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
+ PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
+ mPipBoundsState = pipBoundsState;
mMenuController = menuController;
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
@@ -219,7 +223,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
*/
void synchronizePinnedStackBounds() {
cancelAnimations();
- mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
+ mBounds.set(mPipBoundsState.getBounds());
mTemporaryBounds.setEmpty();
if (mPipTaskOrganizer.isInPip()) {
@@ -384,23 +388,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
* Flings the PiP to the closest snap target.
*/
void flingToSnapTarget(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction) {
- movetoTarget(velocityX, velocityY, updateAction, endAction, false /* isStash */);
+ float velocityX, float velocityY, @Nullable Runnable endAction) {
+ movetoTarget(velocityX, velocityY, endAction, false /* isStash */);
}
/**
* Stash PiP to the closest edge.
*/
void stashToEdge(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction) {
- movetoTarget(velocityX, velocityY, updateAction, endAction, true /* isStash */);
+ float velocityX, float velocityY, @Nullable Runnable endAction) {
+ movetoTarget(velocityX, velocityY, endAction, true /* isStash */);
}
private void movetoTarget(
- float velocityX, float velocityY,
- @Nullable Runnable updateAction, @Nullable Runnable endAction, boolean isStash) {
+ float velocityX, float velocityY, @Nullable Runnable endAction, boolean isStash) {
// If we're flinging to a snap target now, we're not springing to catch up to the touch
// location now.
mSpringingToTouch = false;
@@ -415,11 +416,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
.withEndActions(endAction);
- if (updateAction != null) {
- mTemporaryBoundsPhysicsAnimator.addUpdateListener(
- (target, values) -> updateAction.run());
- }
-
final float offset = ((float) mBounds.width()) * (1.0f - STASH_RATIO);
final float leftEdge = isStash ? mMovementBounds.left - offset : mMovementBounds.left;
final float rightEdge = isStash ? mMovementBounds.right + offset : mMovementBounds.right;
@@ -503,7 +499,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
/**
* Animates the PiP to offset it from the IME or shelf.
*/
- void animateToOffset(Rect originalBounds, int offset) {
+ @VisibleForTesting
+ public void animateToOffset(Rect originalBounds, int offset) {
if (DEBUG) {
Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
+ " callers=\n" + Debug.getCallers(5, " "));
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 08d9b2ae21b0..ef3875597aa2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
@@ -21,13 +21,6 @@ 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 static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import android.content.Context;
import android.content.res.Resources;
@@ -49,13 +42,13 @@ import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewConfiguration;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.policy.TaskResizingAlgorithm;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.util.DeviceConfigProxy;
import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -71,21 +64,11 @@ public class PipResizeGestureHandler {
private static final float PINCH_THRESHOLD = 0.05f;
private static final float STARTING_SCALE_FACTOR = 1.0f;
- private static final int INVALID_SYSUI_STATE_MASK =
- SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
- | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
- | SYSUI_STATE_BOUNCER_SHOWING
- | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
- | SYSUI_STATE_BUBBLES_EXPANDED
- | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-
private final Context mContext;
private final PipBoundsHandler mPipBoundsHandler;
private final PipMotionHelper mMotionHelper;
private final int mDisplayId;
private final Executor mMainExecutor;
- private final SysUiState mSysUiState;
private final ScaleGestureDetector mScaleGestureDetector;
private final Region mTmpRegion = new Region();
@@ -110,6 +93,7 @@ public class PipResizeGestureHandler {
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnablePinchResize;
+ private boolean mIsSysUiStateValid;
private boolean mThresholdCrossed;
private boolean mUsingPinchToZoom = false;
private float mScaleFactor = STARTING_SCALE_FACTOR;
@@ -123,9 +107,8 @@ public class PipResizeGestureHandler {
private int mCtrlType;
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
- PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
- PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
- Runnable updateMovementBoundsRunnable, SysUiState sysUiState,
+ PipMotionHelper motionHelper, PipTaskOrganizer pipTaskOrganizer,
+ Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PipMenuActivityController menuActivityController) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -135,7 +118,6 @@ public class PipResizeGestureHandler {
mPipTaskOrganizer = pipTaskOrganizer;
mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
- mSysUiState = sysUiState;
mPipMenuActivityController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
@@ -202,7 +184,7 @@ public class PipResizeGestureHandler {
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_PINCH_RESIZE,
/* defaultValue = */ false);
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
@@ -218,6 +200,15 @@ public class PipResizeGestureHandler {
reloadResources();
}
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mIsSysUiStateValid = isSysUiStateValid;
+ }
+
private void reloadResources() {
final Resources res = mContext.getResources();
mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
@@ -319,6 +310,10 @@ public class PipResizeGestureHandler {
return mTmpRegion.contains(x, y);
}
+ public boolean isUsingPinchToZoom() {
+ return mEnablePinchResize;
+ }
+
public boolean willStartResizeGesture(MotionEvent ev) {
if (isInValidSysUiState()) {
switch (ev.getActionMasked()) {
@@ -401,7 +396,7 @@ public class PipResizeGestureHandler {
}
private boolean isInValidSysUiState() {
- return (mSysUiState.getFlags() & INVALID_SYSUI_STATE_MASK) == 0;
+ return mIsSysUiStateValid;
}
private void onDragCornerResize(MotionEvent ev) {
@@ -497,11 +492,11 @@ public class PipResizeGestureHandler {
return mUserResizeBounds;
}
- void updateMaxSize(int maxX, int maxY) {
+ @VisibleForTesting public void updateMaxSize(int maxX, int maxY) {
mMaxSize.set(maxX, maxY);
}
- void updateMinSize(int minX, int minY) {
+ @VisibleForTesting public void updateMinSize(int minX, int minY) {
mMinSize.set(minX, minY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
index 72335dbed115..1a3cc8b1c1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchGesture.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchGesture.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
/**
* A generic interface for a touch gesture.
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 858683c4e2d4..a2233e5c5874 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,65 +14,47 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
-import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
+import android.annotation.NonNull;
import android.annotation.SuppressLint;
-import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.drawable.TransitionDrawable;
import android.os.Handler;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.Size;
-import android.view.Gravity;
import android.view.IPinnedStackController;
import android.view.InputEvent;
import android.view.MotionEvent;
-import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.PipAnimationController;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.DismissCircleView;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
-import kotlin.Unit;
-
/**
* Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
* the PIP.
@@ -86,15 +68,13 @@ public class PipTouchHandler {
/* The multiplier to apply scale the target size by when applying the magnetic field radius */
private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
- // Allow dragging the PIP to a location to close it
- private final boolean mEnableDismissDragToEdge;
// Allow PIP to resize to a slightly bigger state upon touch
private final boolean mEnableResize;
private final Context mContext;
- private final WindowManager mWindowManager;
- private final IActivityManager mActivityManager;
private final PipBoundsHandler mPipBoundsHandler;
+ private final @NonNull PipBoundsState mPipBoundsState;
private final PipUiEventLogger mPipUiEventLogger;
+ private final PipDismissTargetHandler mPipDismissTargetHandler;
private PipResizeGestureHandler mPipResizeGestureHandler;
private IPinnedStackController mPinnedStackController;
@@ -110,34 +90,6 @@ public class PipTouchHandler {
*/
private boolean mEnableStash = false;
- /**
- * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
- * PIP.
- */
- private MagnetizedObject<Rect> mMagnetizedPip;
-
- /**
- * Container for the dismiss circle, so that it can be animated within the container via
- * translation rather than within the WindowManager via slow layout animations.
- */
- private ViewGroup mTargetViewContainer;
-
- /** Circle view used to render the dismiss target. */
- private DismissCircleView mTargetView;
-
- /**
- * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
- */
- private MagnetizedObject.MagneticTarget mMagneticTarget;
-
- /** PhysicsAnimator instance for animating the dismiss target in/out. */
- private PhysicsAnimator<View> mMagneticTargetAnimator;
-
- /** Default configuration to use for springing the dismiss target in/out. */
- private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
-
// The current movement bounds
private Rect mMovementBounds = new Rect();
@@ -145,9 +97,9 @@ public class PipTouchHandler {
private Rect mInsetBounds = new Rect();
// The reference bounds used to calculate the normal/expanded target bounds
private Rect mNormalBounds = new Rect();
- @VisibleForTesting Rect mNormalMovementBounds = new Rect();
+ @VisibleForTesting public Rect mNormalMovementBounds = new Rect();
private Rect mExpandedBounds = new Rect();
- @VisibleForTesting Rect mExpandedMovementBounds = new Rect();
+ @VisibleForTesting public Rect mExpandedMovementBounds = new Rect();
private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
@@ -155,12 +107,6 @@ public class PipTouchHandler {
private int mDeferResizeToNormalBoundsUntilRotation = -1;
private int mDisplayRotation;
- /**
- * Runnable that can be posted delayed to show the target. This needs to be saved as a member
- * variable so we can pass it to removeCallbacks.
- */
- private Runnable mShowTargetAction = this::showDismissTargetMaybe;
-
private Handler mHandler = new Handler();
// Behaviour states
@@ -168,7 +114,6 @@ public class PipTouchHandler {
private boolean mIsImeShowing;
private int mImeHeight;
private int mImeOffset;
- private int mDismissAreaHeight;
private boolean mIsShelfShowing;
private int mShelfHeight;
private int mMovementBoundsExtraOffsets;
@@ -216,119 +161,55 @@ public class PipTouchHandler {
}
@SuppressLint("InflateParams")
- public PipTouchHandler(Context context, IActivityManager activityManager,
+ public PipTouchHandler(Context context,
PipMenuActivityController menuController,
- InputConsumerController inputConsumerController,
PipBoundsHandler pipBoundsHandler,
+ @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
FloatingContentCoordinator floatingContentCoordinator,
- DeviceConfigProxy deviceConfig,
- SysUiState sysUiState,
PipUiEventLogger pipUiEventLogger) {
// Initialize the Pip input consumer
mContext = context;
- mActivityManager = activityManager;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsHandler = pipBoundsHandler;
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mPipBoundsState = pipBoundsState;
mMenuController = menuController;
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
- mMotionHelper = new PipMotionHelper(mContext, pipTaskOrganizer, mMenuController,
- mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
+ mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
+ mMenuController, mPipBoundsHandler.getSnapAlgorithm(), floatingContentCoordinator);
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
- deviceConfig, pipTaskOrganizer, this::getMovementBounds,
- this::updateMovementBounds, sysUiState, pipUiEventLogger, menuController);
+ pipTaskOrganizer, this::getMovementBounds,
+ this::updateMovementBounds, pipUiEventLogger, menuController);
+ mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
+ mMotionHelper, mHandler);
mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
() -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mMotionHelper.getBounds(),
true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()),
- menuController::hideMenu);
+ menuController::hideMenu);
Resources res = context.getResources();
- mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
reloadResources();
- // Register the listener for input consumer touch events
- inputConsumerController.setInputListener(this::handleTouchEvent);
- inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
-
mFloatingContentCoordinator = floatingContentCoordinator;
- mConnection = new PipAccessibilityInteractionConnection(mContext, mMotionHelper,
- pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
+ mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
+ mMotionHelper, pipTaskOrganizer, mPipBoundsHandler.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds, mHandler);
mPipUiEventLogger = pipUiEventLogger;
- mTargetView = new DismissCircleView(context);
- mTargetViewContainer = new FrameLayout(context);
- mTargetViewContainer.setBackgroundDrawable(
- context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
- mTargetViewContainer.setClipChildren(false);
- mTargetViewContainer.addView(mTargetView);
-
- mMagnetizedPip = mMotionHelper.getMagnetizedPip();
- mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
- updateMagneticTargetSize();
-
- mMagnetizedPip.setAnimateStuckToTarget(
- (target, velX, velY, flung, after) -> {
- if (mEnableDismissDragToEdge) {
- mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
- }
- return Unit.INSTANCE;
- });
- mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- // Show the dismiss target, in case the initial touch event occurred within the
- // magnetic field radius.
- if (mEnableDismissDragToEdge) {
- showDismissTargetMaybe();
- }
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- if (wasFlungOut) {
- mMotionHelper.flingToSnapTarget(velX, velY, null, null);
- hideDismissTarget();
- } else {
- mMotionHelper.setSpringingToTouch(true);
- }
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mMotionHelper.notifyDismissalPending();
-
- mHandler.post(() -> {
- mMotionHelper.animateDismiss();
- hideDismissTarget();
- });
-
- mPipUiEventLogger.log(
- PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
- }
- });
-
- mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
-
mEnableStash = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_STASHING,
/* defaultValue = */ false);
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
context.getMainExecutor(),
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(PIP_STASHING)) {
- mEnableStash = properties.getBoolean(
- PIP_STASHING, /* defaultValue = */ false);
- }
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASHING)) {
+ mEnableStash = properties.getBoolean(
+ PIP_STASHING, /* defaultValue = */ false);
}
});
}
@@ -339,27 +220,7 @@ public class PipTouchHandler {
mExpandedShortestEdgeSize = res.getDimensionPixelSize(
R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
- mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
- updateMagneticTargetSize();
- }
-
- private void updateMagneticTargetSize() {
- if (mTargetView == null) {
- return;
- }
-
- final Resources res = mContext.getResources();
- final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
- final FrameLayout.LayoutParams newParams =
- new FrameLayout.LayoutParams(targetSize, targetSize);
- newParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- newParams.bottomMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.floating_dismiss_bottom_margin);
- mTargetView.setLayoutParams(newParams);
-
- // Set the magnetic field radius equal to the target size from the center of the target
- mMagneticTarget.setMagneticFieldRadiusPx(
- (int) (targetSize * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ mPipDismissTargetHandler.updateMagneticTargetSize();
}
private boolean shouldShowResizeHandle() {
@@ -384,7 +245,7 @@ public class PipTouchHandler {
}
public void onActivityPinned() {
- createOrUpdateDismissTarget();
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
@@ -394,7 +255,7 @@ public class PipTouchHandler {
public void onActivityUnpinned(ComponentName topPipActivity) {
if (topPipActivity == null) {
// Clean up state after the last PiP activity is removed
- cleanUpDismissTarget();
+ mPipDismissTargetHandler.cleanUpDismissTarget();
mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
}
@@ -425,7 +286,7 @@ public class PipTouchHandler {
reloadResources();
// Recreate the dismiss target for the new orientation.
- createOrUpdateDismissTarget();
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -438,6 +299,15 @@ public class PipTouchHandler {
mShelfHeight = shelfHeight;
}
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
+ }
+
public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
final Rect toMovementBounds = new Rect();
mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(outBounds, insetBounds,
@@ -560,101 +430,16 @@ public class PipTouchHandler {
}
}
- /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
- private void createOrUpdateDismissTarget() {
- if (!mTargetViewContainer.isAttachedToWindow()) {
- mHandler.removeCallbacks(mShowTargetAction);
- mMagneticTargetAnimator.cancel();
-
- mTargetViewContainer.setVisibility(View.INVISIBLE);
-
- try {
- mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
- } catch (IllegalStateException e) {
- // This shouldn't happen, but if the target is already added, just update its layout
- // params.
- mWindowManager.updateViewLayout(
- mTargetViewContainer, getDismissTargetLayoutParams());
- }
- } else {
- mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
- }
- }
-
- /** Returns layout params for the dismiss target, using the latest display metrics. */
- private WindowManager.LayoutParams getDismissTargetLayoutParams() {
- final Point windowSize = new Point();
- mWindowManager.getDefaultDisplay().getRealSize(windowSize);
-
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- mDismissAreaHeight,
- 0, windowSize.y - mDismissAreaHeight,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
-
- lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.setFitInsetsTypes(0 /* types */);
-
- return lp;
- }
-
- /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
- private void showDismissTargetMaybe() {
- createOrUpdateDismissTarget();
-
- if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
-
- mTargetView.setTranslationY(mTargetViewContainer.getHeight());
- mTargetViewContainer.setVisibility(View.VISIBLE);
-
- // Cancel in case we were in the middle of animating it out.
- mMagneticTargetAnimator.cancel();
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).startTransition(
- DISMISS_TRANSITION_DURATION_MS);
- }
- }
-
- /** Animates the magnetic dismiss target out and then sets it to GONE. */
- private void hideDismissTarget() {
- mHandler.removeCallbacks(mShowTargetAction);
- mMagneticTargetAnimator
- .spring(DynamicAnimation.TRANSLATION_Y,
- mTargetViewContainer.getHeight(),
- mTargetSpringConfig)
- .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE))
- .start();
-
- ((TransitionDrawable) mTargetViewContainer.getBackground()).reverseTransition(
- DISMISS_TRANSITION_DURATION_MS);
- }
-
/**
- * Removes the dismiss target and cancels any pending callbacks to show it.
+ * TODO Add appropriate description
*/
- private void cleanUpDismissTarget() {
- mHandler.removeCallbacks(mShowTargetAction);
-
- if (mTargetViewContainer.isAttachedToWindow()) {
- mWindowManager.removeViewImmediate(mTargetViewContainer);
- }
- }
-
- private void onRegistrationChanged(boolean isRegistered) {
+ public void onRegistrationChanged(boolean isRegistered) {
mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
? mConnection : null);
if (!isRegistered && mTouchState.isUserInteracting()) {
// If the input consumer is unregistered while the user is interacting, then we may not
// get the final TOUCH_UP event, so clean up the dismiss target as well
- cleanUpDismissTarget();
+ mPipDismissTargetHandler.cleanUpDismissTarget();
}
}
@@ -664,7 +449,10 @@ public class PipTouchHandler {
shouldShowResizeHandle());
}
- private boolean handleTouchEvent(InputEvent inputEvent) {
+ /**
+ * TODO Add appropriate description
+ */
+ public boolean handleTouchEvent(InputEvent inputEvent) {
// Skip any non motion events
if (!(inputEvent instanceof MotionEvent)) {
return true;
@@ -684,7 +472,7 @@ public class PipTouchHandler {
}
if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
- && mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
+ && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
// If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
// to the touch state. Touch state needs a DOWN event in order to later process MOVE
// events it'll receive if the object is dragged out of the magnetic field.
@@ -794,25 +582,6 @@ public class PipTouchHandler {
}
/**
- * Updates the appearance of the menu and scrim on top of the PiP while dismissing.
- */
- private void updateDismissFraction() {
- if (mMenuController != null) {
- Rect bounds = mMotionHelper.getBounds();
- final float target = mInsetBounds.bottom;
- float fraction = 0f;
- if (bounds.bottom > target) {
- final float distance = bounds.bottom - target;
- fraction = Math.min(distance / bounds.height(), 1f);
- }
- if (Float.compare(fraction, 0f) != 0 || mMenuController.isMenuVisible()) {
- // Update if the fraction > 0, or if fraction == 0 and the menu was already visible
- mMenuController.setDismissFraction(fraction);
- }
- }
- }
-
- /**
* Sets the controller to update the system of changes from user interaction.
*/
void setPinnedStackController(IPinnedStackController controller) {
@@ -832,9 +601,7 @@ public class PipTouchHandler {
// we store back to this snap fraction. Otherwise, we'll reset the snap
// fraction and snap to the closest edge.
if (resize) {
- Rect expandedBounds = new Rect(mExpandedBounds);
- mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
- mMovementBounds, mExpandedMovementBounds, callback);
+ animateToExpandedState(callback);
}
} else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
// Try and restore the PiP to the closest edge, using the saved snap fraction
@@ -860,13 +627,7 @@ public class PipTouchHandler {
}
if (mDeferResizeToNormalBoundsUntilRotation == -1) {
- Rect restoreBounds = new Rect(getUserResizeBounds());
- Rect restoredMovementBounds = new Rect();
- mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
- mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
- mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
- restoredMovementBounds, mMovementBounds, false /* immediate */);
- mSavedSnapFraction = -1f;
+ animateToUnexpandedState(getUserResizeBounds());
}
} else {
mSavedSnapFraction = -1f;
@@ -884,6 +645,21 @@ public class PipTouchHandler {
}
}
+ private void animateToExpandedState(Runnable callback) {
+ Rect expandedBounds = new Rect(mExpandedBounds);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds,
+ mMovementBounds, mExpandedMovementBounds, callback);
+ }
+
+ private void animateToUnexpandedState(Rect restoreBounds) {
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsHandler.getSnapAlgorithm().getMovementBounds(restoreBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+ restoredMovementBounds, mMovementBounds, false /* immediate */);
+ mSavedSnapFraction = -1f;
+ }
+
/**
* @return the motion helper.
*/
@@ -892,17 +668,17 @@ public class PipTouchHandler {
}
@VisibleForTesting
- PipResizeGestureHandler getPipResizeGestureHandler() {
+ public PipResizeGestureHandler getPipResizeGestureHandler() {
return mPipResizeGestureHandler;
}
@VisibleForTesting
- void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
+ public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
mPipResizeGestureHandler = pipResizeGestureHandler;
}
@VisibleForTesting
- void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
+ public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
mMotionHelper = pipMotionHelper;
}
@@ -952,13 +728,7 @@ public class PipTouchHandler {
if (touchState.startedDragging()) {
mSavedSnapFraction = -1f;
-
- if (mEnableDismissDragToEdge) {
- if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
- mHandler.removeCallbacks(mShowTargetAction);
- showDismissTargetMaybe();
- }
- }
+ mPipDismissTargetHandler.showDismissTargetMaybe();
}
if (touchState.isDragging()) {
@@ -989,9 +759,7 @@ public class PipTouchHandler {
@Override
public boolean onUp(PipTouchState touchState) {
- if (mEnableDismissDragToEdge) {
- hideDismissTarget();
- }
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
if (!touchState.isUserInteracting()) {
return false;
@@ -1017,19 +785,30 @@ public class PipTouchHandler {
if (mEnableStash
&& (animatingBounds.right > mPipBoundsHandler.getDisplayBounds().right
|| animatingBounds.left < mPipBoundsHandler.getDisplayBounds().left)) {
- mMotionHelper.stashToEdge(vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */,
- this::flingEndAction /* endAction */);
+ mMotionHelper.stashToEdge(vel.x, vel.y, this::flingEndAction /* endAction */);
} else {
mMotionHelper.flingToSnapTarget(vel.x, vel.y,
- PipTouchHandler.this::updateDismissFraction /* updateAction */,
this::flingEndAction /* endAction */);
}
} else if (mTouchState.isDoubleTap()) {
- // Expand to fullscreen if this is a double tap
- // the PiP should be frozen until the transition ends
- setTouchEnabled(false);
- mMotionHelper.expandLeavePip();
+ // If using pinch to zoom, double-tap functions as resizing between max/min size
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ final boolean toExpand =
+ mMotionHelper.getBounds().width() < mExpandedBounds.width()
+ && mMotionHelper.getBounds().height() < mExpandedBounds.height();
+ mPipResizeGestureHandler.setUserResizeBounds(toExpand ? mExpandedBounds
+ : mNormalBounds);
+ if (toExpand) {
+ animateToExpandedState(null);
+ } else {
+ animateToUnexpandedState(mNormalBounds);
+ }
+ } else {
+ // Expand to fullscreen if this is a double tap
+ // the PiP should be frozen until the transition ends
+ setTouchEnabled(false);
+ mMotionHelper.expandLeavePip();
+ }
} else if (mMenuState != MENU_STATE_FULL) {
if (!mTouchState.isWaitingForDoubleTap()) {
// User has stalled long enough for this not to be a drag or a double tap, just
@@ -1102,7 +881,6 @@ public class PipTouchHandler {
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
- pw.println(innerPrefix + "mEnableDragToEdgeDismiss=" + mEnableDismissDragToEdge);
pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
mPipBoundsHandler.dump(pw, innerPrefix);
mTouchState.dump(pw, innerPrefix);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index ecd1128a5680..217150770084 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import android.graphics.PointF;
import android.os.Handler;
@@ -35,7 +35,7 @@ public class PipTouchState {
private static final boolean DEBUG = false;
@VisibleForTesting
- static final long DOUBLE_TAP_TIMEOUT = 200;
+ public static final long DOUBLE_TAP_TIMEOUT = 200;
static final long HOVER_EXIT_TIMEOUT = 50;
private final Handler mHandler;
@@ -106,8 +106,8 @@ public class PipTouchState {
mAllowDraggingOffscreen = true;
mIsUserInteracting = true;
mDownTouchTime = ev.getEventTime();
- mIsDoubleTap = !mPreviouslyDragging &&
- (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+ mIsDoubleTap = !mPreviouslyDragging
+ && (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
mIsWaitingForDoubleTap = false;
mIsDragging = false;
mLastDownTouchTime = mDownTouchTime;
@@ -163,8 +163,8 @@ public class PipTouchState {
final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
if (DEBUG) {
- Log.e(TAG, "Relinquish active pointer id on POINTER_UP: " +
- mActivePointerId);
+ Log.e(TAG,
+ "Relinquish active pointer id on POINTER_UP: " + mActivePointerId);
}
mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex));
}
@@ -191,8 +191,8 @@ public class PipTouchState {
mUpTouchTime = ev.getEventTime();
mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex));
mPreviouslyDragging = mIsDragging;
- mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging &&
- (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
+ mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging
+ && (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT;
// Fall through to clean up
}
@@ -223,7 +223,7 @@ public class PipTouchState {
/**
* @return the movement delta between the last handled touch event and the previous touch
- * position.
+ * position.
*/
public PointF getLastTouchDelta() {
return mLastDelta;
@@ -238,7 +238,7 @@ public class PipTouchState {
/**
* @return the movement delta between the last handled touch event and the down touch
- * position.
+ * position.
*/
public PointF getDownTouchDelta() {
return mDownDelta;
@@ -318,7 +318,8 @@ public class PipTouchState {
}
}
- @VisibleForTesting long getDoubleTapTimeoutCallbackDelay() {
+ @VisibleForTesting
+ public long getDoubleTapTimeoutCallbackDelay() {
if (mIsWaitingForDoubleTap) {
return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime));
}
@@ -333,7 +334,8 @@ public class PipTouchState {
mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
}
- void scheduleHoverExitTimeoutCallback() {
+ @VisibleForTesting
+ public void scheduleHoverExitTimeoutCallback() {
mHandler.removeCallbacks(mHoverExitTimeoutCallback);
mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
index 6c5d84645e92..d686cac3457b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUpdateThread.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import android.os.Handler;
import android.os.HandlerThread;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUtils.java
index baa8f118f362..6a58ce0d4646 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
@@ -34,7 +35,7 @@ public class PipUtils {
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
- * The component name may be null if no such activity exists.
+ * The component name may be null if no such activity exists.
*/
public static Pair<ComponentName, Integer> getTopPipActivity(Context context,
IActivityManager activityManager) {
@@ -57,4 +58,14 @@ public class PipUtils {
}
return new Pair<>(null, 0);
}
+
+ /**
+ * The util to check if device has PIP feature
+ *
+ * @param context application context
+ * @return true if device has PIP feature, false otherwise.
+ */
+ public static boolean hasSystemFeature(Context context) {
+ return context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java
index db9bedd2e620..4e82bb557fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlButtonView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -83,9 +83,9 @@ public class PipControlButtonView extends RelativeLayout {
mButtonImageView = findViewById(R.id.button);
mDescriptionTextView = findViewById(R.id.desc);
- int[] values = new int[] {android.R.attr.src, android.R.attr.text};
- TypedArray typedArray =
- context.obtainStyledAttributes(attrs, values, defStyleAttr, defStyleRes);
+ int[] values = new int[]{android.R.attr.src, android.R.attr.text};
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
+ defStyleRes);
setImageResource(typedArray.getResourceId(0, 0));
setText(typedArray.getResourceId(1, 0));
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
index 12a545aa4b02..8eac005425bc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
@@ -45,28 +45,19 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.DisplayInfo;
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.wm.shell.R;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
-@SysUISingleton
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
private static final String TAG = "PipController";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -113,7 +104,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private Context mContext;
private PipBoundsHandler mPipBoundsHandler;
private PipTaskOrganizer mPipTaskOrganizer;
- private PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
private IActivityTaskManager mActivityTaskManager;
private MediaSessionManager mMediaSessionManager;
private int mState = STATE_NO_PIP;
@@ -133,6 +123,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private String[] mLastPackagesResourceGranted;
private PipNotification mPipNotification;
private ParceledListSlice<RemoteAction> mCustomActions;
+ private WindowManagerShellWrapper mWindowManagerShellWrapper;
private int mResizeAnimationDuration;
// Used to calculate the movement bounds
@@ -143,21 +134,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private boolean mImeVisible;
private int mImeHeightAdjustment;
- private final PinnedStackListener mPinnedStackListener = new PipControllerPinnedStackListener();
-
- private final Runnable mResizePinnedStackRunnable = new Runnable() {
- @Override
- public void run() {
- resizePinnedStack(mResumeResizePinnedStackRunnableState);
- }
- };
- private final Runnable mClosePipRunnable = new Runnable() {
- @Override
- public void run() {
- closePip();
- }
- };
-
+ private final Runnable mResizePinnedStackRunnable =
+ () -> resizePinnedStack(mResumeResizePinnedStackRunnableState);
+ private final Runnable mClosePipRunnable = () -> closePip();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -175,17 +154,23 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
};
private final MediaSessionManager.OnActiveSessionsChangedListener mActiveMediaSessionListener =
- new MediaSessionManager.OnActiveSessionsChangedListener() {
- @Override
- public void onActiveSessionsChanged(List<MediaController> controllers) {
- updateMediaController(controllers);
- }
- };
+ controllers -> updateMediaController(controllers);
+ private final PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
+ new PipControllerPinnedStackListener();
+
+ @Override
+ public void registerSessionListenerForCurrentUser() {
+ // TODO Need confirm if TV have to re-registers when switch user
+ mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
+ mMediaSessionManager.addOnActiveSessionsChangedListener(mActiveMediaSessionListener, null,
+ UserHandle.USER_CURRENT, null);
+ }
/**
* Handler for messages from the PIP controller.
*/
- private class PipControllerPinnedStackListener extends PinnedStackListener {
+ private class PipControllerPinnedStackListener extends
+ PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mHandler.post(() -> {
@@ -227,50 +212,47 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
- public PipController(Context context, BroadcastDispatcher broadcastDispatcher,
+ public PipController(Context context,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer) {
- if (mInitialized) {
- return;
+ PipTaskOrganizer pipTaskOrganizer,
+ WindowManagerShellWrapper windowManagerShellWrapper
+ ) {
+ if (!mInitialized) {
+ mInitialized = true;
+ mContext = context;
+ mPipNotification = new PipNotification(context, this);
+ mPipBoundsHandler = pipBoundsHandler;
+ // Ensure that we have the display info in case we get calls to update the bounds
+ // before the listener calls back
+ final DisplayInfo displayInfo = new DisplayInfo();
+ context.getDisplay().getDisplayInfo(displayInfo);
+ mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
+
+ mResizeAnimationDuration = context.getResources()
+ .getInteger(R.integer.config_pipResizeAnimationDuration);
+ mPipTaskOrganizer = pipTaskOrganizer;
+ mPipTaskOrganizer.registerPipTransitionCallback(this);
+ mActivityTaskManager = ActivityTaskManager.getService();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
+ mContext.registerReceiver(mBroadcastReceiver, intentFilter, UserHandle.USER_ALL);
+
+ // Initialize the last orientation and apply the current configuration
+ Configuration initialConfig = mContext.getResources().getConfiguration();
+ mLastOrientation = initialConfig.orientation;
+ loadConfigurationsAndApply(initialConfig);
+
+ mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class);
+ mWindowManagerShellWrapper = windowManagerShellWrapper;
+ try {
+ mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
+ }
}
- mInitialized = true;
- mContext = context;
- mPipNotification = new PipNotification(context, broadcastDispatcher,
- Optional.of(this).get());
- mPipBoundsHandler = pipBoundsHandler;
- // Ensure that we have the display info in case we get calls to update the bounds before the
- // listener calls back
- final DisplayInfo displayInfo = new DisplayInfo();
- context.getDisplay().getDisplayInfo(displayInfo);
- mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
-
- mResizeAnimationDuration = context.getResources()
- .getInteger(R.integer.config_pipResizeAnimationDuration);
- mPipSurfaceTransactionHelper = pipSurfaceTransactionHelper;
- mPipTaskOrganizer = pipTaskOrganizer;
- mPipTaskOrganizer.registerPipTransitionCallback(this);
- mActivityTaskManager = ActivityTaskManager.getService();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
- broadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter,
- null /* handler */, UserHandle.ALL);
-
- // Initialize the last orientation and apply the current configuration
- Configuration initialConfig = mContext.getResources().getConfiguration();
- mLastOrientation = initialConfig.orientation;
- loadConfigurationsAndApply(initialConfig);
-
- mMediaSessionManager =
- (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-
- try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
- } catch (RemoteException | UnsupportedOperationException e) {
- Log.e(TAG, "Failed to register pinned stack listener", e);
- }
+ // TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
+ PipMenuActivity.setPipController(this);
}
private void loadConfigurationsAndApply(Configuration newConfig) {
@@ -333,9 +315,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
if (removePipStack) {
try {
- mActivityTaskManager.removeStack(mPinnedStackId);
+ mActivityTaskManager.removeTask(mPinnedStackId);
} catch (RemoteException e) {
- Log.e(TAG, "removeStack failed", e);
+ Log.e(TAG, "removeTask failed", e);
} finally {
mPinnedStackId = INVALID_STACK_ID;
}
@@ -344,7 +326,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mListeners.get(i).onPipActivityClosed();
}
mHandler.removeCallbacks(mClosePipRunnable);
- updatePipVisibility(false);
}
/**
@@ -358,7 +339,77 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mListeners.get(i).onMoveToFullscreen();
}
resizePinnedStack(STATE_NO_PIP);
- updatePipVisibility(false);
+ }
+
+ @Override
+ public void onActivityPinned(String packageName) {
+ if (DEBUG) Log.d(TAG, "onActivityPinned()");
+
+ RootTaskInfo taskInfo = getPinnedTaskInfo();
+ if (taskInfo == null) {
+ Log.w(TAG, "Cannot find pinned stack");
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo);
+ mPinnedStackId = taskInfo.taskId;
+ mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
+ mPipComponentName = ComponentName.unflattenFromString(
+ taskInfo.childTaskNames[taskInfo.childTaskNames.length - 1]);
+ // Set state to STATE_PIP so we show it when the pinned stack animation ends.
+ mState = STATE_PIP;
+ mMediaSessionManager.addOnActiveSessionsChangedListener(
+ mActiveMediaSessionListener, null);
+ updateMediaController(mMediaSessionManager.getActiveSessions(null));
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).onPipEntered(packageName);
+ }
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean clearedTask) {
+ if (task.configuration.windowConfiguration.getWindowingMode()
+ != WINDOWING_MODE_PINNED) {
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
+
+ // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
+ movePipToFullscreen();
+ }
+
+ @Override
+ public void onTaskStackChanged() {
+ if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
+
+ if (getState() != STATE_NO_PIP) {
+ boolean hasPip = false;
+
+ RootTaskInfo taskInfo = getPinnedTaskInfo();
+ if (taskInfo == null || taskInfo.childTaskIds == null) {
+ Log.w(TAG, "There is nothing in pinned stack");
+ closePipInternal(false);
+ return;
+ }
+ for (int i = taskInfo.childTaskIds.length - 1; i >= 0; --i) {
+ if (taskInfo.childTaskIds[i] == mPipTaskId) {
+ // PIP task is still alive.
+ hasPip = true;
+ break;
+ }
+ }
+ if (!hasPip) {
+ // PIP task doesn't exist anymore in PINNED_STACK.
+ closePipInternal(true);
+ return;
+ }
+ }
+ if (getState() == STATE_PIP) {
+ if (mPipBounds != mDefaultPipBounds) {
+ mPipBounds = mDefaultPipBounds;
+ resizePinnedStack(STATE_PIP);
+ }
+ }
}
/**
@@ -603,80 +654,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
return PLAYBACK_STATE_UNAVAILABLE;
}
- private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
- @Override
- public void onTaskStackChanged() {
- if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-
- if (getState() != STATE_NO_PIP) {
- boolean hasPip = false;
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (taskInfo == null || taskInfo.childTaskIds == null) {
- Log.w(TAG, "There is nothing in pinned stack");
- closePipInternal(false);
- return;
- }
- for (int i = taskInfo.childTaskIds.length - 1; i >= 0; --i) {
- if (taskInfo.childTaskIds[i] == mPipTaskId) {
- // PIP task is still alive.
- hasPip = true;
- break;
- }
- }
- if (!hasPip) {
- // PIP task doesn't exist anymore in PINNED_STACK.
- closePipInternal(true);
- return;
- }
- }
- if (getState() == STATE_PIP) {
- if (mPipBounds != mDefaultPipBounds) {
- mPipBounds = mDefaultPipBounds;
- resizePinnedStack(STATE_PIP);
- }
- }
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- if (DEBUG) Log.d(TAG, "onActivityPinned()");
-
- RootTaskInfo taskInfo = getPinnedTaskInfo();
- if (taskInfo == null) {
- Log.w(TAG, "Cannot find pinned stack");
- return;
- }
- if (DEBUG) Log.d(TAG, "PINNED_STACK:" + taskInfo);
- mPinnedStackId = taskInfo.taskId;
- mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
- mPipComponentName = ComponentName.unflattenFromString(
- taskInfo.childTaskNames[taskInfo.childTaskNames.length - 1]);
- // Set state to STATE_PIP so we show it when the pinned stack animation ends.
- mState = STATE_PIP;
- mMediaSessionManager.addOnActiveSessionsChangedListener(
- mActiveMediaSessionListener, null);
- updateMediaController(mMediaSessionManager.getActiveSessions(null));
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onPipEntered(packageName);
- }
- updatePipVisibility(true);
- }
-
- @Override
- public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
- boolean clearedTask, boolean wasVisible) {
- if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_PINNED) {
- return;
- }
- if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-
- // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
- movePipToFullscreen();
- }
- };
-
@Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
}
@@ -730,12 +707,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
void onMediaControllerChanged();
}
- private void updatePipVisibility(final boolean visible) {
- Dependency.get(UiOffloadThread.class).execute(() -> {
- WindowManagerWrapper.getInstance().setPipVisibility(visible);
- });
- }
-
private String getStateDescription() {
if (mSuspendPipResizingReason == 0) {
return stateToName(mState);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java
index 125444d2dfb5..14960c38fd43 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import android.content.Context;
import android.util.AttributeSet;
@@ -30,6 +30,10 @@ import com.android.wm.shell.R;
*/
public class PipControlsView extends LinearLayout {
+ public PipControlsView(Context context) {
+ this(context, null);
+ }
+
public PipControlsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java
index 4ecd52f8adf5..f66e9025a9ed 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipControlsViewController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -26,16 +26,14 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
import com.android.wm.shell.R;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+
/**
* Controller for {@link PipControlsView}.
*/
@@ -47,7 +45,7 @@ public class PipControlsViewController {
private final PipControlsView mView;
private final LayoutInflater mLayoutInflater;
private final Handler mHandler;
- private final Optional<Pip> mPipOptional;
+ private final PipController mPipController;
private final PipControlButtonView mPlayPauseButtonView;
private MediaController mMediaController;
private PipControlButtonView mFocusedChild;
@@ -75,14 +73,12 @@ public class PipControlsViewController {
@Override
public void onViewAttachedToWindow(View v) {
updateMediaController();
- mPipOptional.ifPresent(
- pip -> pip.addMediaListener(mPipMediaListener));
+ mPipController.addMediaListener(mPipMediaListener);
}
@Override
public void onViewDetachedFromWindow(View v) {
- mPipOptional.ifPresent(
- pip -> pip.removeMediaListener(mPipMediaListener));
+ mPipController.removeMediaListener(mPipMediaListener);
}
};
@@ -108,12 +104,11 @@ public class PipControlsViewController {
}
};
-
- public PipControlsViewController(PipControlsView view, Optional<Pip> pipOptional,
- LayoutInflater layoutInflater, @Main Handler handler) {
+ public PipControlsViewController(PipControlsView view, PipController pipController,
+ LayoutInflater layoutInflater, Handler handler) {
super();
mView = view;
- mPipOptional = pipOptional;
+ mPipController = pipController;
mLayoutInflater = layoutInflater;
mHandler = handler;
@@ -124,33 +119,29 @@ public class PipControlsViewController {
View fullButtonView = mView.getFullButtonView();
fullButtonView.setOnFocusChangeListener(mFocusChangeListener);
- fullButtonView.setOnClickListener(
- v -> mPipOptional.ifPresent(pip -> pip.movePipToFullscreen())
- );
+ fullButtonView.setOnClickListener(mView -> mPipController.movePipToFullscreen());
View closeButtonView = mView.getCloseButtonView();
closeButtonView.setOnFocusChangeListener(mFocusChangeListener);
closeButtonView.setOnClickListener(v -> {
- mPipOptional.ifPresent(pip -> pip.closePip());
+ mPipController.closePip();
if (mListener != null) {
mListener.onClosed();
}
});
-
mPlayPauseButtonView = mView.getPlayPauseButtonView();
mPlayPauseButtonView.setOnFocusChangeListener(mFocusChangeListener);
mPlayPauseButtonView.setOnClickListener(v -> {
if (mMediaController == null || mMediaController.getPlaybackState() == null) {
return;
}
- mPipOptional.ifPresent(pip -> {
- if (pip.getPlaybackState() == PipController.PLAYBACK_STATE_PAUSED) {
- mMediaController.getTransportControls().play();
- } else if (pip.getPlaybackState() == PipController.PLAYBACK_STATE_PLAYING) {
- mMediaController.getTransportControls().pause();
- }
- });
+ final int playbackState = mPipController.getPlaybackState();
+ if (playbackState == PipController.PLAYBACK_STATE_PAUSED) {
+ mMediaController.getTransportControls().play();
+ } else if (playbackState == PipController.PLAYBACK_STATE_PLAYING) {
+ mMediaController.getTransportControls().pause();
+ }
// View will be updated later in {@link mMediaControllerCallback}
});
@@ -158,7 +149,7 @@ public class PipControlsViewController {
private void updateMediaController() {
AtomicReference<MediaController> newController = new AtomicReference<>();
- mPipOptional.ifPresent(pip -> newController.set(pip.getMediaController()));
+ newController.set(mPipController.getMediaController());
if (newController.get() == null || mMediaController == newController.get()) {
return;
@@ -221,7 +212,7 @@ public class PipControlsViewController {
mPlayPauseButtonView.setVisibility(View.GONE);
} else {
AtomicInteger state = new AtomicInteger(PipController.STATE_UNKNOWN);
- mPipOptional.ifPresent(pip -> state.set(pip.getPlaybackState()));
+ state.set(mPipController.getPlaybackState());
if (state.get() == PipController.STATE_UNKNOWN
|| state.get() == PipController.PLAYBACK_STATE_UNAVAILABLE) {
mPlayPauseButtonView.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java
index 7e812d9ca8a1..06d2408c95f4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipMenuActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -25,59 +25,40 @@ import android.content.pm.ParceledListSlice;
import android.os.Bundle;
import android.util.Log;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.tv.dagger.TvPipComponent;
import com.android.wm.shell.R;
import java.util.Collections;
-import java.util.Optional;
-
-import javax.inject.Inject;
/**
* Activity to show the PIP menu to control PIP.
+ * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
*/
-
public class PipMenuActivity extends Activity implements PipController.Listener {
private static final String TAG = "PipMenuActivity";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
- private final TvPipComponent.Builder mPipComponentBuilder;
- private TvPipComponent mTvPipComponent;
- private final Optional<Pip> mPipOptional;
+ private static PipController sPipController;
private Animator mFadeInAnimation;
private Animator mFadeOutAnimation;
private boolean mRestorePipSizeWhenClose;
private PipControlsViewController mPipControlsViewController;
- @Inject
- public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder,
- Optional<Pip> pipOptional) {
- super();
- mPipComponentBuilder = pipComponentBuilder;
- mPipOptional = pipOptional;
- }
-
@Override
protected void onCreate(Bundle bundle) {
if (DEBUG) Log.d(TAG, "onCreate()");
super.onCreate(bundle);
- mPipOptional.ifPresent(pip -> {
- if (!pip.isPipShown()) {
- finish();
- }
- });
+ if (sPipController == null || sPipController.isPipShown()) {
+ finish();
+ }
setContentView(R.layout.tv_pip_menu);
- mTvPipComponent = mPipComponentBuilder.pipControlsView(
- findViewById(R.id.pip_controls)).build();
- mPipControlsViewController = mTvPipComponent.getPipControlsViewController();
-
- mPipOptional.ifPresent(pip -> pip.addListener(this));
-
+ mPipControlsViewController = new PipControlsViewController(
+ findViewById(R.id.pip_controls), sPipController,
+ getLayoutInflater(), getApplicationContext().getMainThreadHandler());
+ sPipController.addListener(this);
mRestorePipSizeWhenClose = true;
mFadeInAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_in_animation);
@@ -104,7 +85,7 @@ public class PipMenuActivity extends Activity implements PipController.Listener
if (DEBUG) Log.d(TAG, " > restoring to the default position");
// When PIP menu activity is closed, restore to the default position.
- mPipOptional.ifPresent(pip -> pip.resizePinnedStack(PipController.STATE_PIP));
+ sPipController.resizePinnedStack(PipController.STATE_PIP);
}
finish();
}
@@ -131,9 +112,9 @@ public class PipMenuActivity extends Activity implements PipController.Listener
if (DEBUG) Log.d(TAG, "onDestroy()");
super.onDestroy();
- mPipOptional.ifPresent(pip -> pip.removeListener(this));
- mPipOptional.ifPresent(pip -> pip.resumePipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH));
+ sPipController.removeListener(this);
+ sPipController.resumePipResizing(
+ PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
@Override
@@ -184,8 +165,8 @@ public class PipMenuActivity extends Activity implements PipController.Listener
if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
finish();
- mPipOptional.ifPresent(pip -> pip.suspendPipResizing(
- PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH));
+ sPipController.suspendPipResizing(
+ PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
}
@Override
@@ -194,4 +175,16 @@ public class PipMenuActivity extends Activity implements PipController.Listener
super.finish();
}
+
+ /**
+ * TODO(b/169395392) Refactor PipMenuActivity to PipMenuView
+ *
+ * @param pipController The singleton pipController instance for TV
+ */
+ public static void setPipController(PipController pipController) {
+ if (sPipController != null) {
+ return;
+ }
+ sPipController = pipController;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
index 78569edf009d..7433085e6fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipNotification.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.tv;
+package com.android.wm.shell.pip.tv;
import android.app.Notification;
import android.app.NotificationManager;
@@ -32,12 +32,11 @@ import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.PlaybackState;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.util.NotificationChannels;
import com.android.wm.shell.R;
/**
@@ -53,6 +52,8 @@ public class PipNotification {
private static final String ACTION_MENU = "PipNotification.menu";
private static final String ACTION_CLOSE = "PipNotification.close";
+ public static final String NOTIFICATION_CHANNEL_TVPIP = "TPP";
+
private final PackageManager mPackageManager;
private final PipController mPipController;
@@ -163,14 +164,13 @@ public class PipNotification {
}
};
- public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher,
- PipController pipController) {
+ public PipNotification(Context context, PipController pipController) {
mPackageManager = context.getPackageManager();
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
- mNotificationBuilder = new Notification.Builder(context, NotificationChannels.TVPIP)
+ mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL_TVPIP)
.setLocalOnly(true)
.setOngoing(false)
.setCategory(Notification.CATEGORY_SYSTEM)
@@ -185,7 +185,7 @@ public class PipNotification {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_MENU);
intentFilter.addAction(ACTION_CLOSE);
- broadcastDispatcher.registerReceiver(mEventReceiver, intentFilter);
+ context.registerReceiver(mEventReceiver, intentFilter, UserHandle.USER_ALL);
onConfigurationChanged(context);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index e3029e55a214..f3dadfcb933a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,7 +26,9 @@ import com.android.internal.protolog.common.IProtoLogGroup;
public enum ShellProtoLogGroup implements IProtoLogGroup {
// NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
// with those in the framework ProtoLogGroup
- WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
+ WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
index 00146e9447bd..2b14e8bf88d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/DividerView.java
@@ -845,15 +845,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
void enterSplitMode(boolean isHomeStackResizable) {
- post(() -> {
- final SurfaceControl sc = getWindowSurfaceControl();
- if (sc == null) {
- return;
- }
- Transaction t = mTiles.getTransaction();
- t.show(sc).apply();
- mTiles.releaseTransaction(t);
- });
+ setHidden(false);
SnapTarget miniMid =
mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
@@ -880,14 +872,19 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
void exitSplitMode() {
- // Reset tile bounds
+ // The view is going to be removed right after this function involved, updates the surface
+ // in the current thread instead of posting it to the view's UI thread.
final SurfaceControl sc = getWindowSurfaceControl();
if (sc == null) {
return;
}
Transaction t = mTiles.getTransaction();
- t.hide(sc).apply();
+ t.hide(sc);
+ mImeController.setDimsHidden(t, true);
+ t.apply();
mTiles.releaseTransaction(t);
+
+ // Reset tile bounds
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
}
@@ -1319,38 +1316,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mBackground.getRight(), mBackground.getBottom(), Op.UNION);
}
- void onDockedFirstAnimationFrame() {
- saveSnapTargetBeforeMinimized(mSplitLayout.getSnapAlgorithm().getMiddleTarget());
- }
-
- void onDockedTopTask() {
- mState.animateAfterRecentsDrawn = true;
- startDragging(false /* animate */, false /* touching */);
- updateDockSide();
- mEntranceAnimationRunning = true;
-
- resizeStackSurfaces(calculatePositionForInsetBounds(),
- mSplitLayout.getSnapAlgorithm().getMiddleTarget().position,
- mSplitLayout.getSnapAlgorithm().getMiddleTarget(),
- null /* transaction */);
- }
-
- void onRecentsDrawn() {
- updateDockSide();
- final int position = calculatePositionForInsetBounds();
- if (mState.animateAfterRecentsDrawn) {
- mState.animateAfterRecentsDrawn = false;
-
- mHandler.post(() -> {
- // Delay switching resizing mode because this might cause jank in recents animation
- // that's longer than this animation.
- stopDragging(position, getSnapAlgorithm().getMiddleTarget(),
- mLongPressEntraceAnimDuration, Interpolators.FAST_OUT_SLOW_IN,
- 200 /* endDelay */);
- });
- }
- }
-
void onUndockingTask() {
int dockSide = mSplitLayout.getPrimarySplitSide();
if (inSplitMode()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 184342f14d4f..985dff20ad32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -48,14 +48,6 @@ public interface SplitScreen {
/** Switch to minimized state if appropriate. */
void setMinimized(boolean minimized);
- /**
- * Workaround for b/62528361, at the time recents has drawn, it may happen before a
- * configuration change to the Divider, and internally, the event will be posted to the
- * subscriber, or DividerView, which has been removed and prevented from resizing. Instead,
- * register the event handler here and proxy the event to the current DividerView.
- */
- void onRecentsDrawn();
-
/** Called when there's an activity forced resizable. */
void onActivityForcedResizable(String packageName, int taskId, int reason);
@@ -68,12 +60,6 @@ public interface SplitScreen {
/** Called when there's a task undocking. */
void onUndockingTask();
- /** Called when the first docked animation frame rendered. */
- void onDockedFirstAnimationFrame();
-
- /** Called when top task docked. */
- void onDockedTopTask();
-
/** Called when app transition finished. */
void onAppTransitionFinished();
@@ -88,4 +74,13 @@ public interface SplitScreen {
/** @return the container token for the secondary split root task. */
WindowContainerToken getSecondaryRoot();
+
+ /**
+ * Splits the primary task if feasible, this is to preserve legacy way to toggle split screen.
+ * Like triggering split screen through long pressing recents app button or through
+ * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}.
+ *
+ * @return {@code true} if it successes to split the primary task.
+ */
+ boolean splitPrimaryTask();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index eed5092ea96b..43e4d62baaf6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -16,19 +16,25 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
+import android.os.RemoteException;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.Toast;
import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -40,12 +46,14 @@ import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -56,7 +64,7 @@ public class SplitScreenController implements SplitScreen,
DisplayController.OnDisplaysChangedListener {
static final boolean DEBUG = false;
- private static final String TAG = "Divider";
+ private static final String TAG = "SplitScreenCtrl";
private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
private final Context mContext;
@@ -99,7 +107,7 @@ public class SplitScreenController implements SplitScreen,
public SplitScreenController(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, Handler handler, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer) {
+ ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue) {
mContext = context;
mDisplayController = displayController;
mSystemWindows = systemWindows;
@@ -107,8 +115,7 @@ public class SplitScreenController implements SplitScreen,
mHandler = handler;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
mTransactionPool = transactionPool;
- mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler,
- shellTaskOrganizer);
+ mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
mTaskOrganizer = shellTaskOrganizer;
mSplits = new SplitScreenTaskOrganizer(this, shellTaskOrganizer);
mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler,
@@ -157,12 +164,12 @@ public class SplitScreenController implements SplitScreen,
// Don't initialize the divider or anything until we get the default display.
}
- /** Returns {@code true} if split screen is supported on the device. */
+ @Override
public boolean isSplitScreenSupported() {
return mSplits.isSplitScreenSupported();
}
- /** Called when keyguard showing state changed. */
+ @Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (!isSplitActive() || mView == null) {
return;
@@ -229,21 +236,22 @@ public class SplitScreenController implements SplitScreen,
mHandler.post(task);
}
- /** Returns {@link DividerView}. */
+ @Override
public DividerView getDividerView() {
return mView;
}
- /** Returns {@code true} if one of the split screen is in minimized mode. */
+ @Override
public boolean isMinimized() {
return mMinimized;
}
+ @Override
public boolean isHomeStackResizable() {
return mHomeStackResizable;
}
- /** Returns {@code true} if the divider is visible. */
+ @Override
public boolean isDividerVisible() {
return mView != null && mView.getVisibility() == View.VISIBLE;
}
@@ -328,7 +336,7 @@ public class SplitScreenController implements SplitScreen,
}
}
- /** Switch to minimized state if appropriate. */
+ @Override
public void setMinimized(final boolean minimized) {
if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
mHandler.post(() -> {
@@ -392,55 +400,29 @@ public class SplitScreenController implements SplitScreen,
mWindowManager.setTouchable(!mAdjustedForIme);
}
- /**
- * Workaround for b/62528361, at the time recents has drawn, it may happen before a
- * configuration change to the Divider, and internally, the event will be posted to the
- * subscriber, or DividerView, which has been removed and prevented from resizing. Instead,
- * register the event handler here and proxy the event to the current DividerView.
- */
- public void onRecentsDrawn() {
- if (mView != null) {
- mView.onRecentsDrawn();
- }
- }
-
- /** Called when there's an activity forced resizable. */
+ @Override
public void onActivityForcedResizable(String packageName, int taskId, int reason) {
mForcedResizableController.activityForcedResizable(packageName, taskId, reason);
}
- /** Called when there's an activity dismissing split screen. */
+ @Override
public void onActivityDismissingSplitScreen() {
mForcedResizableController.activityDismissingSplitScreen();
}
- /** Called when there's an activity launch on secondary display failed. */
+ @Override
public void onActivityLaunchOnSecondaryDisplayFailed() {
mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
}
- /** Called when there's a task undocking. */
+ @Override
public void onUndockingTask() {
if (mView != null) {
mView.onUndockingTask();
}
}
- /** Called when the first docked animation frame rendered. */
- public void onDockedFirstAnimationFrame() {
- if (mView != null) {
- mView.onDockedFirstAnimationFrame();
- }
- }
-
- /** Called when top task docked. */
- public void onDockedTopTask() {
- if (mView != null) {
- mView.onDockedTopTask();
- }
- }
-
- /** Called when app transition finished. */
+ @Override
public void onAppTransitionFinished() {
if (mView == null) {
return;
@@ -448,7 +430,7 @@ public class SplitScreenController implements SplitScreen,
mForcedResizableController.onAppTransitionFinished();
}
- /** Dumps current status of Split Screen. */
+ @Override
public void dump(PrintWriter pw) {
pw.print(" mVisible="); pw.println(mVisible);
pw.print(" mMinimized="); pw.println(mMinimized);
@@ -465,7 +447,7 @@ public class SplitScreenController implements SplitScreen,
return (long) (transitionDuration * transitionScale);
}
- /** Registers listener that gets called whenever the existence of the divider changes. */
+ @Override
public void registerInSplitScreenListener(Consumer<Boolean> listener) {
listener.accept(isDividerVisible());
synchronized (mDockedStackExistsListeners) {
@@ -480,6 +462,42 @@ public class SplitScreenController implements SplitScreen,
}
}
+ @Override
+ public boolean splitPrimaryTask() {
+ try {
+ if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED
+ || isSplitActive()) {
+ return false;
+ }
+
+ // Try fetching the top running task.
+ final List<RunningTaskInfo> runningTasks =
+ ActivityTaskManager.getService().getTasks(1 /* maxNum */);
+ if (runningTasks == null || runningTasks.isEmpty()) {
+ return false;
+ }
+ // Note: The set of running tasks from the system is ordered by recency.
+ final RunningTaskInfo topRunningTask = runningTasks.get(0);
+
+ final int activityType = topRunningTask.configuration.windowConfiguration
+ .getActivityType();
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ return false;
+ }
+
+ if (!topRunningTask.supportsSplitScreenMultiWindow) {
+ Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(
+ topRunningTask.taskId, true /* onTop */);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
/** Notifies the bounds of split screen changed. */
void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
synchronized (mBoundsChangedListeners) {
@@ -502,6 +520,9 @@ public class SplitScreenController implements SplitScreen,
mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, true /* dismissOrMaximize */);
updateVisibility(false /* visible */);
mMinimized = false;
+ // Resets divider bar position to undefined, so new divider bar will apply default position
+ // next time entering split mode.
+ mDividerState.mRatioPositionBeforeMinimized = 0;
removeDivider();
mImePositionProcessor.reset();
}
@@ -536,7 +557,7 @@ public class SplitScreenController implements SplitScreen,
return mWindowManagerProxy;
}
- /** @return the container token for the secondary split root task. */
+ @Override
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
return null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
index 30bc43b0292f..8660702a2509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskOrganizer.java
@@ -23,8 +23,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_SPLIT_SCREEN;
+
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
@@ -56,17 +57,16 @@ class SplitScreenTaskOrganizer implements ShellTaskOrganizer.TaskListener {
ShellTaskOrganizer shellTaskOrganizer) {
mSplitScreenController = splitScreenController;
mTaskOrganizer = shellTaskOrganizer;
- mTaskOrganizer.addListener(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_SPLIT_SCREEN);
}
void init() throws RemoteException {
synchronized (this) {
try {
mPrimary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mSecondary = mTaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
} catch (Exception e) {
// teardown to prevent callbacks
mTaskOrganizer.removeListener(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
index 25827cdb9e24..47e7c99d2268 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java
@@ -28,7 +28,6 @@ import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.Display;
@@ -41,7 +40,6 @@ import android.window.WindowOrganizer;
import com.android.internal.annotations.GuardedBy;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
import java.util.ArrayList;
import java.util.List;
@@ -85,9 +83,8 @@ class WindowManagerProxy {
private final TaskOrganizer mTaskOrganizer;
- WindowManagerProxy(TransactionPool transactionPool, Handler handler,
- TaskOrganizer taskOrganizer) {
- mSyncTransactionQueue = new SyncTransactionQueue(transactionPool, handler);
+ WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) {
+ mSyncTransactionQueue = syncQueue;
mTaskOrganizer = taskOrganizer;
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
new file mode 100644
index 000000000000..0cedc0a7147f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.wm.shell.flicker.helpers
+
+import android.app.Instrumentation
+import android.support.test.launcherhelper.ILauncherStrategy
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
+import com.android.server.wm.flicker.helpers.waitForIME
+import org.junit.Assert
+
+open class ImeAppHelper(
+ instr: Instrumentation,
+ launcherName: String = "ImeApp",
+ launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+ .getInstance(instr)
+ .launcherStrategy
+) : FlickerAppHelper(instr, launcherName, launcherStrategy) {
+ open fun openIME(device: UiDevice) {
+ val editText = device.wait(
+ Until.findObject(By.res(getPackage(), "plain_text_input")),
+ FIND_TIMEOUT)
+ Assert.assertNotNull("Text field not found, this usually happens when the device " +
+ "was left in an unknown state (e.g. in split screen)", editText)
+ editText.click()
+ if (!device.waitForIME()) {
+ Assert.fail("IME did not appear")
+ }
+ }
+
+ open fun closeIME(device: UiDevice) {
+ device.pressBack()
+ // Using only the AccessibilityInfo it is not possible to identify if the IME is active
+ device.waitForIdle(1000)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index c3c576d3f28c..010aa0d7d832 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -39,7 +39,7 @@ import org.junit.runners.Parameterized
/**
* Test Pip launch.
- * To run this test: `atest FlickerTests:PipToAppTest`
+ * To run this test: `atest WMShellFlickerTests:PipToAppTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
new file mode 100644
index 000000000000..43e022538685
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -0,0 +1,217 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.content.ComponentName
+import android.graphics.Region
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.util.Log
+import android.view.Surface
+import android.view.WindowManager
+import androidx.test.filters.RequiresDevice
+import com.android.compatibility.common.util.SystemUtil
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.closePipWindow
+import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.helpers.ImeAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import java.io.IOException
+
+/**
+ * Test Pip launch.
+ * To run this test: `atest WMShellFlickerTests:PipKeyboardTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipKeyboardTest(
+ rotationName: String,
+ rotation: Int
+) : PipTestBase(rotationName, rotation) {
+ private val windowManager: WindowManager =
+ instrumentation.context.getSystemService(WindowManager::class.java)
+
+ private val keyboardApp = ImeAppHelper(instrumentation, "ImeApp",
+ LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy)
+
+ private val KEYBOARD_ACTIVITY: ComponentName = ComponentName.createRelative(
+ "com.android.wm.shell.flicker.testapp", ".ImeActivity")
+ private val PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
+ private val INPUT_METHOD_WINDOW_NAME = "InputMethod"
+
+ private val testRepetitions = 10
+
+ private val keyboardScenario: FlickerBuilder
+ get() = FlickerBuilder(instrumentation).apply {
+ repeat { testRepetitions }
+ // disable layer tracing
+ withLayerTracing { null }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.pressHome()
+ // launch our target pip app
+ testApp.open()
+ this.setRotation(rotation)
+ testApp.clickEnterPipButton(device)
+ // open an app with an input field and a keyboard
+ // UiAutomator doesn't support to launch the multiple Activities in a task.
+ // So use launchActivity() for the Keyboard Activity.
+ launchActivity(KEYBOARD_ACTIVITY)
+ }
+ }
+ teardown {
+ test {
+ keyboardApp.exit()
+
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ }
+
+ /** Ensure the pip window remains visible throughout any keyboard interactions. */
+ @Test
+ fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() {
+ val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose"
+ runWithFlicker(keyboardScenario) {
+ withTestName { testTag }
+ transitions {
+ // open the soft keyboard
+ keyboardApp.openIME(device)
+
+ // then close it again
+ keyboardApp.closeIME(device)
+ }
+ assertions {
+ windowManagerTrace {
+ all("PiP window must remain inside visible bounds") {
+ coversAtMostRegion(
+ partialWindowTitle = "PipActivity",
+ region = Region(windowManager.maximumWindowMetrics.bounds)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ /** Ensure the pip window does not obscure the keyboard. */
+ @Test
+ fun pipWindow_doesNotObscure_keyboard() {
+ val testTag = "pipWindow_doesNotObscure_keyboard"
+ runWithFlicker(keyboardScenario) {
+ withTestName { testTag }
+ transitions {
+ // open the soft keyboard
+ keyboardApp.openIME(device)
+ }
+ teardown {
+ eachRun {
+ // close the keyboard
+ keyboardApp.closeIME(device)
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ end {
+ isAboveWindow(INPUT_METHOD_WINDOW_NAME, PIP_ACTIVITY_WINDOW_NAME)
+ }
+ }
+ }
+ }
+ }
+
+ private fun launchActivity(
+ activity: ComponentName? = null,
+ action: String? = null,
+ flags: Set<Int> = setOf(),
+ boolExtras: Map<String, Boolean> = mapOf(),
+ intExtras: Map<String, Int> = mapOf(),
+ stringExtras: Map<String, String> = mapOf()
+ ) {
+ require(activity != null || !action.isNullOrBlank()) {
+ "Cannot launch an activity with neither activity name nor action!"
+ }
+ val command = composeCommand(
+ "start", activity, action, flags, boolExtras, intExtras, stringExtras)
+ executeShellCommand(command)
+ }
+
+ private fun composeCommand(
+ command: String,
+ activity: ComponentName?,
+ action: String?,
+ flags: Set<Int>,
+ boolExtras: Map<String, Boolean>,
+ intExtras: Map<String, Int>,
+ stringExtras: Map<String, String>
+ ): String = buildString {
+ append("am ")
+ append(command)
+ activity?.let {
+ append(" -n ")
+ append(it.flattenToShortString())
+ }
+ action?.let {
+ append(" -a ")
+ append(it)
+ }
+ flags.forEach {
+ append(" -f ")
+ append(it)
+ }
+ boolExtras.forEach {
+ append(it.withFlag("ez"))
+ }
+ intExtras.forEach {
+ append(it.withFlag("ei"))
+ }
+ stringExtras.forEach {
+ append(it.withFlag("es"))
+ }
+ }
+
+ private fun Map.Entry<String, *>.withFlag(flag: String): String = " --$flag $key $value"
+
+ private fun executeShellCommand(cmd: String): String {
+ try {
+ return SystemUtil.runShellCommand(instrumentation, cmd)
+ } catch (e: IOException) {
+ Log.e("FlickerTests", "Error running shell command: $cmd")
+ throw e
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index 95dc1d48eee8..3b66c58414e0 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -33,5 +33,14 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".ImeActivity"
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
+ android:label="ImeApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
new file mode 100644
index 000000000000..4708cfd48381
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusableInTouchMode="true"
+ android:background="@android:color/holo_green_light">
+ <EditText android:id="@+id/plain_text_input"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:inputType="text"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
new file mode 100644
index 000000000000..856728715c1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class ImeActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams p = getWindow().getAttributes();
+ p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
+ .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(p);
+ setContentView(R.layout.activity_ime);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 937b00b3a0fd..9940ea575873 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -23,20 +23,30 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "androidx.dynamicanimation_dynamicanimation",
+ "dagger2",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"testables",
],
+
libs: [
"android.test.mock",
"android.test.base",
"android.test.runner",
],
+
jni_libs: [
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
+ kotlincflags: ["-Xjvm-default=enable"],
+
+ plugins: ["dagger2-compiler"],
+
optimize: {
enabled: false,
},
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7b499d4d6e7d..5418a5b21680 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -19,20 +19,32 @@ package com.android.wm.shell;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import org.junit.Before;
@@ -54,7 +66,9 @@ public class ShellTaskOrganizerTests {
private ITaskOrganizerController mTaskOrganizerController;
ShellTaskOrganizer mOrganizer;
+ private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+ private final ShellExecutor mTestExecutor = mock(ShellExecutor.class);
private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener {
final ArrayList<RunningTaskInfo> appeared = new ArrayList<>();
@@ -85,7 +99,12 @@ public class ShellTaskOrganizerTests {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mOrganizer = new ShellTaskOrganizer(mTaskOrganizerController, mTransactionPool);
+ try {
+ doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
+ .when(mTaskOrganizerController).registerTaskOrganizer(any());
+ } catch (RemoteException e) {}
+ mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
+ mTransactionPool, mTestExecutor, mTestExecutor));
}
@Test
@@ -96,10 +115,42 @@ public class ShellTaskOrganizerTests {
}
@Test
+ public void testOneListenerPerType() {
+ mOrganizer.addListener(new TrackingTaskListener(), TASK_LISTENER_TYPE_MULTI_WINDOW);
+ try {
+ mOrganizer.addListener(new TrackingTaskListener(), TASK_LISTENER_TYPE_MULTI_WINDOW);
+ fail("Expected exception due to already registered listener");
+ } catch (Exception e) {
+ // Expected failure
+ }
+ }
+
+ @Test
+ public void testRegisterWithExistingTasks() throws RemoteException {
+ // Setup some tasks
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+ taskInfos.add(new TaskAppearedInfo(task1, new SurfaceControl()));
+ taskInfos.add(new TaskAppearedInfo(task2, new SurfaceControl()));
+ doReturn(new ParceledListSlice(taskInfos))
+ .when(mTaskOrganizerController).registerTaskOrganizer(any());
+
+ // Register and expect the tasks to be stored
+ mOrganizer.registerOrganizer();
+
+ // Check that the tasks are next reported when the listener is added
+ TrackingTaskListener listener = new TrackingTaskListener();
+ mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
+ assertTrue(listener.appeared.contains(task1));
+ assertTrue(listener.appeared.contains(task2));
+ }
+
+ @Test
public void testAppearedVanished() {
- RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener listener = new TrackingTaskListener();
- mOrganizer.addListener(listener, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
mOrganizer.onTaskAppeared(taskInfo, null);
assertTrue(listener.appeared.contains(taskInfo));
@@ -109,33 +160,34 @@ public class ShellTaskOrganizerTests {
@Test
public void testAddListenerExistingTasks() {
- RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
mOrganizer.onTaskAppeared(taskInfo, null);
TrackingTaskListener listener = new TrackingTaskListener();
- mOrganizer.addListener(listener, WINDOWING_MODE_MULTI_WINDOW);
+ mOrganizer.addListener(listener, TASK_LISTENER_TYPE_MULTI_WINDOW);
assertTrue(listener.appeared.contains(taskInfo));
}
@Test
public void testWindowingModeChange() {
- RunningTaskInfo taskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW);
+ RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
TrackingTaskListener mwListener = new TrackingTaskListener();
TrackingTaskListener pipListener = new TrackingTaskListener();
- mOrganizer.addListener(mwListener, WINDOWING_MODE_MULTI_WINDOW);
- mOrganizer.addListener(pipListener, WINDOWING_MODE_PINNED);
+ mOrganizer.addListener(mwListener, TASK_LISTENER_TYPE_MULTI_WINDOW);
+ mOrganizer.addListener(pipListener, TASK_LISTENER_TYPE_PIP);
mOrganizer.onTaskAppeared(taskInfo, null);
assertTrue(mwListener.appeared.contains(taskInfo));
assertTrue(pipListener.appeared.isEmpty());
- taskInfo = createTaskInfo(WINDOWING_MODE_PINNED);
+ taskInfo = createTaskInfo(1, WINDOWING_MODE_PINNED);
mOrganizer.onTaskInfoChanged(taskInfo);
assertTrue(mwListener.vanished.contains(taskInfo));
assertTrue(pipListener.appeared.contains(taskInfo));
}
- private RunningTaskInfo createTaskInfo(int windowingMode) {
+ private RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
index bd596800e86d..4bd9bed26a82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.util.animation
+/*
+ * 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.wm.shell.animation
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -11,11 +27,11 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.animation.PhysicsAnimator.EndListener
-import com.android.systemui.util.animation.PhysicsAnimator.UpdateListener
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
+import com.android.wm.shell.animation.PhysicsAnimator.EndListener
+import com.android.wm.shell.animation.PhysicsAnimator.UpdateListener
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.getAnimationUpdateFrames
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.verifyAnimationUpdateFrames
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
new file mode 100644
index 000000000000..080cddc58a09
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_IME;
+import static android.view.Surface.ROTATION_0;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.graphics.Point;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.view.IInputMethodManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class DisplayImeControllerTest {
+
+ private SurfaceControl.Transaction mT;
+ private DisplayImeController.PerDisplay mPerDisplay;
+ private IInputMethodManager mMock;
+
+ @Before
+ public void setUp() throws Exception {
+ mT = mock(SurfaceControl.Transaction.class);
+ mMock = mock(IInputMethodManager.class);
+ mPerDisplay = new DisplayImeController(null, null, Runnable::run, new TransactionPool() {
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return mT;
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+ }) {
+ @Override
+ public IInputMethodManager getImms() {
+ return mMock;
+ }
+ }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
+ }
+
+ @Test
+ public void reappliesVisibilityToChangedLeash() {
+ verifyZeroInteractions(mT);
+
+ mPerDisplay.mImeShowing = false;
+ mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
+ new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ });
+
+ verify(mT).hide(any());
+
+ mPerDisplay.mImeShowing = true;
+ mPerDisplay.insetsControlChanged(new InsetsState(), new InsetsSourceControl[] {
+ new InsetsSourceControl(ITYPE_IME, mock(SurfaceControl.class), new Point(0, 0))
+ });
+
+ verify(mT).show(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index 251ca9c8dcb2..fe536411d5ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.util.magnetictarget
+package com.android.wm.shell.common.magnetictarget
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 5ff94b6308ef..6d1a3c472245 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -38,18 +39,17 @@ import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.DisplayController;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -65,6 +65,7 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
OneHandedAnimationController.OneHandedTransitionAnimator mFakeAnimator;
WindowContainerToken mToken;
SurfaceControl mLeash;
+ TestableLooper mTestableLooper;
@Mock
IWindowContainerToken mMockRealToken;
@Mock
@@ -77,12 +78,16 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
DisplayController mMockDisplayController;
@Mock
SurfaceControl mMockLeash;
- @Spy
- Handler mUpdateHandler;
+ @Mock
+ WindowContainerTransaction mMockWindowContainerTransaction;
+
+ Handler mSpyUpdateHandler;
+ Handler.Callback mUpdateCallback = (msg) -> false;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
mToken = new WindowContainerToken(mMockRealToken);
mLeash = new SurfaceControl();
mDisplay = mContext.getDisplay();
@@ -107,17 +112,14 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
mMockDisplayController,
mMockAnimationController,
mTutorialHandler);
- mUpdateHandler = mDisplayAreaOrganizer.getUpdateHandler();
- }
-
- @Test
- public void testGetDisplayAreaUpdateHandler_isNotNull() {
- assertThat(mUpdateHandler).isNotNull();
+ mSpyUpdateHandler = spy(new Handler(OneHandedThread.get().getLooper(), mUpdateCallback));
+ mDisplayAreaOrganizer.setUpdateHandler(mSpyUpdateHandler);
}
@Test
public void testOnDisplayAreaAppeared() {
mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+ mTestableLooper.processAllMessages();
verify(mMockAnimationController, never()).getAnimator(any(), any(), any());
}
@@ -125,162 +127,195 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase {
@Test
public void testOnDisplayAreaVanished() {
mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
+ mTestableLooper.processAllMessages();
mDisplayAreaOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
- }
- @Test
- public void testOnDisplayAreaInfoChanged_updateDisplayAreaInfo() {
- final DisplayAreaInfo newDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY,
- FEATURE_ONE_HANDED);
- mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
- mDisplayAreaOrganizer.onDisplayAreaInfoChanged(newDisplayAreaInfo);
-
- assertThat(mDisplayAreaOrganizer.mDisplayAreaMap.containsKey(mDisplayAreaInfo)).isTrue();
+ assertThat(mDisplayAreaOrganizer.mDisplayAreaMap).isEmpty();
}
- @Ignore("b/160848002")
@Test
public void testScheduleOffset() {
final int xOffSet = 0;
final int yOffSet = 100;
-
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
mDisplayAreaOrganizer.scheduleOffset(xOffSet, yOffSet);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_OFFSET_ANIMATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
}
- @Ignore("b/160848002")
@Test
- public void testRotation_portraitToLandscape() {
+ public void testRotation_portrait_0_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 90
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_portrait_0_to_seascape_270() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 270
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_270,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_portrait_180_to_landscape_90() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 90
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_portrait_180_to_seascape_270() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 270
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_270,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
}
- @Ignore("b/160848002")
@Test
- public void testRotation_landscapeToPortrait() {
+ public void testRotation_landscape_90_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 0
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_0,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_landscape_90_to_portrait_180() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 180
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_180,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_Seascape_270_to_portrait_0() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 0
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_0,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
+ }
+ @Test
+ public void testRotation_seascape_90_to_portrait_180() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 180
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_180,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(true);
+ verify(mSpyUpdateHandler).sendMessage(any());
}
- @Ignore("b/160848002")
@Test
- public void testRotation_portraitToPortrait() {
+ public void testRotation_portrait_0_to_portrait_0() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 0
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_0,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
+ @Test
+ public void testRotation_portrait_0_to_portrait_180() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 0 -> 180
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_0, Surface.ROTATION_180,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
+ @Test
+ public void testRotation_portrait_180_to_portrait_180() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 180 -> 180
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_180,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
- // Rotate 180 -> 180
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0);
+ @Test
+ public void testRotation_portrait_180_to_portrait_0() {
+ when(mMockLeash.isValid()).thenReturn(false);
+ // Rotate 180 -> 0
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_180, Surface.ROTATION_0,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
}
- @Ignore("b/160848002")
@Test
- public void testRotation_landscapeToLandscape() {
+ public void testRotation_landscape_90_to_landscape_90() {
when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 90
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
+ @Test
+ public void testRotation_landscape_90_to_seascape_270() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 90 -> 270
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_90, Surface.ROTATION_270,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
+ @Test
+ public void testRotation_seascape_270_to_seascape_270() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 270
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_270,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
+ }
+ @Test
+ public void testRotation_seascape_90_to_landscape_90() {
+ when(mMockLeash.isValid()).thenReturn(false);
// Rotate 270 -> 90
- TestableLooper.get(this).processAllMessages();
- mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90);
+ mDisplayAreaOrganizer.onRotateDisplay(Surface.ROTATION_270, Surface.ROTATION_90,
+ mMockWindowContainerTransaction);
+ mTestableLooper.processAllMessages();
- assertThat(mUpdateHandler.hasMessages(
- OneHandedDisplayAreaOrganizer.MSG_RESET_IMMEDIATE)).isEqualTo(false);
+ verify(mSpyUpdateHandler, never()).sendMessage(any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 6f5cbe21be75..255e74917ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.graphics.Matrix;
@@ -33,8 +32,9 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipAnimationControllerTest extends SysuiTestCase {
+public class PipAnimationControllerTest extends PipTestCase {
private PipAnimationController mPipAnimationController;
@@ -61,7 +61,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
mPipAnimationController = new PipAnimationController(
- new PipSurfaceTransactionHelper(mContext, mock(ConfigurationController.class)));
+ new PipSurfaceTransactionHelper(mContext));
mLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("FakeLeash")
@@ -163,7 +163,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
* A dummy {@link SurfaceControl.Transaction} class.
* This is created as {@link Mock} does not support method chaining.
*/
- private static class DummySurfaceControlTx extends SurfaceControl.Transaction {
+ public static class DummySurfaceControlTx extends SurfaceControl.Transaction {
@Override
public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) {
return this;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
index cdb177096f11..d9e3148913b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip;
+package com.android.wm.shell.pip;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -31,7 +31,8 @@ import android.view.Gravity;
import androidx.test.filters.SmallTest;
-import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -46,7 +47,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipBoundsHandlerTest extends SysuiTestCase {
+public class PipBoundsHandlerTest extends PipTestCase {
private static final int ROUNDING_ERROR_MARGIN = 16;
private static final float ASPECT_RATIO_ERROR_MARGIN = 0.01f;
private static final float DEFAULT_ASPECT_RATIO = 1f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java
new file mode 100644
index 000000000000..fdebe4e4e6f5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java
@@ -0,0 +1,53 @@
+/*
+ * 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.wm.shell.pip;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+
+/**
+ * Base class that does One Handed specific setup.
+ */
+public abstract class PipTestCase {
+
+ protected TestableContext mContext;
+
+ @Before
+ public void setup() {
+ final Context context =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final DisplayManager dm = context.getSystemService(DisplayManager.class);
+ mContext = new TestableContext(
+ context.createDisplayContext(dm.getDisplay(DEFAULT_DISPLAY)));
+
+ InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
new file mode 100644
index 000000000000..2b987e98bc7e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMediaController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link PipController}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PipControllerTest extends PipTestCase {
+ private com.android.wm.shell.pip.phone.PipController mPipController;
+ private TestableContext mSpiedContext;
+
+ @Mock private DisplayController mMockdDisplayController;
+ @Mock private PackageManager mPackageManager;
+ @Mock private com.android.wm.shell.pip.phone.PipMenuActivityController
+ mMockPipMenuActivityController;
+ @Mock private PipAppOpsListener mMockPipAppOpsListener;
+ @Mock private PipBoundsHandler mMockPipBoundsHandler;
+ @Mock private PipMediaController mMockPipMediaController;
+ @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+ @Mock private PipTouchHandler mMockPipTouchHandler;
+ @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
+ private PipBoundsState mPipBoundsState;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mPipBoundsState = new PipBoundsState();
+
+ mSpiedContext = spy(mContext);
+
+ when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+ when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
+
+ mPipController = new PipController(mSpiedContext, mMockdDisplayController,
+ mMockPipAppOpsListener, mMockPipBoundsHandler, mPipBoundsState,
+ mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer,
+ mMockPipTouchHandler, mMockWindowManagerShellWrapper);
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotRegisterPipTransitionCallback() {
+ verify(mMockPipTaskOrganizer, never()).registerPipTransitionCallback(any());
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotAddDisplayChangingController() {
+ verify(mMockdDisplayController, never()).addDisplayChangingController(any());
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotAddDisplayWindowListener() {
+ verify(mMockdDisplayController, never()).addDisplayWindowListener(any());
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
new file mode 100644
index 000000000000..37b93bcdd051
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTaskOrganizerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.pip.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * Unit tests for {@link PipTaskOrganizer}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PipTaskOrganizerTest extends PipTestCase {
+ private PipTaskOrganizer mSpiedPipTaskOrganizer;
+ private TestableContext mSpiedContext;
+
+ @Mock private DisplayController mMockdDisplayController;
+ @Mock private PackageManager mPackageManager;
+ @Mock private PipBoundsHandler mMockPipBoundsHandler;
+ @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
+ @Mock private PipUiEventLogger mMockPipUiEventLogger;
+ @Mock private Optional<SplitScreen> mMockOptionalSplitScreen;
+ @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+ private PipBoundsState mPipBoundsState;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mPipBoundsState = new PipBoundsState();
+
+ mSpiedContext = spy(mContext);
+
+ when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+ when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
+
+ mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mSpiedContext, mPipBoundsState,
+ mMockPipBoundsHandler, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen,
+ mMockdDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer));
+ }
+
+ @Test
+ public void testNonPipDevice_shellTaskOrganizer_shouldNotAddListener() {
+ verify(mMockShellTaskOrganizer, never()).addListener(any(), anyInt());
+ }
+
+ @Test
+ public void testNonPipDevice_displayController_shouldNotAddDisplayWindowListener() {
+ verify(mMockdDisplayController, never()).addDisplayWindowListener(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index c8d4aca90519..4713142118a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -22,7 +22,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.app.IActivityManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
@@ -31,16 +30,18 @@ import android.util.Size;
import androidx.test.filters.SmallTest;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSnapAlgorithm;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipResizeGestureHandler;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import org.junit.Before;
import org.junit.Test;
@@ -58,34 +59,23 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipTouchHandlerTest extends SysuiTestCase {
+public class PipTouchHandlerTest extends PipTestCase {
private PipTouchHandler mPipTouchHandler;
@Mock
- private IActivityManager mActivityManager;
-
- @Mock
private PipMenuActivityController mPipMenuActivityController;
@Mock
- private InputConsumerController mInputConsumerController;
-
- @Mock
private PipTaskOrganizer mPipTaskOrganizer;
@Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
- private DeviceConfigProxy mDeviceConfigProxy;
-
- @Mock
- private SysUiState mSysUiState;
-
- @Mock
private PipUiEventLogger mPipUiEventLogger;
+ private PipBoundsState mPipBoundsState;
private PipBoundsHandler mPipBoundsHandler;
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
@@ -102,12 +92,12 @@ public class PipTouchHandlerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mPipBoundsState = new PipBoundsState();
mPipBoundsHandler = new PipBoundsHandler(mContext);
mPipSnapAlgorithm = mPipBoundsHandler.getSnapAlgorithm();
mPipSnapAlgorithm = new PipSnapAlgorithm(mContext);
- mPipTouchHandler = new PipTouchHandler(mContext, mActivityManager,
- mPipMenuActivityController, mInputConsumerController, mPipBoundsHandler,
- mPipTaskOrganizer, mFloatingContentCoordinator, mDeviceConfigProxy, mSysUiState,
+ mPipTouchHandler = new PipTouchHandler(mContext, mPipMenuActivityController,
+ mPipBoundsHandler, mPipBoundsState, mPipTaskOrganizer, mFloatingContentCoordinator,
mPipUiEventLogger);
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
index 17b2e3225200..40667f76b17e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/phone/PipTouchStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.pip.phone;
+package com.android.wm.shell.pip.phone;
import static android.view.MotionEvent.ACTION_BUTTON_PRESS;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -35,7 +35,8 @@ import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
-import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.pip.phone.PipTouchState;
import org.junit.Before;
import org.junit.Test;
@@ -46,7 +47,7 @@ import java.util.concurrent.CountDownLatch;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@RunWithLooper
-public class PipTouchStateTest extends SysuiTestCase {
+public class PipTouchStateTest extends PipTestCase {
private PipTouchState mTouchState;
private CountDownLatch mDoubleTapCallbackTriggeredLatch;
@@ -157,4 +158,4 @@ public class PipTouchStateTest extends SysuiTestCase {
private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
return MotionEvent.obtain(0, eventTime, action, x, y, 0);
}
-} \ No newline at end of file
+}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 6f05cbd0ebb3..71c8e1f6121f 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -30,23 +30,62 @@
namespace android {
-CursorWindow::CursorWindow(const String8& name, int ashmemFd,
- void* data, size_t size, bool readOnly) :
- mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
+/**
+ * By default windows are lightweight inline allocations of this size;
+ * they're only inflated to ashmem regions when more space is needed.
+ */
+static constexpr const size_t kInlineSize = 16384;
+
+CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
+ size_t inflatedSize, bool readOnly) :
+ mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size),
+ mInflatedSize(inflatedSize), mReadOnly(readOnly) {
mHeader = static_cast<Header*>(mData);
}
CursorWindow::~CursorWindow() {
- ::munmap(mData, mSize);
- ::close(mAshmemFd);
+ if (mAshmemFd != -1) {
+ ::munmap(mData, mSize);
+ ::close(mAshmemFd);
+ } else {
+ free(mData);
+ }
+}
+
+status_t CursorWindow::create(const String8& name, size_t inflatedSize,
+ CursorWindow** outCursorWindow) {
+ *outCursorWindow = nullptr;
+
+ size_t size = std::min(kInlineSize, inflatedSize);
+ void* data = calloc(size, 1);
+ if (!data) return NO_MEMORY;
+
+ CursorWindow* window = new CursorWindow(name, -1, data, size,
+ inflatedSize, false /*readOnly*/);
+ status_t result = window->clear();
+ if (!result) {
+ LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ delete window;
+ return result;
}
-status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
+status_t CursorWindow::inflate() {
+ // Shortcut when we can't expand any further
+ if (mSize == mInflatedSize) return INVALID_OPERATION;
+
String8 ashmemName("CursorWindow: ");
- ashmemName.append(name);
+ ashmemName.append(mName);
status_t result;
- int ashmemFd = ashmem_create_region(ashmemName.string(), size);
+ int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
if (ashmemFd < 0) {
result = -errno;
ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
@@ -55,7 +94,8 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o
if (result < 0) {
ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
} else {
- void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+ void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, ashmemFd, 0);
if (data == MAP_FAILED) {
result = -errno;
ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
@@ -64,33 +104,49 @@ status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** o
if (result < 0) {
ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
} else {
- CursorWindow* window = new CursorWindow(name, ashmemFd,
- data, size, false /*readOnly*/);
- result = window->clear();
- if (!result) {
- LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
- "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
- window->mHeader->freeOffset,
- window->mHeader->numRows,
- window->mHeader->numColumns,
- window->mSize, window->mData);
- *outCursorWindow = window;
- return OK;
- }
- delete window;
+ // Move inline contents into new ashmem region
+ memcpy(data, mData, mSize);
+ free(mData);
+ mAshmemFd = ashmemFd;
+ mData = data;
+ mHeader = static_cast<Header*>(mData);
+ mSize = mInflatedSize;
+ LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+ mHeader->freeOffset,
+ mHeader->numRows,
+ mHeader->numColumns,
+ mSize, mData);
+ return OK;
}
}
- ::munmap(data, size);
+ ::munmap(data, mInflatedSize);
}
::close(ashmemFd);
}
- *outCursorWindow = NULL;
return result;
}
status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
- String8 name = parcel->readString8();
+ *outCursorWindow = nullptr;
+
+ String8 name;
+ status_t result = parcel->readString8(&name);
+ if (result) return result;
+
+ bool isAshmem;
+ result = parcel->readBool(&isAshmem);
+ if (result) return result;
+ if (isAshmem) {
+ return createFromParcelAshmem(parcel, name, outCursorWindow);
+ } else {
+ return createFromParcelInline(parcel, name, outCursorWindow);
+ }
+}
+
+status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name,
+ CursorWindow** outCursorWindow) {
status_t result;
int actualSize;
int ashmemFd = parcel->readFileDescriptor();
@@ -122,8 +178,8 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor
actualSize, (int) size, errno);
} else {
CursorWindow* window = new CursorWindow(name, dupAshmemFd,
- data, size, true /*readOnly*/);
- LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, "
+ data, size, size, true /*readOnly*/);
+ LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, "
"numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
window->mHeader->freeOffset,
window->mHeader->numRows,
@@ -140,12 +196,62 @@ status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursor
return result;
}
+status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name,
+ CursorWindow** outCursorWindow) {
+ uint32_t sentSize;
+ status_t result = parcel->readUint32(&sentSize);
+ if (result) return result;
+ if (sentSize > kInlineSize) return NO_MEMORY;
+
+ void* data = calloc(sentSize, 1);
+ if (!data) return NO_MEMORY;
+
+ result = parcel->read(data, sentSize);
+ if (result) return result;
+
+ CursorWindow* window = new CursorWindow(name, -1, data, sentSize,
+ sentSize, true /*readOnly*/);
+ LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+}
+
status_t CursorWindow::writeToParcel(Parcel* parcel) {
- status_t status = parcel->writeString8(mName);
- if (!status) {
- status = parcel->writeDupFileDescriptor(mAshmemFd);
+ LOG_WINDOW("Writing CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+ mHeader->freeOffset,
+ mHeader->numRows,
+ mHeader->numColumns,
+ mSize, mData);
+
+ status_t result = parcel->writeString8(mName);
+ if (result) return result;
+
+ if (mAshmemFd != -1) {
+ result = parcel->writeBool(true);
+ if (result) return result;
+ return writeToParcelAshmem(parcel);
+ } else {
+ result = parcel->writeBool(false);
+ if (result) return result;
+ return writeToParcelInline(parcel);
}
- return status;
+}
+
+status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) {
+ return parcel->writeDupFileDescriptor(mAshmemFd);
+}
+
+status_t CursorWindow::writeToParcelInline(Parcel* parcel) {
+ status_t result = parcel->writeUint32(mHeader->freeOffset);
+ if (result) return result;
+
+ return parcel->write(mData, mHeader->freeOffset);
}
status_t CursorWindow::clear() {
@@ -187,6 +293,7 @@ status_t CursorWindow::allocRow() {
if (rowSlot == NULL) {
return NO_MEMORY;
}
+ uint32_t rowSlotOffset = offsetFromPtr(rowSlot);
// Allocate the slots for the field directory
size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
@@ -201,7 +308,8 @@ status_t CursorWindow::allocRow() {
memset(fieldDir, 0, fieldDirSize);
LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n",
- mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
+ mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset);
+ rowSlot = static_cast<RowSlot*>(offsetToPtr(rowSlotOffset));
rowSlot->offset = fieldDirOffset;
return OK;
}
@@ -229,10 +337,14 @@ uint32_t CursorWindow::alloc(size_t size, bool aligned) {
uint32_t offset = mHeader->freeOffset + padding;
uint32_t nextFreeOffset = offset + size;
if (nextFreeOffset > mSize) {
- ALOGW("Window is full: requested allocation %zu bytes, "
- "free space %zu bytes, window size %zu bytes",
- size, freeSpace(), mSize);
- return 0;
+ // Try inflating to ashmem before finally giving up
+ inflate();
+ if (nextFreeOffset > mSize) {
+ ALOGW("Window is full: requested allocation %zu bytes, "
+ "free space %zu bytes, window size %zu bytes",
+ size, freeSpace(), mSize);
+ return 0;
+ }
}
mHeader->freeOffset = nextFreeOffset;
@@ -260,7 +372,10 @@ CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
}
if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
if (!chunk->nextChunkOffset) {
- chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ uint32_t chunkOffset = offsetFromPtr(chunk);
+ uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunkOffset));
+ chunk->nextChunkOffset = newChunk;
if (!chunk->nextChunkOffset) {
return NULL;
}
@@ -308,6 +423,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
if (!fieldSlot) {
return BAD_VALUE;
}
+ uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot);
uint32_t offset = alloc(size);
if (!offset) {
@@ -316,6 +432,7 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
memcpy(offsetToPtr(offset), value, size);
+ fieldSlot = static_cast<FieldSlot*>(offsetToPtr(fieldSlotOffset));
fieldSlot->type = type;
fieldSlot->data.buffer.offset = offset;
fieldSlot->data.buffer.size = size;
diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h
index ad64b246b3f5..0bee60929cc9 100644
--- a/libs/androidfw/include/androidfw/CursorWindow.h
+++ b/libs/androidfw/include/androidfw/CursorWindow.h
@@ -50,8 +50,8 @@ namespace android {
* Strings are stored in UTF-8.
*/
class CursorWindow {
- CursorWindow(const String8& name, int ashmemFd,
- void* data, size_t size, bool readOnly);
+ CursorWindow(const String8& name, int ashmemFd, void* data, size_t size,
+ size_t inflatedSize, bool readOnly);
public:
/* Field types. */
@@ -165,11 +165,12 @@ private:
int mAshmemFd;
void* mData;
size_t mSize;
+ size_t mInflatedSize;
bool mReadOnly;
Header* mHeader;
inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) {
- if (offset >= mSize) {
+ if (offset > mSize) {
ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize);
return NULL;
}
@@ -185,6 +186,18 @@ private:
return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
}
+ static status_t createFromParcelAshmem(Parcel*, String8&, CursorWindow**);
+ static status_t createFromParcelInline(Parcel*, String8&, CursorWindow**);
+
+ status_t writeToParcelAshmem(Parcel*);
+ status_t writeToParcelInline(Parcel*);
+
+ /**
+ * By default windows are lightweight inline allocations; this method
+ * inflates the window into a larger ashmem region.
+ */
+ status_t inflate();
+
/**
* Allocate a portion of the window. Returns the offset
* of the allocation, or 0 if there isn't enough space.
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 90d2537d97a8..155bb6ba8f75 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -327,6 +327,7 @@ cc_defaults {
"jni/PathMeasure.cpp",
"jni/Picture.cpp",
"jni/Shader.cpp",
+ "jni/RenderEffect.cpp",
"jni/Typeface.cpp",
"jni/Utils.cpp",
"jni/YuvToJpegEncoder.cpp",
@@ -334,6 +335,7 @@ cc_defaults {
"jni/fonts/FontFamily.cpp",
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
+ "jni/text/TextShaper.cpp",
],
header_libs: [ "android_graphics_jni_headers" ],
@@ -462,14 +464,6 @@ cc_defaults {
"RenderNode.cpp",
"RenderProperties.cpp",
"RootRenderNode.cpp",
- "shader/Shader.cpp",
- "shader/BitmapShader.cpp",
- "shader/BlurShader.cpp",
- "shader/ComposeShader.cpp",
- "shader/LinearGradientShader.cpp",
- "shader/RadialGradientShader.cpp",
- "shader/RuntimeShader.cpp",
- "shader/SweepGradientShader.cpp",
"SkiaCanvas.cpp",
"VectorDrawable.cpp",
],
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 30ce5370760d..fd18d2f9192d 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -31,6 +31,7 @@ const std::string FrameInfoNames[] = {
"AnimationStart",
"PerformTraversalsStart",
"DrawStart",
+ "FrameDeadline",
"SyncQueued",
"SyncStart",
"IssueDrawCommandsStart",
@@ -45,7 +46,7 @@ static_assert((sizeof(FrameInfoNames) / sizeof(FrameInfoNames[0])) ==
static_cast<int>(FrameInfoIndex::NumIndexes),
"size mismatch: FrameInfoNames doesn't match the enum!");
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 18,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f5bfedde2f92..d24eca7d00c6 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -27,7 +27,7 @@
namespace android {
namespace uirenderer {
-#define UI_THREAD_FRAME_INFO_SIZE 10
+#define UI_THREAD_FRAME_INFO_SIZE 11
enum class FrameInfoIndex {
Flags = 0,
@@ -40,6 +40,7 @@ enum class FrameInfoIndex {
AnimationStart,
PerformTraversalsStart,
DrawStart,
+ FrameDeadline,
// End of UI frame info
SyncQueued,
@@ -77,9 +78,11 @@ public:
explicit UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
set(FrameInfoIndex::FrameTimelineVsyncId) = INVALID_VSYNC_ID;
+ set(FrameInfoIndex::FrameDeadline) = std::numeric_limits<int64_t>::max();
}
- UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync, int64_t vsyncId) {
+ UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync,
+ int64_t vsyncId, int64_t frameDeadline) {
set(FrameInfoIndex::FrameTimelineVsyncId) = vsyncId;
set(FrameInfoIndex::Vsync) = vsyncTime;
set(FrameInfoIndex::IntendedVsync) = intendedVsync;
@@ -89,6 +92,7 @@ public:
set(FrameInfoIndex::AnimationStart) = vsyncTime;
set(FrameInfoIndex::PerformTraversalsStart) = vsyncTime;
set(FrameInfoIndex::DrawStart) = vsyncTime;
+ set(FrameInfoIndex::FrameDeadline) = frameDeadline;
return *this;
}
diff --git a/libs/hwui/OWNERS b/libs/hwui/OWNERS
index c232d1360419..bb93e66968be 100644
--- a/libs/hwui/OWNERS
+++ b/libs/hwui/OWNERS
@@ -5,3 +5,6 @@ njawad@google.com
reed@google.com
scroggo@google.com
stani@google.com
+
+# For text, e.g. Typeface, Font, Minikin, etc.
+nona@google.com
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index ff9cf45cdc73..8fba9cf21df1 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -49,6 +49,12 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) {
return true;
}
+bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) {
+ if(mImageFilter.get() == imageFilter) return false;
+ mImageFilter = sk_ref_sp(imageFilter);
+ return true;
+}
+
bool LayerProperties::setFromPaint(const SkPaint* paint) {
bool changed = false;
changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint)));
@@ -63,6 +69,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
setAlpha(other.alpha());
setXferMode(other.xferMode());
setColorFilter(other.getColorFilter());
+ setImageFilter(other.getImageFilter());
return *this;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index ef4cd1f1eb62..aeb60e6ce355 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -27,6 +27,7 @@
#include "utils/PaintUtils.h"
#include <SkBlendMode.h>
+#include <SkImageFilter.h>
#include <SkCamera.h>
#include <SkColor.h>
#include <SkMatrix.h>
@@ -93,6 +94,10 @@ public:
SkColorFilter* getColorFilter() const { return mColorFilter.get(); }
+ bool setImageFilter(SkImageFilter* imageFilter);
+
+ SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
+
// Sets alpha, xfermode, and colorfilter from an SkPaint
// paint may be NULL, in which case defaults will be set
bool setFromPaint(const SkPaint* paint);
@@ -118,6 +123,7 @@ private:
uint8_t mAlpha;
SkBlendMode mMode;
sk_sp<SkColorFilter> mColorFilter;
+ sk_sp<SkImageFilter> mImageFilter;
};
/*
@@ -541,6 +547,7 @@ public:
bool promotedToLayer() const {
return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
(mComputedFields.mNeedLayerForFunctors ||
+ mLayerProperties.mImageFilter != nullptr ||
(!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
mPrimitiveFields.mHasOverlappingRendering));
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index cfba5d4f6aa2..1dbce58fb7c9 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -42,8 +42,6 @@
#include <SkTextBlob.h>
#include <SkVertices.h>
-#include <shader/BitmapShader.h>
-
#include <memory>
#include <optional>
#include <utility>
@@ -51,7 +49,6 @@
namespace android {
using uirenderer::PaintUtils;
-using uirenderer::BitmapShader;
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
return new SkiaCanvas(bitmap);
@@ -684,9 +681,7 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
if (paint) {
pnt = *paint;
}
-
- pnt.setShader(sk_ref_sp(new BitmapShader(
- bitmap.makeImage(), SkTileMode::kClamp, SkTileMode::kClamp, nullptr)));
+ pnt.setShader(bitmap.makeImage()->makeShader());
auto v = builder.detach();
apply_looper(&pnt, [&](const SkPaint& p) {
mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
@@ -735,8 +730,7 @@ void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
// ----------------------------------------------------------------------------
void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y, float boundsLeft, float boundsTop, float boundsRight,
- float boundsBottom, float totalAdvance) {
+ float y, float totalAdvance) {
if (count <= 0 || paint.nothingToDraw()) return;
Paint paintCopy(paint);
if (mPaintFilter) {
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 1df2b2671659..2cb850c83934 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -161,8 +161,7 @@ protected:
void drawDrawable(SkDrawable* drawable) { mCanvas->drawDrawable(drawable); }
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y, float boundsLeft, float boundsTop, float boundsRight,
- float boundsBottom, float totalAdvance) override;
+ float y, float totalAdvance) override;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) override;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 0f566e4b494a..cd908354aea5 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -23,6 +23,7 @@
#include "PathParser.h"
#include "SkColorFilter.h"
#include "SkImageInfo.h"
+#include "SkShader.h"
#include "hwui/Paint.h"
#ifdef __ANDROID__
@@ -158,10 +159,10 @@ void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
- Paint paint;
+ SkPaint paint;
if (properties.getFillGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
- paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getFillGradient())));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
needsFill = true;
} else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -178,7 +179,7 @@ void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
bool needsStroke = false;
if (properties.getStrokeGradient() != nullptr) {
paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
- paint.setShader(sk_sp<Shader>(SkSafeRef(properties.getStrokeGradient())));
+ paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
needsStroke = true;
} else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index d4086f1aa622..ac7d41e0d600 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -31,8 +31,8 @@
#include <SkPath.h>
#include <SkPathMeasure.h>
#include <SkRect.h>
+#include <SkShader.h>
#include <SkSurface.h>
-#include <shader/Shader.h>
#include <cutils/compiler.h>
#include <stddef.h>
@@ -227,20 +227,20 @@ public:
strokeGradient = prop.strokeGradient;
onPropertyChanged();
}
- void setFillGradient(Shader* gradient) {
+ void setFillGradient(SkShader* gradient) {
if (fillGradient.get() != gradient) {
fillGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- void setStrokeGradient(Shader* gradient) {
+ void setStrokeGradient(SkShader* gradient) {
if (strokeGradient.get() != gradient) {
strokeGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- Shader* getFillGradient() const { return fillGradient.get(); }
- Shader* getStrokeGradient() const { return strokeGradient.get(); }
+ SkShader* getFillGradient() const { return fillGradient.get(); }
+ SkShader* getStrokeGradient() const { return strokeGradient.get(); }
float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
void setStrokeWidth(float strokeWidth) {
VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -320,8 +320,8 @@ public:
count,
};
PrimitiveFields mPrimitiveFields;
- sk_sp<Shader> fillGradient;
- sk_sp<Shader> strokeGradient;
+ sk_sp<SkShader> fillGradient;
+ sk_sp<SkShader> strokeGradient;
};
// Called from UI thread
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 4bbf1214bdcf..dca10e29cbb8 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -47,6 +47,7 @@ extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_NinePatch(JNIEnv*);
extern int register_android_graphics_PathEffect(JNIEnv* env);
extern int register_android_graphics_Shader(JNIEnv* env);
+extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
namespace android {
@@ -108,6 +109,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
// {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
{"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
+ {"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)},
{"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
{"android.graphics.animation.NativeInterpolatorFactory",
REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)},
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 12e2e8135278..e1f5abd786bf 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -43,6 +43,7 @@ extern int register_android_graphics_Movie(JNIEnv* env);
extern int register_android_graphics_NinePatch(JNIEnv*);
extern int register_android_graphics_PathEffect(JNIEnv* env);
extern int register_android_graphics_Shader(JNIEnv* env);
+extern int register_android_graphics_RenderEffect(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
extern int register_android_graphics_YuvImage(JNIEnv* env);
@@ -73,6 +74,7 @@ extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
+extern int register_android_graphics_text_TextShaper(JNIEnv *env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -123,6 +125,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Picture),
REG_JNI(register_android_graphics_Region),
REG_JNI(register_android_graphics_Shader),
+ REG_JNI(register_android_graphics_RenderEffect),
REG_JNI(register_android_graphics_TextureLayer),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_YuvImage),
@@ -137,6 +140,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_pdf_PdfRenderer),
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
+ REG_JNI(register_android_graphics_text_TextShaper),
REG_JNI(register_android_util_PathParser),
REG_JNI(register_android_view_RenderNode),
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 2a377bbb83f2..146bf283c58a 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -73,7 +73,7 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa
static void simplifyPaint(int color, Paint* paint) {
paint->setColor(color);
- paint->setShader((sk_sp<uirenderer::Shader>)nullptr);
+ paint->setShader(nullptr);
paint->setColorFilter(nullptr);
paint->setLooper(nullptr);
paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
@@ -84,13 +84,12 @@ static void simplifyPaint(int color, Paint* paint) {
class DrawTextFunctor {
public:
DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
- float y, minikin::MinikinRect& bounds, float totalAdvance)
+ float y, float totalAdvance)
: layout(layout)
, canvas(canvas)
, paint(paint)
, x(x)
, y(y)
- , bounds(bounds)
, totalAdvance(totalAdvance) {}
void operator()(size_t start, size_t end) {
@@ -114,19 +113,16 @@ public:
Paint outlinePaint(paint);
simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, bounds.mLeft, bounds.mTop,
- bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
// inner
Paint innerPaint(paint);
simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
innerPaint.setStyle(SkPaint::kFill_Style);
- canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, bounds.mLeft, bounds.mTop,
- bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
} else {
// standard draw path
- canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, bounds.mLeft, bounds.mTop,
- bounds.mRight, bounds.mBottom, totalAdvance);
+ canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
}
}
@@ -136,10 +132,29 @@ private:
const Paint& paint;
float x;
float y;
- minikin::MinikinRect& bounds;
float totalAdvance;
};
+void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
+ int glyphCount, const Paint& paint) {
+ // Minikin modify skFont for auto-fakebold/auto-fakeitalic.
+ Paint copied(paint);
+
+ auto glyphFunc = [&](uint16_t* outGlyphIds, float* outPositions) {
+ for (uint32_t i = 0; i < glyphCount; ++i) {
+ outGlyphIds[i] = static_cast<uint16_t>(glyphIds[i]);
+ }
+ memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
+ };
+
+ const minikin::MinikinFont* minikinFont = font.typeface().get();
+ SkFont* skfont = &copied.getSkFont();
+ MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
+
+ // total advance is used for drawing underline. We do not support underlyine by glyph drawing.
+ drawGlyphs(glyphFunc, glyphCount, copied, 0 /* x */, 0 /* y */, 0 /* total Advance */);
+}
+
void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, int contextStart,
int contextCount, float x, float y, minikin::Bidi bidiFlags,
const Paint& origPaint, const Typeface* typeface, minikin::MeasuredText* mt) {
@@ -156,15 +171,12 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count,
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
- minikin::MinikinRect bounds;
- layout.getBounds(&bounds);
-
// Set align to left for drawing, as we don't want individual
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
paint.setTextAlign(Paint::kLeft_Align);
- DrawTextFunctor f(layout, this, paint, x, y, bounds, layout.getAdvance());
+ DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
MinikinUtils::forFontRun(layout, &paint, f);
}
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 333567b0cf91..772b7a28ef04 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -32,6 +32,7 @@ class SkCanvasState;
class SkVertices;
namespace minikin {
+class Font;
class Layout;
class MeasuredText;
enum class Bidi : uint8_t;
@@ -255,6 +256,9 @@ public:
*/
virtual void drawVectorDrawable(VectorDrawableRoot* tree) = 0;
+ void drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
+ int glyphCount, const Paint& paint);
+
/**
* Converts utf16 text to glyphs, calculating position and boundary,
* and delegating the final draw to virtual drawGlyphs method.
@@ -288,8 +292,7 @@ protected:
* totalAdvance: used to define width of text decorations (underlines, strikethroughs).
*/
virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
- float y, float boundsLeft, float boundsTop, float boundsRight,
- float boundsBottom, float totalAdvance) = 0;
+ float y,float totalAdvance) = 0;
virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
const Paint& paint, const SkPath& path, size_t start,
size_t end) = 0;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index a6137b073d5a..0e338f35b8e7 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -33,8 +33,7 @@ namespace android {
MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize,
std::string_view filePath, int ttcIndex,
const std::vector<minikin::FontVariation>& axes)
- : minikin::MinikinFont(typeface->uniqueID())
- , mTypeface(std::move(typeface))
+ : mTypeface(std::move(typeface))
, mFontData(fontData)
, mFontSize(fontSize)
, mTtcIndex(ttcIndex)
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index 298967689cd9..77a21428f36a 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -49,6 +49,8 @@ public:
void GetFontExtent(minikin::MinikinExtent* extent, const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const override;
+ const std::string& GetFontPath() const override { return mFilePath; }
+
SkTypeface* GetSkTypeface() const;
sk_sp<SkTypeface> RefSkTypeface() const;
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 0eacde9a467e..7c3f0d84a75b 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -69,7 +69,7 @@ public:
size_t start = 0;
size_t nGlyphs = layout.nGlyphs();
for (size_t i = 0; i < nGlyphs; i++) {
- const minikin::MinikinFont* nextFont = layout.getFont(i);
+ const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
if (i > 0 && nextFont != curFont) {
SkFont* skfont = &paint->getSkFont();
MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 0bb689c19079..e75e9e7c6933 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -30,8 +30,6 @@
#include <minikin/FamilyVariant.h>
#include <minikin/Hyphenator.h>
-#include <shader/Shader.h>
-
namespace android {
class Paint : public SkPaint {
@@ -151,14 +149,8 @@ public:
// The only respected flags are : [ antialias, dither, filterBitmap ]
static uint32_t GetSkPaintJavaFlags(const SkPaint&);
static void SetSkPaintJavaFlags(SkPaint*, uint32_t flags);
-
- void setShader(sk_sp<uirenderer::Shader> shader);
private:
-
- using SkPaint::setShader;
- using SkPaint::setImageFilter;
-
SkFont mFont;
sk_sp<SkDrawLooper> mLooper;
@@ -177,7 +169,6 @@ private:
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
- sk_sp<uirenderer::Shader> mShader;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index 21f60fd7b671..fa2674fc2f5e 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -24,8 +24,7 @@ Paint::Paint()
, mWordSpacing(0)
, mFontFeatureSettings()
, mMinikinLocaleListId(0)
- , mFamilyVariant(minikin::FamilyVariant::DEFAULT)
- , mShader(nullptr) {
+ , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {
// SkPaint::antialiasing defaults to false, but
// SkFont::edging defaults to kAntiAlias. To keep them
// insync, we manually set the font to kAilas.
@@ -46,8 +45,7 @@ Paint::Paint(const Paint& paint)
, mAlign(paint.mAlign)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern)
- , mShader(paint.mShader){}
+ , mDevKern(paint.mDevKern) {}
Paint::~Paint() {}
@@ -67,30 +65,9 @@ Paint& Paint::operator=(const Paint& other) {
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
- mShader = other.mShader;
return *this;
}
-void Paint::setShader(sk_sp<uirenderer::Shader> shader) {
- if (shader) {
- // If there is an SkShader compatible shader, apply it
- sk_sp<SkShader> skShader = shader->asSkShader();
- if (skShader.get()) {
- SkPaint::setShader(skShader);
- SkPaint::setImageFilter(nullptr);
- } else {
- // ... otherwise the specified shader can only be represented as an ImageFilter
- SkPaint::setShader(nullptr);
- SkPaint::setImageFilter(shader->asSkImageFilter());
- }
- } else {
- // No shader is provided at all, clear out both the SkShader and SkImageFilter slots
- SkPaint::setShader(nullptr);
- SkPaint::setImageFilter(nullptr);
- }
- mShader = shader;
-}
-
bool operator==(const Paint& a, const Paint& b) {
return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
a.mFont == b.mFont &&
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index ccc328c702db..03f1d62625f1 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -188,7 +188,7 @@ void Typeface::setRobotoTypefaceForTest() {
std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
std::move(typeface), data, st.st_size, kRobotoFont, 0,
std::vector<minikin::FontVariation>());
- std::vector<minikin::Font> fonts;
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 68eaa0a3ca54..2e85840cad99 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -42,7 +42,7 @@ struct NativeFamilyBuilder {
: langId(langId), variant(static_cast<minikin::FamilyVariant>(variant)) {}
uint32_t langId;
minikin::FamilyVariant variant;
- std::vector<minikin::Font> fonts;
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
std::vector<minikin::FontVariation> axes;
};
diff --git a/libs/hwui/jni/FontUtils.h b/libs/hwui/jni/FontUtils.h
index b36b4e60e33a..ba4e56e4c7f7 100644
--- a/libs/hwui/jni/FontUtils.h
+++ b/libs/hwui/jni/FontUtils.h
@@ -19,6 +19,7 @@
#include <jni.h>
#include <memory>
+#include <utility>
#include <minikin/Font.h>
@@ -34,8 +35,8 @@ struct FontFamilyWrapper {
};
struct FontWrapper {
- FontWrapper(minikin::Font&& font) : font(std::move(font)) {}
- minikin::Font font;
+ explicit FontWrapper(std::shared_ptr<minikin::Font>&& font) : font(font) {}
+ std::shared_ptr<minikin::Font> font;
};
// Utility wrapper for java.util.List
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index ecbb55ec878d..77f46beb2100 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -9,6 +9,7 @@
#include "GraphicsJNI.h"
#include "SkCanvas.h"
+#include "SkFontMetrics.h"
#include "SkMath.h"
#include "SkRegion.h"
#include <cutils/ashmem.h>
@@ -228,6 +229,20 @@ static jfieldID gColorSpace_Named_LinearExtendedSRGBFieldID;
static jclass gTransferParameters_class;
static jmethodID gTransferParameters_constructorMethodID;
+static jclass gFontMetrics_class;
+static jfieldID gFontMetrics_top;
+static jfieldID gFontMetrics_ascent;
+static jfieldID gFontMetrics_descent;
+static jfieldID gFontMetrics_bottom;
+static jfieldID gFontMetrics_leading;
+
+static jclass gFontMetricsInt_class;
+static jfieldID gFontMetricsInt_top;
+static jfieldID gFontMetricsInt_ascent;
+static jfieldID gFontMetricsInt_descent;
+static jfieldID gFontMetricsInt_bottom;
+static jfieldID gFontMetricsInt_leading;
+
///////////////////////////////////////////////////////////////////////////////
void GraphicsJNI::get_jrect(JNIEnv* env, jobject obj, int* L, int* T, int* R, int* B)
@@ -468,6 +483,32 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region)
return r;
}
+void GraphicsJNI::set_metrics(JNIEnv* env, jobject metrics, const SkFontMetrics& skmetrics) {
+ if (metrics == nullptr) return;
+ SkASSERT(env->IsInstanceOf(metrics, gFontMetrics_class));
+ env->SetFloatField(metrics, gFontMetrics_top, SkScalarToFloat(skmetrics.fTop));
+ env->SetFloatField(metrics, gFontMetrics_ascent, SkScalarToFloat(skmetrics.fAscent));
+ env->SetFloatField(metrics, gFontMetrics_descent, SkScalarToFloat(skmetrics.fDescent));
+ env->SetFloatField(metrics, gFontMetrics_bottom, SkScalarToFloat(skmetrics.fBottom));
+ env->SetFloatField(metrics, gFontMetrics_leading, SkScalarToFloat(skmetrics.fLeading));
+}
+
+int GraphicsJNI::set_metrics_int(JNIEnv* env, jobject metrics, const SkFontMetrics& skmetrics) {
+ int ascent = SkScalarRoundToInt(skmetrics.fAscent);
+ int descent = SkScalarRoundToInt(skmetrics.fDescent);
+ int leading = SkScalarRoundToInt(skmetrics.fLeading);
+
+ if (metrics) {
+ SkASSERT(env->IsInstanceOf(metrics, gFontMetricsInt_class));
+ env->SetIntField(metrics, gFontMetricsInt_top, SkScalarFloorToInt(skmetrics.fTop));
+ env->SetIntField(metrics, gFontMetricsInt_ascent, ascent);
+ env->SetIntField(metrics, gFontMetricsInt_descent, descent);
+ env->SetIntField(metrics, gFontMetricsInt_bottom, SkScalarCeilToInt(skmetrics.fBottom));
+ env->SetIntField(metrics, gFontMetricsInt_leading, leading);
+ }
+ return descent - ascent + leading;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
jobject GraphicsJNI::createBitmapRegionDecoder(JNIEnv* env, skia::BitmapRegionDecoder* bitmap)
@@ -764,5 +805,23 @@ int register_android_graphics_Graphics(JNIEnv* env)
gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
"<init>", "(DDDDDDD)V");
+ gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
+ gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
+
+ gFontMetrics_top = GetFieldIDOrDie(env, gFontMetrics_class, "top", "F");
+ gFontMetrics_ascent = GetFieldIDOrDie(env, gFontMetrics_class, "ascent", "F");
+ gFontMetrics_descent = GetFieldIDOrDie(env, gFontMetrics_class, "descent", "F");
+ gFontMetrics_bottom = GetFieldIDOrDie(env, gFontMetrics_class, "bottom", "F");
+ gFontMetrics_leading = GetFieldIDOrDie(env, gFontMetrics_class, "leading", "F");
+
+ gFontMetricsInt_class = FindClassOrDie(env, "android/graphics/Paint$FontMetricsInt");
+ gFontMetricsInt_class = MakeGlobalRefOrDie(env, gFontMetricsInt_class);
+
+ gFontMetricsInt_top = GetFieldIDOrDie(env, gFontMetricsInt_class, "top", "I");
+ gFontMetricsInt_ascent = GetFieldIDOrDie(env, gFontMetricsInt_class, "ascent", "I");
+ gFontMetricsInt_descent = GetFieldIDOrDie(env, gFontMetricsInt_class, "descent", "I");
+ gFontMetricsInt_bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
+ gFontMetricsInt_leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
+
return 0;
}
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 79ab617411e3..541d5a53de07 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -18,6 +18,7 @@
#include "graphics_jni_helpers.h"
class SkCanvas;
+struct SkFontMetrics;
namespace android {
namespace skia {
@@ -85,6 +86,17 @@ public:
bool* isHardware);
static SkRegion* getNativeRegion(JNIEnv*, jobject region);
+ /**
+ * Set SkFontMetrics to Java Paint.FontMetrics.
+ * Do nothing if metrics is nullptr.
+ */
+ static void set_metrics(JNIEnv*, jobject metrics, const SkFontMetrics& skmetrics);
+ /**
+ * Set SkFontMetrics to Java Paint.FontMetricsInt and return recommended interline space.
+ * Do nothing if metrics is nullptr.
+ */
+ static int set_metrics_int(JNIEnv*, jobject metrics, const SkFontMetrics& skmetrics);
+
/*
* LegacyBitmapConfig is the old enum in Skia that matched the enum int values
* in Bitmap.Config. Skia no longer supports this config, but has replaced it
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 554674a331cd..89ff9b257642 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -47,7 +47,6 @@
#include <minikin/LocaleList.h>
#include <minikin/Measurement.h>
#include <minikin/MinikinPaint.h>
-#include <shader/Shader.h>
#include <unicode/utf16.h>
#include <cassert>
@@ -55,24 +54,8 @@
#include <memory>
#include <vector>
-using namespace android::uirenderer;
-
namespace android {
-struct JMetricsID {
- jfieldID top;
- jfieldID ascent;
- jfieldID descent;
- jfieldID bottom;
- jfieldID leading;
-};
-
-static jclass gFontMetrics_class;
-static JMetricsID gFontMetrics_fieldID;
-
-static jclass gFontMetricsInt_class;
-static JMetricsID gFontMetricsInt_fieldID;
-
static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
const SkPoint pos[], SkPath* dst) {
dst->reset();
@@ -618,35 +601,14 @@ namespace PaintGlue {
static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
SkFontMetrics metrics;
SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
-
- if (metricsObj) {
- SkASSERT(env->IsInstanceOf(metricsObj, gFontMetrics_class));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.top, SkScalarToFloat(metrics.fTop));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.ascent, SkScalarToFloat(metrics.fAscent));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.descent, SkScalarToFloat(metrics.fDescent));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.bottom, SkScalarToFloat(metrics.fBottom));
- env->SetFloatField(metricsObj, gFontMetrics_fieldID.leading, SkScalarToFloat(metrics.fLeading));
- }
+ GraphicsJNI::set_metrics(env, metricsObj, metrics);
return SkScalarToFloat(spacing);
}
static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
SkFontMetrics metrics;
-
getMetricsInternal(paintHandle, &metrics);
- int ascent = SkScalarRoundToInt(metrics.fAscent);
- int descent = SkScalarRoundToInt(metrics.fDescent);
- int leading = SkScalarRoundToInt(metrics.fLeading);
-
- if (metricsObj) {
- SkASSERT(env->IsInstanceOf(metricsObj, gFontMetricsInt_class));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.top, SkScalarFloorToInt(metrics.fTop));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.ascent, ascent);
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.descent, descent);
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.bottom, SkScalarCeilToInt(metrics.fBottom));
- env->SetIntField(metricsObj, gFontMetricsInt_fieldID.leading, leading);
- }
- return descent - ascent + leading;
+ return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
}
@@ -785,10 +747,11 @@ namespace PaintGlue {
return obj->getFillPath(*src, dst) ? JNI_TRUE : JNI_FALSE;
}
- static void setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
- auto* paint = reinterpret_cast<Paint*>(objHandle);
- auto* shader = reinterpret_cast<Shader*>(shaderHandle);
- paint->setShader(sk_ref_sp(shader));
+ static jlong setShader(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong shaderHandle) {
+ Paint* obj = reinterpret_cast<Paint*>(objHandle);
+ SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
+ obj->setShader(sk_ref_sp(shader));
+ return reinterpret_cast<jlong>(obj->getShader());
}
static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
@@ -1099,7 +1062,7 @@ static const JNINativeMethod methods[] = {
{"nGetStrokeJoin","(J)I", (void*) PaintGlue::getStrokeJoin},
{"nSetStrokeJoin","(JI)V", (void*) PaintGlue::setStrokeJoin},
{"nGetFillPath","(JJJ)Z", (void*) PaintGlue::getFillPath},
- {"nSetShader","(JJ)V", (void*) PaintGlue::setShader},
+ {"nSetShader","(JJ)J", (void*) PaintGlue::setShader},
{"nSetColorFilter","(JJ)J", (void*) PaintGlue::setColorFilter},
{"nSetXfermode","(JI)V", (void*) PaintGlue::setXfermode},
{"nSetPathEffect","(JJ)J", (void*) PaintGlue::setPathEffect},
@@ -1137,24 +1100,6 @@ static const JNINativeMethod methods[] = {
};
int register_android_graphics_Paint(JNIEnv* env) {
- gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
- gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
-
- gFontMetrics_fieldID.top = GetFieldIDOrDie(env, gFontMetrics_class, "top", "F");
- gFontMetrics_fieldID.ascent = GetFieldIDOrDie(env, gFontMetrics_class, "ascent", "F");
- gFontMetrics_fieldID.descent = GetFieldIDOrDie(env, gFontMetrics_class, "descent", "F");
- gFontMetrics_fieldID.bottom = GetFieldIDOrDie(env, gFontMetrics_class, "bottom", "F");
- gFontMetrics_fieldID.leading = GetFieldIDOrDie(env, gFontMetrics_class, "leading", "F");
-
- gFontMetricsInt_class = FindClassOrDie(env, "android/graphics/Paint$FontMetricsInt");
- gFontMetricsInt_class = MakeGlobalRefOrDie(env, gFontMetricsInt_class);
-
- gFontMetricsInt_fieldID.top = GetFieldIDOrDie(env, gFontMetricsInt_class, "top", "I");
- gFontMetricsInt_fieldID.ascent = GetFieldIDOrDie(env, gFontMetricsInt_class, "ascent", "I");
- gFontMetricsInt_fieldID.descent = GetFieldIDOrDie(env, gFontMetricsInt_class, "descent", "I");
- gFontMetricsInt_fieldID.bottom = GetFieldIDOrDie(env, gFontMetricsInt_class, "bottom", "I");
- gFontMetricsInt_fieldID.leading = GetFieldIDOrDie(env, gFontMetricsInt_class, "leading", "I");
-
return RegisterMethodsOrDie(env, "android/graphics/Paint", methods, NELEM(methods));
}
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
new file mode 100644
index 000000000000..0ebd0ca720d8
--- /dev/null
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+#include "Bitmap.h"
+#include "GraphicsJNI.h"
+#include "SkImageFilter.h"
+#include "SkImageFilters.h"
+#include "graphics_jni_helpers.h"
+#include "utils/Blur.h"
+#include <utils/Log.h>
+
+using namespace android::uirenderer;
+
+static jlong createOffsetEffect(
+ JNIEnv* env,
+ jobject,
+ jfloat offsetX,
+ jfloat offsetY,
+ jlong inputFilterHandle
+) {
+ auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
+ sk_sp<SkImageFilter> offset = SkImageFilters::Offset(offsetX, offsetY, sk_ref_sp(inputFilter));
+ return reinterpret_cast<jlong>(offset.release());
+}
+
+static jlong createBlurEffect(JNIEnv* env , jobject, jfloat radiusX,
+ jfloat radiusY, jlong inputFilterHandle, jint edgeTreatment) {
+ auto* inputImageFilter = reinterpret_cast<SkImageFilter*>(inputFilterHandle);
+ sk_sp<SkImageFilter> blurFilter =
+ SkImageFilters::Blur(
+ Blur::convertRadiusToSigma(radiusX),
+ Blur::convertRadiusToSigma(radiusY),
+ static_cast<SkTileMode>(edgeTreatment),
+ sk_ref_sp(inputImageFilter),
+ nullptr);
+ return reinterpret_cast<jlong>(blurFilter.release());
+}
+
+static void RenderEffect_safeUnref(SkImageFilter* filter) {
+ SkSafeUnref(filter);
+}
+
+static jlong getRenderEffectFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&RenderEffect_safeUnref));
+}
+
+static const JNINativeMethod gRenderEffectMethods[] = {
+ {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer},
+ {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect},
+ {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect}
+};
+
+int register_android_graphics_RenderEffect(JNIEnv* env) {
+ android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect",
+ gRenderEffectMethods, NELEM(gRenderEffectMethods));
+ return 0;
+} \ No newline at end of file
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7cb77233846f..e36e35504494 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -5,14 +5,6 @@
#include "SkShader.h"
#include "SkBlendMode.h"
#include "include/effects/SkRuntimeEffect.h"
-#include "shader/Shader.h"
-#include "shader/BitmapShader.h"
-#include "shader/BlurShader.h"
-#include "shader/ComposeShader.h"
-#include "shader/LinearGradientShader.h"
-#include "shader/RadialGradientShader.h"
-#include "shader/RuntimeShader.h"
-#include "shader/SweepGradientShader.h"
#include <vector>
@@ -58,7 +50,7 @@ static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvAr
///////////////////////////////////////////////////////////////////////////////////////////////
-static void Shader_safeUnref(Shader* shader) {
+static void Shader_safeUnref(SkShader* shader) {
SkSafeUnref(shader);
}
@@ -82,15 +74,15 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j
SkBitmap bitmap;
image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode);
}
+ sk_sp<SkShader> shader = image->makeShader(
+ (SkTileMode)tileModeX, (SkTileMode)tileModeY);
+ ThrowIAE_IfNull(env, shader.get());
- auto* shader = new BitmapShader(
- image,
- static_cast<SkTileMode>(tileModeX),
- static_cast<SkTileMode>(tileModeY),
- matrix
- );
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -126,18 +118,17 @@ static jlong LinearGradient_create(JNIEnv* env, jobject, jlong matrixPtr,
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* shader = new LinearGradientShader(
- pts,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- static_cast<SkTileMode>(tileMode),
- sGradientShaderFlags,
- matrix
- );
+ sk_sp<SkShader> shader(SkGradientShader::MakeLinear(pts, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr));
+ ThrowIAE_IfNull(env, shader);
- return reinterpret_cast<jlong>(shader);
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
+
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -157,20 +148,17 @@ static jlong RadialGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = SkGradientShader::MakeRadial(center, radius, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ static_cast<SkTileMode>(tileMode), sGradientShaderFlags, nullptr);
+ ThrowIAE_IfNull(env, shader);
- auto* shader = new RadialGradientShader(
- center,
- radius,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- static_cast<SkTileMode>(tileMode),
- sGradientShaderFlags,
- matrix
- );
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////
@@ -186,74 +174,66 @@ static jlong SweepGradient_create(JNIEnv* env, jobject, jlong matrixPtr, jfloat
#error Need to convert float array to SkScalar array before calling the following function.
#endif
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = SkGradientShader::MakeSweep(x, y, &colors[0],
+ GraphicsJNI::getNativeColorSpace(colorSpaceHandle), pos, colors.size(),
+ sGradientShaderFlags, nullptr);
+ ThrowIAE_IfNull(env, shader);
- auto* shader = new SweepGradientShader(
- x,
- y,
- colors,
- GraphicsJNI::getNativeColorSpace(colorSpaceHandle),
- pos,
- sGradientShaderFlags,
- matrix
- );
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ if (matrix) {
+ shader = shader->makeWithLocalMatrix(*matrix);
+ }
- return reinterpret_cast<jlong>(shader);
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong ComposeShader_create(JNIEnv* env, jobject o, jlong matrixPtr,
jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle) {
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* shaderA = reinterpret_cast<Shader*>(shaderAHandle);
- auto* shaderB = reinterpret_cast<Shader*>(shaderBHandle);
-
- auto mode = static_cast<SkBlendMode>(xfermodeHandle);
-
- auto* composeShader = new ComposeShader(
- *shaderA,
- *shaderB,
- mode,
- matrix
- );
-
- return reinterpret_cast<jlong>(composeShader);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
-static jlong BlurShader_create(JNIEnv* env , jobject o, jlong matrixPtr, jfloat sigmaX,
- jfloat sigmaY, jlong shaderHandle) {
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
- auto* inputShader = reinterpret_cast<Shader*>(shaderHandle);
-
- auto* blurShader = new BlurShader(
- sigmaX,
- sigmaY,
- inputShader,
- matrix
- );
- return reinterpret_cast<jlong>(blurShader);
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
+ SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
+ SkBlendMode mode = static_cast<SkBlendMode>(xfermodeHandle);
+ sk_sp<SkShader> baseShader(SkShaders::Blend(mode,
+ sk_ref_sp(shaderA), sk_ref_sp(shaderB)));
+
+ SkShader* shader;
+
+ if (matrix) {
+ shader = baseShader->makeWithLocalMatrix(*matrix).release();
+ } else {
+ shader = baseShader.release();
+ }
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
static jlong RuntimeShader_create(JNIEnv* env, jobject, jlong shaderFactory, jlong matrixPtr,
- jbyteArray inputs, jlong colorSpaceHandle, jboolean isOpaque) {
- auto* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
+ jbyteArray inputs, jlongArray inputShaders, jlong colorSpaceHandle, jboolean isOpaque) {
+ SkRuntimeEffect* effect = reinterpret_cast<SkRuntimeEffect*>(shaderFactory);
AutoJavaByteArray arInputs(env, inputs);
- auto data = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
- auto* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ std::vector<sk_sp<SkShader>> shaderVector;
+ if (inputShaders) {
+ jsize shaderCount = env->GetArrayLength(inputShaders);
+ shaderVector.resize(shaderCount);
+ jlong* arrayPtr = env->GetLongArrayElements(inputShaders, NULL);
+ for (int i = 0; i < shaderCount; i++) {
+ shaderVector[i] = sk_ref_sp(reinterpret_cast<SkShader*>(arrayPtr[i]));
+ }
+ env->ReleaseLongArrayElements(inputShaders, arrayPtr, 0);
+ }
- auto* shader = new RuntimeShader(
- *effect,
- std::move(data),
- isOpaque == JNI_TRUE,
- matrix
- );
- return reinterpret_cast<jlong>(shader);
+ sk_sp<SkData> fData;
+ fData = SkData::MakeWithCopy(arInputs.ptr(), arInputs.length());
+ const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+ sk_sp<SkShader> shader = effect->makeShader(fData, shaderVector.data(), shaderVector.size(),
+ matrix, isOpaque == JNI_TRUE);
+ ThrowIAE_IfNull(env, shader);
+
+ return reinterpret_cast<jlong>(shader.release());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -271,8 +251,12 @@ static jlong RuntimeShader_createShaderFactory(JNIEnv* env, jobject, jstring sks
///////////////////////////////////////////////////////////////////////////////////////////////
+static void Effect_safeUnref(SkRuntimeEffect* effect) {
+ SkSafeUnref(effect);
+}
+
static jlong RuntimeShader_getNativeFinalizer(JNIEnv*, jobject) {
- return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Effect_safeUnref));
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -290,10 +274,6 @@ static const JNINativeMethod gBitmapShaderMethods[] = {
{ "nativeCreate", "(JJII)J", (void*)BitmapShader_constructor },
};
-static const JNINativeMethod gBlurShaderMethods[] = {
- { "nativeCreate", "(JFFJ)J", (void*)BlurShader_create }
-};
-
static const JNINativeMethod gLinearGradientMethods[] = {
{ "nativeCreate", "(JFFFF[J[FIJ)J", (void*)LinearGradient_create },
};
@@ -312,7 +292,7 @@ static const JNINativeMethod gComposeShaderMethods[] = {
static const JNINativeMethod gRuntimeShaderMethods[] = {
{ "nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer },
- { "nativeCreate", "(JJ[BJZ)J", (void*)RuntimeShader_create },
+ { "nativeCreate", "(JJ[B[JJZ)J", (void*)RuntimeShader_create },
{ "nativeCreateShaderFactory", "(Ljava/lang/String;)J",
(void*)RuntimeShader_createShaderFactory },
};
@@ -325,8 +305,6 @@ int register_android_graphics_Shader(JNIEnv* env)
NELEM(gShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods,
NELEM(gBitmapShaderMethods));
- android::RegisterMethodsOrDie(env, "android/graphics/BlurShader", gBlurShaderMethods,
- NELEM(gBlurShaderMethods));
android::RegisterMethodsOrDie(env, "android/graphics/LinearGradient", gLinearGradientMethods,
NELEM(gLinearGradientMethods));
android::RegisterMethodsOrDie(env, "android/graphics/RadialGradient", gRadialGradientMethods,
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index b6c6cd0b5c1c..c04340c36511 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -30,6 +30,7 @@
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
+#include "FontUtils.h"
#include "Bitmap.h"
#include "SkGraphics.h"
#include "SkRegion.h"
@@ -540,6 +541,21 @@ static void drawBitmapMesh(JNIEnv* env, jobject, jlong canvasHandle, jlong bitma
colorA.ptr() + colorIndex, paint);
}
+static void drawGlyphs(JNIEnv* env, jobject, jlong canvasHandle, jintArray glyphIds,
+ jfloatArray positions, jint glyphOffset, jint positionOffset,
+ jint glyphCount, jlong fontHandle, jlong paintHandle) {
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
+ AutoJavaIntArray glyphIdArray(env, glyphIds);
+ AutoJavaFloatArray positionArray(env, positions);
+ get_canvas(canvasHandle)->drawGlyphs(
+ *font->font.get(),
+ glyphIdArray.ptr() + glyphOffset,
+ positionArray.ptr() + positionOffset,
+ glyphCount,
+ *paint);
+}
+
static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
jint index, jint count, jfloat x, jfloat y, jint bidiFlags,
jlong paintHandle) {
@@ -719,6 +735,7 @@ static const JNINativeMethod gDrawMethods[] = {
{"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
{"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
{"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
+ {"nDrawGlyphs", "(J[I[FIIIJJ)V", (void*)CanvasJNI::drawGlyphs},
{"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
{"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
{"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index c89463bf3ab4..a146b64e29cc 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -514,7 +514,8 @@ static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(
proxy.setLightGeometry((Vector3){0, 0, 0}, 0);
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
UiFrameInfoBuilder(proxy.frameInfo())
- .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID)
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID,
+ std::numeric_limits<int64_t>::max())
.addFlag(FrameInfoFlags::SurfaceCanvas);
proxy.syncAndDrawFrame();
}
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 85c802b40459..4b4aa92b97b7 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -215,6 +215,12 @@ static jboolean android_view_RenderNode_setAlpha(CRITICAL_JNI_PARAMS_COMMA jlong
return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA);
}
+static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+ jlong renderEffectPtr) {
+ SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr);
+ return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC);
+}
+
static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
bool hasOverlappingRendering) {
return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering,
@@ -690,6 +696,7 @@ static const JNINativeMethod gMethods[] = {
{ "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
{ "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha },
+ { "nSetRenderEffect", "(JJ)V", (void*) android_view_RenderNode_setRenderEffect },
{ "nSetHasOverlappingRendering", "(JZ)Z",
(void*) android_view_RenderNode_setHasOverlappingRendering },
{ "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint },
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index a1adcb30e80d..9cffceb308c8 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -143,13 +143,13 @@ static void updateFullPathPropertiesAndStrokeStyles(JNIEnv*, jobject, jlong full
static void updateFullPathFillGradient(JNIEnv*, jobject, jlong pathPtr, jlong fillGradientPtr) {
VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
- auto* fillShader = reinterpret_cast<Shader*>(fillGradientPtr);
+ SkShader* fillShader = reinterpret_cast<SkShader*>(fillGradientPtr);
path->mutateStagingProperties()->setFillGradient(fillShader);
}
static void updateFullPathStrokeGradient(JNIEnv*, jobject, jlong pathPtr, jlong strokeGradientPtr) {
VectorDrawable::FullPath* path = reinterpret_cast<VectorDrawable::FullPath*>(pathPtr);
- auto* strokeShader = reinterpret_cast<Shader*>(strokeGradientPtr);
+ SkShader* strokeShader = reinterpret_cast<SkShader*>(strokeGradientPtr);
path->mutateStagingProperties()->setStrokeGradient(strokeShader);
}
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 996cdceed8a7..aeb096df141a 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -18,6 +18,8 @@
#define LOG_TAG "Minikin"
#include "SkData.h"
+#include "SkFont.h"
+#include "SkFontMetrics.h"
#include "SkFontMgr.h"
#include "SkRefCnt.h"
#include "SkTypeface.h"
@@ -27,6 +29,7 @@
#include "FontUtils.h"
#include <hwui/MinikinSkia.h>
+#include <hwui/Paint.h>
#include <hwui/Typeface.h>
#include <minikin/FontFamily.h>
#include <ui/FatVector.h>
@@ -115,11 +118,43 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo
std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize,
std::string_view(fontPath.c_str(), fontPath.size()),
ttcIndex, builder->axes);
- minikin::Font font = minikin::Font::Builder(minikinFont).setWeight(weight)
+ std::shared_ptr<minikin::Font> font = minikin::Font::Builder(minikinFont).setWeight(weight)
.setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build();
return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
}
+// Fast Native
+static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
+ jint weight, jboolean italic, jint ttcIndex) {
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
+
+ // Reconstruct SkTypeface with different arguments from existing SkTypeface.
+ FatVector<SkFontArguments::VariationPosition::Coordinate, 2> skVariation;
+ for (const auto& axis : builder->axes) {
+ skVariation.push_back({axis.axisTag, axis.value});
+ }
+ SkFontArguments args;
+ args.setCollectionIndex(ttcIndex);
+ args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
+
+ sk_sp<SkTypeface> newTypeface = minikinSkia->GetSkTypeface()->makeClone(args);
+
+ std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
+ std::move(newTypeface),
+ minikinSkia->GetFontData(),
+ minikinSkia->GetFontSize(),
+ minikinSkia->getFilePath(),
+ minikinSkia->GetFontIndex(),
+ builder->axes);
+ std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
+ .setWeight(weight)
+ .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
+ .build();
+ return reinterpret_cast<jlong>(new FontWrapper(std::move(newFont)));
+}
+
// Critical Native
static jlong Font_Builder_getReleaseNativeFont(CRITICAL_JNI_PARAMS) {
return reinterpret_cast<jlong>(releaseFont);
@@ -127,16 +162,146 @@ static jlong Font_Builder_getReleaseNativeFont(CRITICAL_JNI_PARAMS) {
///////////////////////////////////////////////////////////////////////////////
+// Fast Native
+static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
+ jlong paintHandle, jobject rect) {
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+
+ SkFont* skFont = &paint->getSkFont();
+ // We don't use populateSkFont since it is designed to be used for layout result with addressing
+ // auto fake-bolding.
+ skFont->setTypeface(minikinSkia->RefSkTypeface());
+
+ uint16_t glyph16 = glyphId;
+ SkRect skBounds;
+ SkScalar skWidth;
+ skFont->getWidthsBounds(&glyph16, 1, &skWidth, &skBounds, nullptr);
+ GraphicsJNI::rect_to_jrectf(skBounds, env, rect);
+ return SkScalarToFloat(skWidth);
+}
+
+// Fast Native
+static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
+ jobject metricsObj) {
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+ Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+
+ SkFont* skFont = &paint->getSkFont();
+ // We don't use populateSkFont since it is designed to be used for layout result with addressing
+ // auto fake-bolding.
+ skFont->setTypeface(minikinSkia->RefSkTypeface());
+
+ SkFontMetrics metrics;
+ SkScalar spacing = skFont->getMetrics(&metrics);
+ GraphicsJNI::set_metrics(env, metricsObj, metrics);
+ return spacing;
+}
+
+// Critical Native
+static jlong Font_getFontInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+
+ uint64_t result = font->style().weight();
+ result |= font->style().slant() == minikin::FontStyle::Slant::ITALIC ? 0x10000 : 0x00000;
+ result |= ((static_cast<uint64_t>(minikinSkia->GetFontIndex())) << 32);
+ result |= ((static_cast<uint64_t>(minikinSkia->GetAxes().size())) << 48);
+ return result;
+}
+
+// Critical Native
+static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle, jint index) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const minikin::FontVariation& var = minikinSkia->GetAxes().at(index);
+ uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
+ return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
+}
+
+// FastNative
+static jstring Font_getFontPath(JNIEnv* env, jobject, jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->typeface().get());
+ const std::string& filePath = minikinSkia->getFilePath();
+ if (filePath.empty()) {
+ return nullptr;
+ }
+ return env->NewStringUTF(filePath.c_str());
+}
+
+// Critical Native
+static jlong Font_getNativeFontPtr(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+ FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
+ return reinterpret_cast<jlong>(font->font.get());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct FontBufferWrapper {
+ FontBufferWrapper(const std::shared_ptr<minikin::MinikinFont>& font) : minikinFont(font) {}
+ // MinikinFont holds a shared pointer of SkTypeface which has reference to font data.
+ std::shared_ptr<minikin::MinikinFont> minikinFont;
+};
+
+static void unrefBuffer(jlong nativePtr) {
+ FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr);
+ delete wrapper;
+}
+
+// Critical Native
+static jlong FontBufferHelper_refFontBuffer(CRITICAL_JNI_PARAMS_COMMA jlong fontHandle) {
+ const minikin::Font* font = reinterpret_cast<minikin::Font*>(fontHandle);
+ return reinterpret_cast<jlong>(new FontBufferWrapper(font->typeface()));
+}
+
+// Fast Native
+static jobject FontBufferHelper_wrapByteBuffer(JNIEnv* env, jobject, jlong nativePtr) {
+ FontBufferWrapper* wrapper = reinterpret_cast<FontBufferWrapper*>(nativePtr);
+ return env->NewDirectByteBuffer(
+ const_cast<void*>(wrapper->minikinFont->GetFontData()),
+ wrapper->minikinFont->GetFontSize());
+}
+
+// Critical Native
+static jlong FontBufferHelper_getReleaseFunc(CRITICAL_JNI_PARAMS) {
+ return reinterpret_cast<jlong>(unrefBuffer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
static const JNINativeMethod gFontBuilderMethods[] = {
{ "nInitBuilder", "()J", (void*) Font_Builder_initBuilder },
{ "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis },
{ "nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;IZI)J", (void*) Font_Builder_build },
+ { "nClone", "(JJIZI)J", (void*) Font_Builder_clone },
{ "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont },
};
+static const JNINativeMethod gFontMethods[] = {
+ { "nGetGlyphBounds", "(JIJLandroid/graphics/RectF;)F", (void*) Font_getGlyphBounds },
+ { "nGetFontMetrics", "(JJLandroid/graphics/Paint$FontMetrics;)F", (void*) Font_getFontMetrics },
+ { "nGetFontInfo", "(J)J", (void*) Font_getFontInfo },
+ { "nGetAxisInfo", "(JI)J", (void*) Font_getAxisInfo },
+ { "nGetFontPath", "(J)Ljava/lang/String;", (void*) Font_getFontPath },
+ { "nGetNativeFontPtr", "(J)J", (void*) Font_getNativeFontPtr },
+};
+
+static const JNINativeMethod gFontBufferHelperMethods[] = {
+ { "nRefFontBuffer", "(J)J", (void*) FontBufferHelper_refFontBuffer },
+ { "nWrapByteBuffer", "(J)Ljava/nio/ByteBuffer;", (void*) FontBufferHelper_wrapByteBuffer },
+ { "nGetReleaseFunc", "()J", (void*) FontBufferHelper_getReleaseFunc },
+};
+
int register_android_graphics_fonts_Font(JNIEnv* env) {
return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods,
- NELEM(gFontBuilderMethods));
+ NELEM(gFontBuilderMethods)) +
+ RegisterMethodsOrDie(env, "android/graphics/fonts/Font", gFontMethods,
+ NELEM(gFontMethods)) +
+ RegisterMethodsOrDie(env, "android/graphics/fonts/NativeFontBufferHelper",
+ gFontBufferHelperMethods, NELEM(gFontBufferHelperMethods));
}
}
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index df619d9f1406..37e52766f2ef 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -30,7 +30,7 @@
namespace android {
struct NativeFamilyBuilder {
- std::vector<minikin::Font> fonts;
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
};
static inline NativeFamilyBuilder* toBuilder(jlong ptr) {
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
new file mode 100644
index 000000000000..9d9e91f19851
--- /dev/null
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TextShaper"
+
+#include "graphics_jni_helpers.h"
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <set>
+#include <algorithm>
+
+#include "SkPaint.h"
+#include "SkTypeface.h"
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <minikin/MinikinPaint.h>
+#include <minikin/MinikinFont.h>
+
+namespace android {
+
+struct LayoutWrapper {
+ LayoutWrapper(minikin::Layout&& layout, float ascent, float descent)
+ : layout(std::move(layout)), ascent(ascent), descent(descent) {}
+ minikin::Layout layout;
+ float ascent;
+ float descent;
+};
+
+static void releaseLayout(jlong ptr) {
+ delete reinterpret_cast<LayoutWrapper*>(ptr);
+}
+
+static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int count,
+ int contextStart, int contextCount, minikin::Bidi bidiFlags,
+ const Paint& paint, const Typeface* typeface) {
+
+ minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(&paint, typeface);
+
+ minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface,
+ text, textSize, start, count, contextStart, contextCount, nullptr);
+
+ std::set<const minikin::Font*> seenFonts;
+ float overallAscent = 0;
+ float overallDescent = 0;
+ for (int i = 0; i < layout.nGlyphs(); ++i) {
+ const minikin::Font* font = layout.getFont(i);
+ if (seenFonts.find(font) != seenFonts.end()) continue;
+ minikin::MinikinExtent extent = {};
+ font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+ overallAscent = std::min(overallAscent, extent.ascent);
+ overallDescent = std::max(overallDescent, extent.descent);
+ }
+
+ std::unique_ptr<LayoutWrapper> ptr = std::make_unique<LayoutWrapper>(
+ std::move(layout), overallAscent, overallDescent
+ );
+
+ return reinterpret_cast<jlong>(ptr.release());
+}
+
+static jlong TextShaper_shapeTextRunChars(JNIEnv *env, jobject, jcharArray charArray,
+ jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl,
+ jlong paintPtr) {
+ ScopedCharArrayRO text(env, charArray);
+ Paint* paint = reinterpret_cast<Paint*>(paintPtr);
+ const Typeface* typeface = paint->getAndroidTypeface();
+ const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ return shapeTextRun(
+ text.get(), text.size(),
+ start, count,
+ contextStart, contextCount,
+ bidiFlags,
+ *paint, typeface);
+
+}
+
+static jlong TextShaper_shapeTextRunString(JNIEnv *env, jobject, jstring string,
+ jint start, jint count, jint contextStart, jint contextCount, jboolean isRtl,
+ jlong paintPtr) {
+ ScopedStringChars text(env, string);
+ Paint* paint = reinterpret_cast<Paint*>(paintPtr);
+ const Typeface* typeface = paint->getAndroidTypeface();
+ const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+ return shapeTextRun(
+ text.get(), text.size(),
+ start, count,
+ contextStart, contextCount,
+ bidiFlags,
+ *paint, typeface);
+}
+
+// CriticalNative
+static jint TextShaper_Result_getGlyphCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.nGlyphs();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getTotalAdvance(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getAdvance();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getAscent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->ascent;
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getDescent(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->descent;
+}
+
+// CriticalNative
+static jint TextShaper_Result_getGlyphId(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getGlyphId(i);
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getX(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getX(i);
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return layout->layout.getY(i);
+}
+
+// CriticalNative
+static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+ const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+ return reinterpret_cast<jlong>(layout->layout.getFont(i));
+}
+
+// CriticalNative
+static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) {
+ return reinterpret_cast<jlong>(releaseLayout);
+}
+
+static const JNINativeMethod gMethods[] = {
+ // Fast Natives
+ {"nativeShapeTextRun", "("
+ "[C" // text
+ "I" // start
+ "I" // count
+ "I" // contextStart
+ "I" // contextCount
+ "Z" // isRtl
+ "J)" // paint
+ "J", // LayoutPtr
+ (void*) TextShaper_shapeTextRunChars},
+
+ {"nativeShapeTextRun", "("
+ "Ljava/lang/String;" // text
+ "I" // start
+ "I" // count
+ "I" // contextStart
+ "I" // contextCount
+ "Z" // isRtl
+ "J)" // paint
+ "J", // LayoutPtr
+ (void*) TextShaper_shapeTextRunString},
+
+};
+
+static const JNINativeMethod gResultMethods[] = {
+ { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
+ { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
+ { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
+ { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
+ { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
+ { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
+ { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
+ { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
+ { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+};
+
+int register_android_graphics_text_TextShaper(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/text/TextShaper", gMethods,
+ NELEM(gMethods))
+ + RegisterMethodsOrDie(env, "android/graphics/text/PositionedGlyphs",
+ gResultMethods, NELEM(gResultMethods));
+}
+
+}
+
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 00ceb2d84f9e..1473b3e5abb7 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -172,10 +172,12 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip
SkPaint* paint) {
paint->setFilterQuality(kLow_SkFilterQuality);
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
- properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) {
+ properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
+ properties.getImageFilter() != nullptr) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
+ paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
return true;
}
return false;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 66aa8c203799..3baff7ea8f90 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -15,7 +15,7 @@
*/
#include "ShaderCache.h"
-#include <GrContext.h>
+#include <GrDirectContext.h>
#include <log/log.h>
#include <openssl/sha.h>
#include <algorithm>
@@ -206,7 +206,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) {
}
}
-void ShaderCache::onVkFrameFlushed(GrContext* context) {
+void ShaderCache::onVkFrameFlushed(GrDirectContext* context) {
{
std::lock_guard<std::mutex> lock(mMutex);
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 5b8e668a56f4..4dcc9fb49802 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -80,7 +80,7 @@ public:
* Pipeline cache is saved on disk only if the size of the data has changed or there was
* a new shader compiled.
*/
- void onVkFrameFlushed(GrContext* context);
+ void onVkFrameFlushed(GrDirectContext* context);
private:
// Creation and (the lack of) destruction is handled internally.
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index b57dee4897ac..85924c5e8939 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -61,7 +61,7 @@ CacheManager::CacheManager()
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
}
-void CacheManager::reset(sk_sp<GrContext> context) {
+void CacheManager::reset(sk_sp<GrDirectContext> context) {
if (context != mGrContext) {
destroy();
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index b009cc4f48f2..0a6b8dc26cc3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -18,7 +18,7 @@
#define CACHEMANAGER_H
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
-#include <GrContext.h>
+#include <GrDirectContext.h>
#endif
#include <SkSurface.h>
#include <utils/String8.h>
@@ -58,13 +58,13 @@ private:
explicit CacheManager();
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
- void reset(sk_sp<GrContext> grContext);
+ void reset(sk_sp<GrDirectContext> grContext);
#endif
void destroy();
const size_t mMaxSurfaceArea;
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
- sk_sp<GrContext> mGrContext;
+ sk_sp<GrDirectContext> mGrContext;
#endif
const size_t mMaxResourceBytes;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5f46ddf55b4e..c71bed8554a0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,7 +139,7 @@ void CanvasContext::destroy() {
mAnimationContext->destroy();
}
-static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) {
+static void setBufferCount(ANativeWindow* window) {
int query_value;
int err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
if (err != 0 || query_value < 0) {
@@ -148,7 +148,9 @@ static void setBufferCount(ANativeWindow* window, uint32_t extraBuffers) {
}
auto min_undequeued_buffers = static_cast<uint32_t>(query_value);
- int bufferCount = min_undequeued_buffers + 2 + extraBuffers;
+ // We only need to set min_undequeued + 2 because the renderahead amount was already factored into the
+ // query for min_undequeued
+ int bufferCount = min_undequeued_buffers + 2;
native_window_set_buffer_count(window, bufferCount);
}
@@ -182,7 +184,8 @@ void CanvasContext::setupPipelineSurface() {
mNativeSurface ? mNativeSurface->getNativeWindow() : nullptr, mSwapBehavior);
if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) {
- setBufferCount(mNativeSurface->getNativeWindow(), mRenderAheadCapacity);
+ setBufferCount(mNativeSurface->getNativeWindow());
+
}
mFrameNumber = -1;
@@ -641,10 +644,11 @@ void CanvasContext::prepareAndDraw(RenderNode* node) {
nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+ int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
UiFrameInfoBuilder(frameInfo)
.addFlag(FrameInfoFlags::RTAnimation)
- .setVsync(vsync, vsync, vsyncId);
+ .setVsync(vsync, vsync, vsyncId, frameDeadline);
TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1ea595d6a30a..c9146b2fc2d1 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -130,7 +130,8 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
- mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId);
+ int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
+ mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId, frameDeadline);
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index a04738d6a6f0..aceb5a528fc8 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -27,7 +27,7 @@
#include <SkRect.h>
#include <utils/RefBase.h>
-class GrContext;
+class GrDirectContext;
struct ANativeWindow;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9371656eda7f..a101d46f6da0 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -52,8 +52,9 @@ static JVMAttachHook gOnStartHook = nullptr;
void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
RenderThread* rt = reinterpret_cast<RenderThread*>(data);
int64_t vsyncId = AChoreographer_getVsyncId(rt->mChoreographer);
+ int64_t frameDeadline = AChoreographer_getFrameDeadline(rt->mChoreographer);
rt->mVsyncRequested = false;
- if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId) &&
+ if (rt->timeLord().vsyncReceived(frameTimeNanos, frameTimeNanos, vsyncId, frameDeadline) &&
!rt->mFrameCallbackTaskPending) {
ATRACE_NAME("queue mFrameCallbackTask");
rt->mFrameCallbackTaskPending = true;
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index 7dc36c449568..abb633028363 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include "TimeLord.h"
+#include <limits>
namespace android {
namespace uirenderer {
@@ -22,12 +23,15 @@ namespace renderthread {
TimeLord::TimeLord() : mFrameIntervalNanos(milliseconds_to_nanoseconds(16)),
mFrameTimeNanos(0),
mFrameIntendedTimeNanos(0),
- mFrameVsyncId(-1) {}
+ mFrameVsyncId(-1),
+ mFrameDeadline(std::numeric_limits<int64_t>::max()){}
-bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId) {
+bool TimeLord::vsyncReceived(nsecs_t vsync, nsecs_t intendedVsync, int64_t vsyncId,
+ int64_t frameDeadline) {
if (intendedVsync > mFrameIntendedTimeNanos) {
mFrameIntendedTimeNanos = intendedVsync;
mFrameVsyncId = vsyncId;
+ mFrameDeadline = frameDeadline;
}
if (vsync > mFrameTimeNanos) {
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
index 23c1e51c427a..fa05c030fa0f 100644
--- a/libs/hwui/renderthread/TimeLord.h
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -32,10 +32,12 @@ public:
nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
// returns true if the vsync is newer, false if it was rejected for staleness
- bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId);
+ bool vsyncReceived(nsecs_t vsync, nsecs_t indendedVsync, int64_t vsyncId,
+ int64_t frameDeadline);
nsecs_t latestVsync() { return mFrameTimeNanos; }
nsecs_t computeFrameTimeNanos();
int64_t lastVsyncId() const { return mFrameVsyncId; }
+ int64_t lastFrameDeadline() const { return mFrameDeadline; }
private:
friend class RenderThread;
@@ -47,6 +49,7 @@ private:
nsecs_t mFrameTimeNanos;
nsecs_t mFrameIntendedTimeNanos;
int64_t mFrameVsyncId;
+ int64_t mFrameDeadline;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 1da09b454da7..acf4931d6144 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
#include "VulkanSurface.h"
+#include <GrDirectContext.h>
#include <SkSurface.h>
#include <algorithm>
@@ -117,7 +118,7 @@ static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode,
SkColorType colorType, sk_sp<SkColorSpace> colorSpace,
- GrContext* grContext, const VulkanManager& vkManager,
+ GrDirectContext* grContext, const VulkanManager& vkManager,
uint32_t extraBuffers) {
// Connect and set native window to default configurations.
if (!ConnectAndSetWindowDefaults(window)) {
@@ -310,7 +311,7 @@ bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& window
}
VulkanSurface::VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo,
- GrContext* grContext)
+ GrDirectContext* grContext)
: mNativeWindow(window), mWindowInfo(windowInfo), mGrContext(grContext) {}
VulkanSurface::~VulkanSurface() {
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index 40a44b11c0bc..409921bdfdd7 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -35,7 +35,7 @@ class VulkanManager;
class VulkanSurface {
public:
static VulkanSurface* Create(ANativeWindow* window, ColorMode colorMode, SkColorType colorType,
- sk_sp<SkColorSpace> colorSpace, GrContext* grContext,
+ sk_sp<SkColorSpace> colorSpace, GrDirectContext* grContext,
const VulkanManager& vkManager, uint32_t extraBuffers);
~VulkanSurface();
@@ -101,7 +101,7 @@ private:
SkMatrix preTransform;
};
- VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrContext* grContext);
+ VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext);
static bool InitializeWindowInfoStruct(ANativeWindow* window, ColorMode colorMode,
SkColorType colorType, sk_sp<SkColorSpace> colorSpace,
const VulkanManager& vkManager, uint32_t extraBuffers,
@@ -119,7 +119,7 @@ private:
sp<ANativeWindow> mNativeWindow;
WindowInfo mWindowInfo;
- GrContext* mGrContext;
+ GrDirectContext* mGrContext;
uint32_t mPresentCount = 0;
NativeBufferInfo* mCurrentBufferInfo = nullptr;
diff --git a/libs/hwui/shader/BlurShader.cpp b/libs/hwui/shader/BlurShader.cpp
index fa10be100bca..2abd8714204b 100644
--- a/libs/hwui/shader/BlurShader.cpp
+++ b/libs/hwui/shader/BlurShader.cpp
@@ -20,13 +20,14 @@
#include "utils/Blur.h"
namespace android::uirenderer {
-BlurShader::BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix)
+BlurShader::BlurShader(float radiusX, float radiusY, Shader* inputShader, SkTileMode edgeTreatment,
+ const SkMatrix* matrix)
: Shader(matrix)
, skImageFilter(
SkImageFilters::Blur(
Blur::convertRadiusToSigma(radiusX),
Blur::convertRadiusToSigma(radiusY),
- SkTileMode::kClamp,
+ edgeTreatment,
inputShader ? inputShader->asSkImageFilter() : nullptr,
nullptr)
) { }
diff --git a/libs/hwui/shader/BlurShader.h b/libs/hwui/shader/BlurShader.h
index 9eb22bd11f4a..60a15898893e 100644
--- a/libs/hwui/shader/BlurShader.h
+++ b/libs/hwui/shader/BlurShader.h
@@ -30,8 +30,12 @@ public:
*
* This will blur the contents of the provided input shader if it is non-null, otherwise
* the source bitmap will be blurred instead.
+ *
+ * The edge treatment parameter determines how content near the edges of the source is to
+ * participate in the blur
*/
- BlurShader(float radiusX, float radiusY, Shader* inputShader, const SkMatrix* matrix);
+ BlurShader(float radiusX, float radiusY, Shader* inputShader, SkTileMode edgeTreatment,
+ const SkMatrix* matrix);
~BlurShader() override;
protected:
sk_sp<SkImageFilter> makeSkImageFilter() override;
diff --git a/libs/hwui/shader/ComposeShader.cpp b/libs/hwui/shader/ComposeShader.cpp
deleted file mode 100644
index 3765489e7431..000000000000
--- a/libs/hwui/shader/ComposeShader.cpp
+++ /dev/null
@@ -1,51 +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.
- */
-
-#include "ComposeShader.h"
-
-#include "SkImageFilters.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-ComposeShader::ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
- const SkMatrix* matrix)
- : Shader(matrix) {
- // If both Shaders can be represented as SkShaders then use those, if not
- // create an SkImageFilter from both Shaders and create the equivalent SkImageFilter
- sk_sp<SkShader> skShaderA = shaderA.asSkShader();
- sk_sp<SkShader> skShaderB = shaderB.asSkShader();
- if (skShaderA.get() && skShaderB.get()) {
- skShader = SkShaders::Blend(blendMode, skShaderA, skShaderB);
- skImageFilter = nullptr;
- } else {
- sk_sp<SkImageFilter> skImageFilterA = shaderA.asSkImageFilter();
- sk_sp<SkImageFilter> skImageFilterB = shaderB.asSkImageFilter();
- skShader = nullptr;
- skImageFilter = SkImageFilters::Xfermode(blendMode, skImageFilterA, skImageFilterB);
- }
-}
-
-sk_sp<SkShader> ComposeShader::makeSkShader() {
- return skShader;
-}
-
-sk_sp<SkImageFilter> ComposeShader::makeSkImageFilter() {
- return skImageFilter;
-}
-
-ComposeShader::~ComposeShader() {}
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/ComposeShader.h b/libs/hwui/shader/ComposeShader.h
deleted file mode 100644
index a246b520d46a..000000000000
--- a/libs/hwui/shader/ComposeShader.h
+++ /dev/null
@@ -1,43 +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.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that can composite 2 Shaders together with the specified blend mode.
- * This implementation can appropriately convert the composed result to either an SkShader or
- * SkImageFilter depending on the inputs
- */
-class ComposeShader : public Shader {
-public:
- ComposeShader(Shader& shaderA, Shader& shaderB, const SkBlendMode blendMode,
- const SkMatrix* matrix);
- ~ComposeShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
- sk_sp<SkImageFilter> makeSkImageFilter() override;
-
-private:
- sk_sp<SkShader> skShader;
- sk_sp<SkImageFilter> skImageFilter;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/LinearGradientShader.cpp b/libs/hwui/shader/LinearGradientShader.cpp
deleted file mode 100644
index 868fa44fb4b7..000000000000
--- a/libs/hwui/shader/LinearGradientShader.cpp
+++ /dev/null
@@ -1,39 +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.
- */
-
-#include "LinearGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-LinearGradientShader::LinearGradientShader(const SkPoint pts[2],
- const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeLinear(pts, colors.data(), colorspace, pos, colors.size(),
- tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> LinearGradientShader::makeSkShader() {
- return skShader;
-}
-
-LinearGradientShader::~LinearGradientShader() {}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/LinearGradientShader.h b/libs/hwui/shader/LinearGradientShader.h
deleted file mode 100644
index 596f4e009448..000000000000
--- a/libs/hwui/shader/LinearGradientShader.h
+++ /dev/null
@@ -1,42 +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.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp of colors to either as either SkShader or
- * SkImageFilter
- */
-class LinearGradientShader : public Shader {
-public:
- LinearGradientShader(const SkPoint pts[2], const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix);
- ~LinearGradientShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/RadialGradientShader.cpp b/libs/hwui/shader/RadialGradientShader.cpp
deleted file mode 100644
index 21ff56fee2f8..000000000000
--- a/libs/hwui/shader/RadialGradientShader.cpp
+++ /dev/null
@@ -1,38 +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.
- */
-#include "RadialGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-
-namespace android::uirenderer {
-
-RadialGradientShader::RadialGradientShader(const SkPoint& center, const float radius,
- const std::vector<SkColor4f>& colors,
- sk_sp<SkColorSpace> colorspace, const SkScalar pos[],
- const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeRadial(center, radius, colors.data(), colorspace, pos,
- colors.size(), tileMode, shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> RadialGradientShader::makeSkShader() {
- return skShader;
-}
-
-RadialGradientShader::~RadialGradientShader() {}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/RadialGradientShader.h b/libs/hwui/shader/RadialGradientShader.h
deleted file mode 100644
index 9a2ff139aedb..000000000000
--- a/libs/hwui/shader/RadialGradientShader.h
+++ /dev/null
@@ -1,42 +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.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp from the center outward to either as either
- * a SkShader or SkImageFilter
- */
-class RadialGradientShader : public Shader {
-public:
- RadialGradientShader(const SkPoint& center, const float radius,
- const std::vector<SkColor4f>& colors, sk_sp<SkColorSpace> colorSpace,
- const SkScalar pos[], const SkTileMode tileMode, const uint32_t shaderFlags,
- const SkMatrix* matrix);
- ~RadialGradientShader() override;
-
-protected:
- sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/RuntimeShader.cpp b/libs/hwui/shader/RuntimeShader.cpp
deleted file mode 100644
index dd0b6980841a..000000000000
--- a/libs/hwui/shader/RuntimeShader.cpp
+++ /dev/null
@@ -1,36 +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.
- */
-
-#include "RuntimeShader.h"
-
-#include "SkShader.h"
-#include "include/effects/SkRuntimeEffect.h"
-
-namespace android::uirenderer {
-
-RuntimeShader::RuntimeShader(SkRuntimeEffect& effect, sk_sp<SkData> data, bool isOpaque,
- const SkMatrix* matrix)
- : Shader(nullptr)
- , // Explicitly passing null as RuntimeShader is created with the
- // matrix directly
- skShader(effect.makeShader(std::move(data), nullptr, 0, matrix, isOpaque)) {}
-
-sk_sp<SkShader> RuntimeShader::makeSkShader() {
- return skShader;
-}
-
-RuntimeShader::~RuntimeShader() {}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/Shader.cpp b/libs/hwui/shader/Shader.cpp
deleted file mode 100644
index 45123dd55002..000000000000
--- a/libs/hwui/shader/Shader.cpp
+++ /dev/null
@@ -1,87 +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.
- */
-
-#include "Shader.h"
-
-#include "SkImageFilters.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-namespace android::uirenderer {
-
-Shader::Shader(const SkMatrix* matrix)
- : localMatrix(matrix ? *matrix : SkMatrix::I())
- , skShader(nullptr)
- , skImageFilter(nullptr) {}
-
-Shader::~Shader() {}
-
-sk_sp<SkShader> Shader::asSkShader() {
- // If we already have created a shader with these parameters just return the existing
- // shader we have already created
- if (!this->skShader.get()) {
- this->skShader = makeSkShader();
- if (this->skShader.get()) {
- if (!localMatrix.isIdentity()) {
- this->skShader = this->skShader->makeWithLocalMatrix(localMatrix);
- }
- }
- }
- return this->skShader;
-}
-
-/**
- * By default return null as we cannot convert all visual effects to SkShader instances
- */
-sk_sp<SkShader> Shader::makeSkShader() {
- return nullptr;
-}
-
-sk_sp<SkImageFilter> Shader::asSkImageFilter() {
- // If we already have created an ImageFilter with these parameters just return the existing
- // ImageFilter we have already created
- if (!this->skImageFilter.get()) {
- // Attempt to create an SkImageFilter from the current Shader implementation
- this->skImageFilter = makeSkImageFilter();
- if (this->skImageFilter) {
- if (!localMatrix.isIdentity()) {
- // If we have created an SkImageFilter and we have a transformation, wrap
- // the created SkImageFilter to apply the given matrix
- this->skImageFilter = SkImageFilters::MatrixTransform(
- localMatrix, kMedium_SkFilterQuality, this->skImageFilter);
- }
- } else {
- // Otherwise if no SkImageFilter implementation is provided, create one from
- // the result of asSkShader. Note the matrix is already applied to the shader in
- // this case so just convert it to an SkImageFilter using SkImageFilters::Paint
- SkPaint paint;
- paint.setShader(asSkShader());
- sk_sp<SkImageFilter> paintFilter = SkImageFilters::Paint(paint);
- this->skImageFilter = SkImageFilters::Xfermode(SkBlendMode::kDstIn,
- std::move(paintFilter));
- }
- }
- return this->skImageFilter;
-}
-
-/**
- * By default return null for subclasses to implement. If there is not a direct SkImageFilter
- * conversion
- */
-sk_sp<SkImageFilter> Shader::makeSkImageFilter() {
- return nullptr;
-}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/Shader.h b/libs/hwui/shader/Shader.h
deleted file mode 100644
index 6403e1147ded..000000000000
--- a/libs/hwui/shader/Shader.h
+++ /dev/null
@@ -1,84 +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.
- */
-
-#pragma once
-
-#include "SkImageFilter.h"
-#include "SkShader.h"
-#include "SkPaint.h"
-#include "SkRefCnt.h"
-
-class SkMatrix;
-
-namespace android::uirenderer {
-
-/**
- * Shader class that can optionally wrap an SkShader or SkImageFilter depending
- * on the implementation
- */
-class Shader: public SkRefCnt {
-public:
- /**
- * Creates a Shader instance with an optional transformation matrix. The transformation matrix
- * is copied internally and ownership is unchanged. It is the responsibility of the caller to
- * deallocate it appropriately.
- * @param matrix Optional matrix to transform the underlying SkShader or SkImageFilter
- */
- Shader(const SkMatrix* matrix);
- virtual ~Shader();
-
- /**
- * Create an SkShader from the current Shader instance or return a previously
- * created instance. This can be null if no SkShader could be created from this
- * Shader instance.
- */
- sk_sp<SkShader> asSkShader();
-
- /**
- * Create an SkImageFilter from the current Shader instance or return a previously
- * created instance. Unlike asSkShader, this method cannot return null.
- */
- sk_sp<SkImageFilter> asSkImageFilter();
-
-protected:
- /**
- * Create a new SkShader instance based on this Shader instance
- */
- virtual sk_sp<SkShader> makeSkShader();
-
- /**
- * Create a new SkImageFilter instance based on this Shader instance. If no SkImageFilter
- * can be created then return nullptr
- */
- virtual sk_sp<SkImageFilter> makeSkImageFilter();
-
-private:
- /**
- * Optional matrix transform
- */
- const SkMatrix localMatrix;
-
- /**
- * Cached SkShader instance to be returned on subsequent queries
- */
- sk_sp<SkShader> skShader;
-
- /**
- * Cached SkImageFilter instance to be returned on subsequent queries
- */
- sk_sp<SkImageFilter> skImageFilter;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/shader/SweepGradientShader.cpp b/libs/hwui/shader/SweepGradientShader.cpp
deleted file mode 100644
index 3b1f37f8b051..000000000000
--- a/libs/hwui/shader/SweepGradientShader.cpp
+++ /dev/null
@@ -1,38 +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.
- */
-
-#include "SweepGradientShader.h"
-
-#include <vector>
-
-#include "SkGradientShader.h"
-#include "SkImageFilters.h"
-
-namespace android::uirenderer {
-
-SweepGradientShader::SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
- const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
- const uint32_t shaderFlags, const SkMatrix* matrix)
- : Shader(matrix)
- , skShader(SkGradientShader::MakeSweep(x, y, colors.data(), colorspace, pos, colors.size(),
- shaderFlags, nullptr)) {}
-
-sk_sp<SkShader> SweepGradientShader::makeSkShader() {
- return skShader;
-}
-
-SweepGradientShader::~SweepGradientShader() {}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/shader/SweepGradientShader.h b/libs/hwui/shader/SweepGradientShader.h
deleted file mode 100644
index dad3ef0ffad4..000000000000
--- a/libs/hwui/shader/SweepGradientShader.h
+++ /dev/null
@@ -1,41 +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.
- */
-
-#pragma once
-
-#include "Shader.h"
-#include "SkShader.h"
-
-namespace android::uirenderer {
-
-/**
- * Shader implementation that renders a color ramp clockwise such that the start and end colors
- * are visible at 3 o'clock. This handles converting to either an SkShader or SkImageFilter
- */
-class SweepGradientShader : public Shader {
-public:
- SweepGradientShader(float x, float y, const std::vector<SkColor4f>& colors,
- const sk_sp<SkColorSpace>& colorspace, const SkScalar pos[],
- const uint32_t shaderFlags, const SkMatrix* matrix);
- virtual ~SweepGradientShader() override;
-
-protected:
- virtual sk_sp<SkShader> makeSkShader() override;
-
-private:
- sk_sp<SkShader> skShader;
-};
-} // namespace android::uirenderer
diff --git a/libs/hwui/tests/common/scenes/BitmapShaders.cpp b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
index e2c1651d823a..c4067af388e3 100644
--- a/libs/hwui/tests/common/scenes/BitmapShaders.cpp
+++ b/libs/hwui/tests/common/scenes/BitmapShaders.cpp
@@ -18,7 +18,6 @@
#include "hwui/Paint.h"
#include "TestSceneBase.h"
#include "tests/common/BitmapAllocationTestUtils.h"
-#include <shader/BitmapShader.h>
#include "utils/Color.h"
class BitmapShaders;
@@ -46,24 +45,15 @@ public:
});
Paint paint;
- sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
- hwuiBitmap->makeImage(),
- SkTileMode::kRepeat,
- SkTileMode::kRepeat,
- nullptr
- );
-
sk_sp<SkImage> image = hwuiBitmap->makeImage();
- paint.setShader(std::move(bitmapShader));
+ sk_sp<SkShader> repeatShader =
+ image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat);
+ paint.setShader(std::move(repeatShader));
canvas.drawRoundRect(0, 0, 500, 500, 50.0f, 50.0f, paint);
- sk_sp<BitmapShader> mirrorBitmapShader = sk_make_sp<BitmapShader>(
- image,
- SkTileMode::kMirror,
- SkTileMode::kMirror,
- nullptr
- );
- paint.setShader(std::move(mirrorBitmapShader));
+ sk_sp<SkShader> mirrorShader =
+ image->makeShader(SkTileMode::kMirror, SkTileMode::kMirror);
+ paint.setShader(std::move(mirrorShader));
canvas.drawRoundRect(0, 600, 500, 1100, 50.0f, 50.0f, paint);
}
diff --git a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
index d37bc3c7d37c..5886ea39acce 100644
--- a/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
+++ b/libs/hwui/tests/common/scenes/HwBitmapInCompositeShader.cpp
@@ -20,10 +20,6 @@
#include <SkGradientShader.h>
#include <SkImagePriv.h>
#include <ui/PixelFormat.h>
-#include <shader/BitmapShader.h>
-#include <shader/LinearGradientShader.h>
-#include <shader/RadialGradientShader.h>
-#include <shader/ComposeShader.h>
class HwBitmapInCompositeShader;
@@ -54,41 +50,20 @@ public:
pixels[4000 + 4 * i + 3] = 255;
}
buffer->unlock();
-
- sk_sp<BitmapShader> bitmapShader = sk_make_sp<BitmapShader>(
- Bitmap::createFrom(
- buffer->toAHardwareBuffer(),
- SkColorSpace::MakeSRGB()
- )->makeImage(),
- SkTileMode::kClamp,
- SkTileMode::kClamp,
- nullptr
- );
+ sk_sp<Bitmap> hardwareBitmap(Bitmap::createFrom(buffer->toAHardwareBuffer(),
+ SkColorSpace::MakeSRGB()));
+ sk_sp<SkShader> hardwareShader(createBitmapShader(*hardwareBitmap));
SkPoint center;
center.set(50, 50);
-
- std::vector<SkColor4f> vColors(2);
- vColors[0] = SkColors::kBlack;
- vColors[1] = SkColors::kWhite;
-
- sk_sp<RadialGradientShader> radialShader = sk_make_sp<RadialGradientShader>(
- center,
- 50,
- vColors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kRepeat,
- 0,
- nullptr
- );
-
- sk_sp<ComposeShader> compositeShader = sk_make_sp<ComposeShader>(
- *bitmapShader.get(),
- *radialShader.get(),
- SkBlendMode::kDstATop,
- nullptr
- );
+ SkColor colors[2];
+ colors[0] = Color::Black;
+ colors[1] = Color::White;
+ sk_sp<SkShader> gradientShader = SkGradientShader::MakeRadial(
+ center, 50, colors, nullptr, 2, SkTileMode::kRepeat);
+
+ sk_sp<SkShader> compositeShader(
+ SkShaders::Blend(SkBlendMode::kDstATop, hardwareShader, gradientShader));
Paint paint;
paint.setShader(std::move(compositeShader));
diff --git a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
index 76e39deedd9a..a9449b62a1f8 100644
--- a/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListOfFadedTextAnimation.cpp
@@ -17,8 +17,7 @@
#include "TestSceneBase.h"
#include "tests/common/TestListViewSceneBase.h"
#include "hwui/Paint.h"
-#include "SkColor.h"
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
class ListOfFadedTextAnimation;
@@ -43,26 +42,15 @@ class ListOfFadedTextAnimation : public TestListViewSceneBase {
pts[0].set(0, 0);
pts[1].set(0, 1);
+ SkColor colors[2] = {Color::Black, Color::Transparent};
+ sk_sp<SkShader> s(
+ SkGradientShader::MakeLinear(pts, colors, NULL, 2, SkTileMode::kClamp));
+
SkMatrix matrix;
matrix.setScale(1, length);
matrix.postRotate(-90);
-
- std::vector<SkColor4f> vColors(2);
- vColors[0] = SkColors::kBlack;
- vColors[1] = SkColors::kTransparent;
-
- sk_sp<LinearGradientShader> linearGradientShader = sk_make_sp<LinearGradientShader>(
- pts,
- vColors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kClamp,
- 0,
- &matrix
- );
-
Paint fadingPaint;
- fadingPaint.setShader(linearGradientShader);
+ fadingPaint.setShader(s->makeWithLocalMatrix(matrix));
fadingPaint.setBlendMode(SkBlendMode::kDstOut);
canvas.drawRect(0, 0, length, itemHeight, fadingPaint);
canvas.restore();
diff --git a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
index bdc157f85264..a0bc5aa245d5 100644
--- a/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleColorMatrixAnimation.cpp
@@ -17,7 +17,7 @@
#include "TestSceneBase.h"
#include <SkColorMatrixFilter.h>
-#include <shader/LinearGradientShader.h>
+#include <SkGradientShader.h>
class SimpleColorMatrixAnimation;
@@ -65,12 +65,9 @@ private:
// enough renderer might apply it directly to the paint color)
float pos[] = {0, 1};
SkPoint pts[] = {SkPoint::Make(0, 0), SkPoint::Make(width, height)};
- std::vector<SkColor4f> colors(2);
- colors[0] = SkColor4f::FromColor(Color::DeepPurple_500);
- colors[1] = SkColor4f::FromColor(Color::DeepOrange_500);
- paint.setShader(sk_make_sp<LinearGradientShader>(
- pts, colors, SkColorSpace::MakeSRGB(), pos, SkTileMode::kClamp,
- 0, nullptr));
+ SkColor colors[2] = {Color::DeepPurple_500, Color::DeepOrange_500};
+ paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos, 2,
+ SkTileMode::kClamp));
// overdraw several times to emphasize shader cost
for (int i = 0; i < 10; i++) {
diff --git a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
index 9a15c9d370a4..57a260c8d234 100644
--- a/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/SimpleGradientAnimation.cpp
@@ -17,7 +17,6 @@
#include "TestSceneBase.h"
#include <SkGradientShader.h>
-#include <shader/LinearGradientShader.h>
class SimpleGradientAnimation;
@@ -56,24 +55,9 @@ private:
// overdraw several times to emphasize shader cost
for (int i = 0; i < 10; i++) {
// use i%2 start position to pick 2 color combo with black in it
- std::vector<SkColor4f> vColors(2);
- vColors[0] = ((i % 2) == 0) ?
- SkColor4f::FromColor(Color::Transparent) :
- SkColor4f::FromColor(Color::Black);
- vColors[1] = (((i + 1) % 2) == 0) ?
- SkColor4f::FromColor(Color::Black) :
- SkColor4f::FromColor(Color::Cyan_500);
-
- sk_sp<LinearGradientShader> gradient = sk_make_sp<LinearGradientShader>(
- pts,
- vColors,
- SkColorSpace::MakeSRGB(),
- pos,
- SkTileMode::kClamp,
- 0,
- nullptr
- );
- paint.setShader(gradient);
+ SkColor colors[3] = {Color::Transparent, Color::Black, Color::Cyan_500};
+ paint.setShader(SkGradientShader::MakeLinear(pts, colors + (i % 2), pos, 2,
+ SkTileMode::kClamp));
canvas.drawRect(i, i, width, height, paint);
}
});
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index ed89c590be10..eda5d2266dcf 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -146,7 +146,7 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
testContext.waitForVsync();
nsecs_t vsync = systemTime(SYSTEM_TIME_MONOTONIC);
UiFrameInfoBuilder(proxy->frameInfo())
- .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID, std::numeric_limits<int64_t>::max());
proxy->syncAndDrawFrame();
}
@@ -167,7 +167,7 @@ void run(const TestScene::Info& info, const TestScene::Options& opts,
{
ATRACE_NAME("UI-Draw Frame");
UiFrameInfoBuilder(proxy->frameInfo())
- .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID);
+ .setVsync(vsync, vsync, UiFrameInfoBuilder::INVALID_VSYNC_ID, std::numeric_limits<int64_t>::max());
scene->doFrame(i);
proxy->syncAndDrawFrame();
}
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 1a09b1c52d8a..5d2aa2ff83c9 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -31,10 +31,12 @@ using namespace android;
namespace {
-constexpr char kRobotoRegular[] = "/system/fonts/Roboto-Regular.ttf";
-constexpr char kRobotoBold[] = "/system/fonts/Roboto-Bold.ttf";
-constexpr char kRobotoItalic[] = "/system/fonts/Roboto-Italic.ttf";
-constexpr char kRobotoBoldItalic[] = "/system/fonts/Roboto-BoldItalic.ttf";
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+
+constexpr char kRegularFont[] = "/system/fonts/NotoSerif-Regular.ttf";
+constexpr char kBoldFont[] = "/system/fonts/NotoSerif-Bold.ttf";
+constexpr char kItalicFont[] = "/system/fonts/NotoSerif-Italic.ttf";
+constexpr char kBoldItalicFont[] = "/system/fonts/NotoSerif-BoldItalic.ttf";
void unmap(const void* ptr, void* context) {
void* p = const_cast<void*>(ptr);
@@ -57,7 +59,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
std::shared_ptr<minikin::MinikinFont> font =
std::make_shared<MinikinFontSkia>(std::move(typeface), data, st.st_size, fileName, 0,
std::vector<minikin::FontVariation>());
- std::vector<minikin::Font> fonts;
+ std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
return std::make_shared<minikin::FontFamily>(std::move(fonts));
}
@@ -68,7 +70,7 @@ std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const ch
TEST(TypefaceTest, resolveDefault_and_setDefaultTest) {
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kRobotoVariable), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(regular.get(), Typeface::resolveDefault(regular.get()));
// Keep the original to restore it later.
@@ -347,71 +349,71 @@ TEST(TypefaceTest, createFromFamilies_Single) {
// In Java, new
// Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(false).build();
std::unique_ptr<Typeface> regular(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, false));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
// In Java, new
- // Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
+ // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(false).build();
std::unique_ptr<Typeface> bold(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, false));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
// In Java, new
- // Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
+ // Typeface.Builder("Roboto-Regular.ttf").setWeight(400).setItalic(true).build();
std::unique_ptr<Typeface> italic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 400, true));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
// In Java,
// new
- // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
+ // Typeface.Builder("Roboto-Regular.ttf").setWeight(700).setItalic(true).build();
std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 700, true));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
// In Java,
// new
- // Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
+ // Typeface.Builder("Roboto-Regular.ttf").setWeight(1100).setItalic(false).build();
std::unique_ptr<Typeface> over1000(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
+ Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoVariable), 1100, false));
EXPECT_EQ(1000, over1000->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, over1000->fStyle.slant());
EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
}
TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
- // In Java, new Typeface.Builder("Roboto-Regular.ttf").build();
+ // In Java, new Typeface.Builder("Family-Regular.ttf").build();
std::unique_ptr<Typeface> regular(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoRegular), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kRegularFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(400, regular->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, regular->fStyle.slant());
EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
- // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
+ // In Java, new Typeface.Builder("Family-Bold.ttf").build();
std::unique_ptr<Typeface> bold(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoBold), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kBoldFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(700, bold->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::UPRIGHT, bold->fStyle.slant());
EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
- // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
+ // In Java, new Typeface.Builder("Family-Italic.ttf").build();
std::unique_ptr<Typeface> italic(Typeface::createFromFamilies(
- makeSingleFamlyVector(kRobotoItalic), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
+ makeSingleFamlyVector(kItalicFont), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(400, italic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, italic->fStyle.slant());
EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
- // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
+ // In Java, new Typeface.Builder("Family-BoldItalic.ttf").build();
std::unique_ptr<Typeface> boldItalic(
- Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic),
+ Typeface::createFromFamilies(makeSingleFamlyVector(kBoldItalicFont),
RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(700, boldItalic->fStyle.weight());
EXPECT_EQ(minikin::FontStyle::Slant::ITALIC, boldItalic->fStyle.slant());
@@ -420,8 +422,8 @@ TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
TEST(TypefaceTest, createFromFamilies_Family) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
- buildFamily(kRobotoRegular), buildFamily(kRobotoBold), buildFamily(kRobotoItalic),
- buildFamily(kRobotoBoldItalic)};
+ buildFamily(kRegularFont), buildFamily(kBoldFont), buildFamily(kItalicFont),
+ buildFamily(kBoldItalicFont)};
std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(400, typeface->fStyle.weight());
@@ -430,7 +432,7 @@ TEST(TypefaceTest, createFromFamilies_Family) {
TEST(TypefaceTest, createFromFamilies_Family_withoutRegular) {
std::vector<std::shared_ptr<minikin::FontFamily>> families = {
- buildFamily(kRobotoBold), buildFamily(kRobotoItalic), buildFamily(kRobotoBoldItalic)};
+ buildFamily(kBoldFont), buildFamily(kItalicFont), buildFamily(kBoldItalicFont)};
std::unique_ptr<Typeface> typeface(Typeface::createFromFamilies(
std::move(families), RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
EXPECT_EQ(700, typeface->fStyle.weight());
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 5e56b26f46f0..6d4c57413f00 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -17,14 +17,9 @@
#include <gtest/gtest.h>
#include "PathParser.h"
-#include "GraphicsJNI.h"
-#include "SkGradientShader.h"
-#include "SkShader.h"
#include "VectorDrawable.h"
#include "utils/MathUtils.h"
#include "utils/VectorDrawableUtils.h"
-#include <shader/Shader.h>
-#include <shader/LinearGradientShader.h>
#include <functional>
@@ -400,21 +395,7 @@ TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) {
bitmap.allocN32Pixels(5, 5, false);
SkCanvas canvas(bitmap);
- SkPoint pts[2];
- pts[0].set(0, 0);
- pts[1].set(0, 0);
-
- std::vector<SkColor4f> colors(2);
- colors[0] = SkColors::kBlack;
- colors[1] = SkColors::kBlack;
-
- sk_sp<LinearGradientShader> shader = sk_sp(new LinearGradientShader(pts,
- colors,
- SkColorSpace::MakeSRGB(),
- nullptr,
- SkTileMode::kClamp,
- SkGradientShader::kInterpolateColorsInPremul_Flag,
- nullptr));
+ sk_sp<SkShader> shader = SkShaders::Color(SK_ColorBLACK);
// Initial ref count is 1
EXPECT_TRUE(shader->unique());
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index c1a98eabb2b7..42cf53b44f1e 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -46,8 +46,8 @@ import com.android.internal.location.ProviderProperties;
*/
interface ILocationManager
{
- Location getLastLocation(String provider, String packageName, String attributionTag);
- void getCurrentLocation(String provider, in LocationRequest request, in ICancellationSignal cancellationSignal, in ILocationCallback callback, String packageName, String attributionTag, String listenerId);
+ @nullable Location getLastLocation(String provider, String packageName, String attributionTag);
+ @nullable ICancellationSignal getCurrentLocation(String provider, in LocationRequest request, in ILocationCallback callback, String packageName, String attributionTag, String listenerId);
void registerLocationListener(String provider, in LocationRequest request, in ILocationListener listener, String packageName, String attributionTag, String listenerId);
void unregisterLocationListener(in ILocationListener listener);
@@ -106,11 +106,12 @@ 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 packageName, String attributionTag);
void removeTestProvider(String provider, String packageName, String attributionTag);
void setTestProviderLocation(String provider, in Location location, String packageName, String attributionTag);
void setTestProviderEnabled(String provider, boolean enabled, String packageName, String attributionTag);
- List<LocationRequest> getTestProviderCurrentRequests(String provider);
+
LocationTime getGnssTimeMillis();
void sendExtraCommand(String provider, String command, inout Bundle extras);
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9aa0c870e512..46bd22148fb2 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,8 @@
package android.location;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -586,6 +588,11 @@ public class Location implements Parcelable {
}
/** @hide */
+ public long getElapsedRealtimeMillis() {
+ return NANOSECONDS.toMillis(getElapsedRealtimeNanos());
+ }
+
+ /** @hide */
public long getElapsedRealtimeAgeNanos(long referenceRealtimeNs) {
return referenceRealtimeNs - mElapsedRealtimeNanos;
}
@@ -595,6 +602,11 @@ public class Location implements Parcelable {
return getElapsedRealtimeAgeNanos(SystemClock.elapsedRealtimeNanos());
}
+ /** @hide */
+ public long getElapsedRealtimeAgeMillis() {
+ return NANOSECONDS.toMillis(getElapsedRealtimeAgeNanos());
+ }
+
/**
* Set the time of this fix, in elapsed real-time since system boot.
*
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index db54a02b8f57..019dfd4e46cf 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -20,6 +20,8 @@ import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.LOCATION_HARDWARE;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.location.LocationRequest.createFromDeprecatedCriteria;
+import static android.location.LocationRequest.createFromDeprecatedProvider;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
@@ -87,6 +89,26 @@ import java.util.function.Consumer;
public class LocationManager {
/**
+ * For apps targeting Android S and above, LocationRequest system APIs may not be used with
+ * PendingIntent location requests.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long PREVENT_PENDING_INTENT_SYSTEM_API_USAGE = 169887240L;
+
+ /**
+ * For apps targeting Android S and above, location clients may receive historical locations
+ * (from before the present time) under some circumstances.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long DELIVER_HISTORICAL_LOCATIONS = 73144566L;
+
+ /**
* For apps targeting Android R and above, {@link #getProvider(String)} will no longer throw any
* security exceptions.
*
@@ -784,22 +806,25 @@ public class LocationManager {
Preconditions.checkArgument(provider != null, "invalid null provider");
Preconditions.checkArgument(locationRequest != null, "invalid null location request");
- ICancellationSignal remoteCancellationSignal = CancellationSignal.createTransport();
- GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor, consumer,
- remoteCancellationSignal);
-
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
- cancellationSignal.setOnCancelListener(transport::cancel);
}
+ GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor, consumer,
+ cancellationSignal);
+
+ ICancellationSignal cancelRemote;
try {
- mService.getCurrentLocation(provider, locationRequest, remoteCancellationSignal,
- transport, mContext.getPackageName(), mContext.getAttributionTag(),
- AppOpsManager.toReceiverId(consumer));
+ cancelRemote = mService.getCurrentLocation(provider,
+ locationRequest, transport, mContext.getPackageName(),
+ mContext.getAttributionTag(), AppOpsManager.toReceiverId(consumer));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ if (cancellationSignal != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
}
/**
@@ -1032,9 +1057,7 @@ public class LocationManager {
requestLocationUpdates(
provider,
- new LocationRequest.Builder(minTimeMs)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedProvider(provider, minTimeMs, minDistanceM, false),
executor,
listener);
}
@@ -1095,10 +1118,7 @@ public class LocationManager {
requestLocationUpdates(
FUSED_PROVIDER,
- new LocationRequest.Builder(minTimeMs)
- .setQuality(criteria)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedCriteria(criteria, minTimeMs, minDistanceM, false),
executor,
listener);
}
@@ -1127,9 +1147,7 @@ public class LocationManager {
requestLocationUpdates(
provider,
- new LocationRequest.Builder(minTimeMs)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedProvider(provider, minTimeMs, minDistanceM, false),
pendingIntent);
}
@@ -1156,10 +1174,7 @@ public class LocationManager {
Preconditions.checkArgument(criteria != null, "invalid null criteria");
requestLocationUpdates(
FUSED_PROVIDER,
- new LocationRequest.Builder(minTimeMs)
- .setQuality(criteria)
- .setMinUpdateDistanceMeters(minDistanceM)
- .build(),
+ createFromDeprecatedCriteria(criteria, minTimeMs, minDistanceM, false),
pendingIntent);
}
@@ -1275,13 +1290,15 @@ public class LocationManager {
* arguments. The same listener may be used across multiple providers with different requests
* for each provider.
*
- * <p>It may take a while to receive the first location update. If an immediate location is
- * required, applications may use the {@link #getLastKnownLocation(String)} method.
+ * <p>It may take some time to receive the first location update depending on the conditions the
+ * device finds itself in. In order to take advantage of cached locations, application may
+ * consider using {@link #getLastKnownLocation(String)} or {@link #getCurrentLocation(String,
+ * LocationRequest, CancellationSignal, Executor, Consumer)} instead.
*
* <p>See {@link LocationRequest} documentation for an explanation of various request parameters
* and how they can affect the received locations.
*
- * <p> If your application wants to passively observe location updates from any provider, then
+ * <p>If your application wants to passively observe location updates from all providers, then
* use the {@link #PASSIVE_PROVIDER}. This provider does not turn on or modify active location
* providers, so you do not need to be as careful about minimum time and minimum distance
* parameters. However, if your application performs heavy work on a location update (such as
@@ -1290,13 +1307,20 @@ public class LocationManager {
*
* <p>In case the provider you have selected is disabled, location updates will cease, and a
* provider availability update will be sent. As soon as the provider is enabled again, another
- * provider availability update will be sent and location updates will immediately resume.
+ * provider availability update will be sent and location updates will resume.
*
- * <p> When location callbacks are invoked, the system will hold a wakelock on your
+ * <p>When location callbacks are invoked, the system will hold a wakelock on your
* application's behalf for some period of time, but not indefinitely. If your application
* requires a long running wakelock within the location callback, you should acquire it
* yourself.
*
+ * <p>Spamming location requests is a drain on system resources, and the system has preventative
+ * measures in place to ensure that this behavior will never result in more locations than could
+ * be achieved with a single location request with an equivalent interval that is left in place
+ * the whole time. As part of this amelioration, applications that target Android S and above
+ * may receive cached or historical locations through their listener. These locations will never
+ * be older than the interval of the location request.
+ *
* <p>To unregister for location updates, use {@link #removeUpdates(LocationListener)}.
*
* @param provider a provider listed by {@link #getAllProviders()}
@@ -1807,22 +1831,6 @@ public class LocationManager {
public void clearTestProviderStatus(@NonNull String provider) {}
/**
- * Get the last list of {@link LocationRequest}s sent to the provider.
- *
- * @hide
- */
- @TestApi
- @NonNull
- public List<LocationRequest> getTestProviderCurrentRequests(String providerName) {
- Preconditions.checkArgument(providerName != null, "invalid null provider");
- try {
- return mService.getTestProviderCurrentRequests(providerName);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Sets a proximity alert for the location given by the position (latitude, longitude) and the
* given radius.
*
@@ -2538,7 +2546,7 @@ public class LocationManager {
}
private static class GetCurrentLocationTransport extends ILocationCallback.Stub implements
- ListenerExecutor {
+ ListenerExecutor, CancellationSignal.OnCancelListener {
private final Executor mExecutor;
@@ -2546,33 +2554,22 @@ public class LocationManager {
@Nullable
private Consumer<Location> mConsumer;
- @GuardedBy("this")
- @Nullable
- private ICancellationSignal mRemoteCancellationSignal;
-
GetCurrentLocationTransport(Executor executor, Consumer<Location> consumer,
- ICancellationSignal remoteCancellationSignal) {
+ @Nullable CancellationSignal cancellationSignal) {
Preconditions.checkArgument(executor != null, "illegal null executor");
Preconditions.checkArgument(consumer != null, "illegal null consumer");
mExecutor = executor;
mConsumer = consumer;
- mRemoteCancellationSignal = remoteCancellationSignal;
+
+ if (cancellationSignal != null) {
+ cancellationSignal.setOnCancelListener(this);
+ }
}
- public void cancel() {
- ICancellationSignal cancellationSignal;
+ @Override
+ public void onCancel() {
synchronized (this) {
- cancellationSignal = mRemoteCancellationSignal;
mConsumer = null;
- mRemoteCancellationSignal = null;
- }
-
- if (cancellationSignal != null) {
- try {
- cancellationSignal.cancel();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
}
}
@@ -2582,7 +2579,6 @@ public class LocationManager {
synchronized (this) {
consumer = mConsumer;
mConsumer = null;
- mRemoteCancellationSignal = null;
}
executeSafely(mExecutor, () -> consumer, listener -> listener.accept(location));
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 0521b10a2530..e03643c4c632 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -27,6 +27,8 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -45,6 +47,17 @@ import java.util.Objects;
public final class LocationRequest implements Parcelable {
/**
+ * For apps targeting Android S and above, all LocationRequest objects marked as low power will
+ * throw exceptions if the caller does not have the LOCATION_HARDWARE permission, instead of
+ * silently dropping the low power part of the request.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ public static final long LOW_POWER_EXCEPTIONS = 168936375L;
+
+ /**
* Represents a passive only request. Such a request will not trigger any active locations or
* power usage itself, but may receive locations generated in response to other requests.
*
@@ -237,7 +250,7 @@ public final class LocationRequest implements Parcelable {
boolean hiddenFromAppOps,
boolean locationSettingsIgnored,
boolean lowPower,
- @Nullable WorkSource workSource) {
+ WorkSource workSource) {
Preconditions.checkArgument(intervalMillis != PASSIVE_INTERVAL || quality == POWER_NONE);
Preconditions.checkArgument(minUpdateIntervalMillis <= intervalMillis);
@@ -252,7 +265,7 @@ public final class LocationRequest implements Parcelable {
mHideFromAppOps = hiddenFromAppOps;
mLowPower = lowPower;
mLocationSettingsIgnored = locationSettingsIgnored;
- mWorkSource = workSource;
+ mWorkSource = Objects.requireNonNull(workSource);
}
/**
@@ -632,12 +645,15 @@ public final class LocationRequest implements Parcelable {
@SystemApi
@Deprecated
public void setWorkSource(@Nullable WorkSource workSource) {
+ if (workSource == null) {
+ workSource = new WorkSource();
+ }
mWorkSource = workSource;
}
/**
- * Returns the work source used for power blame for this request. If null, the system is free to
- * assign power blame as it deems most appropriate.
+ * Returns the work source used for power blame for this request. If empty, the system is free
+ * to assign power blame as it deems most appropriate.
*
* @return the work source used for power blame for this request
*
@@ -645,7 +661,7 @@ public final class LocationRequest implements Parcelable {
*/
@TestApi
@SystemApi
- public @Nullable WorkSource getWorkSource() {
+ public @NonNull WorkSource getWorkSource() {
return mWorkSource;
}
@@ -737,7 +753,7 @@ public final class LocationRequest implements Parcelable {
s.append(qualityToString(mQuality)).append(" ");
}
if (mInterval != PASSIVE_INTERVAL) {
- s.append("interval=");
+ s.append("@");
TimeUtils.formatDuration(mInterval, s);
} else {
s.append("PASSIVE");
@@ -752,7 +768,8 @@ public final class LocationRequest implements Parcelable {
if (mMaxUpdates != Integer.MAX_VALUE) {
s.append(" maxUpdates=").append(mMaxUpdates);
}
- if (mMinUpdateIntervalMillis < mInterval) {
+ if (mMinUpdateIntervalMillis != IMPLICIT_MIN_UPDATE_INTERVAL
+ && mMinUpdateIntervalMillis < mInterval) {
s.append(" minUpdateInterval=");
TimeUtils.formatDuration(mMinUpdateIntervalMillis, s);
}
@@ -1048,9 +1065,9 @@ public final class LocationRequest implements Parcelable {
}
/**
- * Sets the work source to use for power blame for this location request. Defaults to null,
- * which implies the system is free to assign power blame as it determines best for this
- * request (which usually means blaming the owner of the location listener).
+ * Sets the work source to use for power blame for this location request. Defaults to an
+ * empty WorkSource, which implies the system is free to assign power blame as it determines
+ * best for this request (which usually means blaming the owner of the location listener).
*
* <p>Permissions enforcement occurs when resulting location request is actually used, not
* when this method is invoked.
@@ -1094,7 +1111,7 @@ public final class LocationRequest implements Parcelable {
mHiddenFromAppOps,
mLocationSettingsIgnored,
mLowPower,
- mWorkSource);
+ new WorkSource(mWorkSource));
}
}
}
diff --git a/location/java/android/location/timezone/LocationTimeZoneEvent.java b/location/java/android/location/timezone/LocationTimeZoneEvent.java
index 55bc507964e6..d3fd5c3ac061 100644
--- a/location/java/android/location/timezone/LocationTimeZoneEvent.java
+++ b/location/java/android/location/timezone/LocationTimeZoneEvent.java
@@ -23,6 +23,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -37,7 +39,7 @@ public final class LocationTimeZoneEvent implements Parcelable {
@IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS,
EVENT_TYPE_UNCERTAIN })
- @interface EventType {}
+ public @interface EventType {}
/** Uninitialized value for {@link #mEventType} - must not be used for real events. */
private static final int EVENT_TYPE_UNKNOWN = 0;
@@ -49,8 +51,8 @@ public final class LocationTimeZoneEvent implements Parcelable {
public static final int EVENT_TYPE_PERMANENT_FAILURE = 1;
/**
- * Indicates a successful geolocation time zone detection event. {@link #mTimeZoneIds} will be
- * non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+ * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+ * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
*/
public static final int EVENT_TYPE_SUCCESS = 2;
@@ -81,10 +83,7 @@ public final class LocationTimeZoneEvent implements Parcelable {
mTimeZoneIds = immutableList(timeZoneIds);
boolean emptyTimeZoneIdListExpected = eventType != EVENT_TYPE_SUCCESS;
- if (emptyTimeZoneIdListExpected && !timeZoneIds.isEmpty()) {
- throw new IllegalStateException(
- "timeZoneIds must only have values when eventType is success");
- }
+ Preconditions.checkState(!emptyTimeZoneIdListExpected || timeZoneIds.isEmpty());
mElapsedRealtimeNanos = elapsedRealtimeNanos;
}
@@ -102,9 +101,7 @@ public final class LocationTimeZoneEvent implements Parcelable {
*
* <p>This value can be reliably compared to {@link
* android.os.SystemClock#elapsedRealtimeNanos}, to calculate the age of a fix and to compare
- * {@link LocationTimeZoneEvent} fixes. This is reliable because elapsed real-time is guaranteed
- * monotonic for each system boot and continues to increment even when the system is in deep
- * sleep.
+ * {@link LocationTimeZoneEvent} instances.
*
* @return elapsed real-time of fix, in nanoseconds since system boot.
*/
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index c32970f1bb04..e023aa1dcd22 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -150,6 +150,11 @@ public final class CallerIdentity {
return mListenerId;
}
+ /** Returns true if this represents a system identity. */
+ public boolean isSystem() {
+ return mUid == Process.SYSTEM_UID;
+ }
+
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index eb2e23e1b5e1..4a095c9be053 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -28,7 +28,6 @@ import android.location.LocationManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -161,7 +160,7 @@ public class GpsNetInitiatedHandler {
be set to true when the phone is having emergency call, and then will
be set to false by mPhoneStateListener when the emergency call ends.
*/
- mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber);
+ mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(phoneNumber);
if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
} else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
updateLocationMode();
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
index 2a37ef882e61..5c9d2908f5df 100644
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
+++ b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
@@ -30,7 +30,9 @@ import java.util.Objects;
public final class LocationTimeZoneProviderRequest implements Parcelable {
public static final LocationTimeZoneProviderRequest EMPTY_REQUEST =
- new LocationTimeZoneProviderRequest(false);
+ new LocationTimeZoneProviderRequest(
+ false /* reportLocationTimeZone */,
+ 0 /* initializationTimeoutMillis */);
public static final Creator<LocationTimeZoneProviderRequest> CREATOR =
new Creator<LocationTimeZoneProviderRequest>() {
@@ -45,17 +47,36 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
}
};
- /** Location time zone reporting is requested (true) */
private final boolean mReportLocationTimeZone;
- private LocationTimeZoneProviderRequest(boolean reportLocationTimeZone) {
+ private final long mInitializationTimeoutMillis;
+
+ private LocationTimeZoneProviderRequest(
+ boolean reportLocationTimeZone, long initializationTimeoutMillis) {
mReportLocationTimeZone = reportLocationTimeZone;
+ mInitializationTimeoutMillis = initializationTimeoutMillis;
}
+ /**
+ * Returns {@code true} if the provider should report events related to the device's current
+ * time zone, {@code false} otherwise.
+ */
public boolean getReportLocationTimeZone() {
return mReportLocationTimeZone;
}
+ // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
+ // be passed to the LocationTimeZoneProvider and remove if it is not useful.
+ /**
+ * Returns the maximum time that the provider is allowed to initialize before it is expected to
+ * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+ * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+ * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+ */
+ public long getInitializationTimeoutMillis() {
+ return mInitializationTimeoutMillis;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -63,14 +84,15 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(mReportLocationTimeZone ? 1 : 0);
+ parcel.writeBoolean(mReportLocationTimeZone);
+ parcel.writeLong(mInitializationTimeoutMillis);
}
static LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
- ClassLoader classLoader = LocationTimeZoneProviderRequest.class.getClassLoader();
- return new Builder()
- .setReportLocationTimeZone(in.readInt() == 1)
- .build();
+ boolean reportLocationTimeZone = in.readBoolean();
+ long initializationTimeoutMillis = in.readLong();
+ return new LocationTimeZoneProviderRequest(
+ reportLocationTimeZone, initializationTimeoutMillis);
}
@Override
@@ -82,31 +104,28 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
return false;
}
LocationTimeZoneProviderRequest that = (LocationTimeZoneProviderRequest) o;
- return mReportLocationTimeZone == that.mReportLocationTimeZone;
+ return mReportLocationTimeZone == that.mReportLocationTimeZone
+ && mInitializationTimeoutMillis == that.mInitializationTimeoutMillis;
}
@Override
public int hashCode() {
- return Objects.hash(mReportLocationTimeZone);
+ return Objects.hash(mReportLocationTimeZone, mInitializationTimeoutMillis);
}
@Override
public String toString() {
- StringBuilder s = new StringBuilder();
- s.append("TimeZoneProviderRequest[");
- if (mReportLocationTimeZone) {
- s.append("ON");
- } else {
- s.append("OFF");
- }
- s.append(']');
- return s.toString();
+ return "LocationTimeZoneProviderRequest{"
+ + "mReportLocationTimeZone=" + mReportLocationTimeZone
+ + ", mInitializationTimeoutMillis=" + mInitializationTimeoutMillis
+ + "}";
}
/** @hide */
public static final class Builder {
private boolean mReportLocationTimeZone;
+ private long mInitializationTimeoutMillis;
/**
* Sets the property that enables / disables the provider. This is set to {@code false} by
@@ -117,10 +136,20 @@ public final class LocationTimeZoneProviderRequest implements Parcelable {
return this;
}
+ /**
+ * Sets the initialization timeout. See {@link
+ * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()} for details.
+ */
+ public Builder setInitializationTimeoutMillis(long timeoutMillis) {
+ mInitializationTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
/** Builds the {@link LocationTimeZoneProviderRequest} instance. */
@NonNull
public LocationTimeZoneProviderRequest build() {
- return new LocationTimeZoneProviderRequest(this.mReportLocationTimeZone);
+ return new LocationTimeZoneProviderRequest(
+ mReportLocationTimeZone, mInitializationTimeoutMillis);
}
}
}
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index cd45e8e6ffa6..c0188c0df497 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -20,5 +20,8 @@ java_sdk_library {
libs: [
"androidx.annotation_annotation",
],
- api_packages: ["com.android.location.provider"],
+ api_packages: [
+ "com.android.location.provider",
+ "com.android.location.timezone.provider",
+ ],
}
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index c00ff57889d1..f43eb639ee23 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -59,3 +59,35 @@ package com.android.location.provider {
}
+package com.android.location.timezone.provider {
+
+ public final class LocationTimeZoneEventUnbundled {
+ method public int getEventType();
+ method @NonNull public java.util.List<java.lang.String> getTimeZoneIds();
+ field public static final int EVENT_TYPE_PERMANENT_FAILURE = 1; // 0x1
+ field public static final int EVENT_TYPE_SUCCESS = 2; // 0x2
+ field public static final int EVENT_TYPE_UNCERTAIN = 3; // 0x3
+ }
+
+ public static final class LocationTimeZoneEventUnbundled.Builder {
+ ctor public LocationTimeZoneEventUnbundled.Builder();
+ method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled build();
+ method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setEventType(int);
+ method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setTimeZoneIds(@NonNull java.util.List<java.lang.String>);
+ }
+
+ public abstract class LocationTimeZoneProviderBase {
+ ctor public LocationTimeZoneProviderBase(android.content.Context, String);
+ method public final android.os.IBinder getBinder();
+ method protected final android.content.Context getContext();
+ method protected abstract void onSetRequest(@NonNull com.android.location.timezone.provider.LocationTimeZoneProviderRequestUnbundled);
+ method protected final void reportLocationTimeZoneEvent(@NonNull com.android.location.timezone.provider.LocationTimeZoneEventUnbundled);
+ }
+
+ public final class LocationTimeZoneProviderRequestUnbundled {
+ method @IntRange(from=0) public long getInitializationTimeoutMillis();
+ method public boolean getReportLocationTimeZone();
+ }
+
+}
+
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
new file mode 100644
index 000000000000..aa0e8951f77c
--- /dev/null
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
@@ -0,0 +1,166 @@
+/*
+ * 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.location.timezone.provider;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * An event from a {@link LocationTimeZoneProviderBase} sent while determining a device's time zone
+ * using its location.
+ */
+public final class LocationTimeZoneEventUnbundled {
+
+ @IntDef({ EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS, EVENT_TYPE_UNCERTAIN })
+ @interface EventType {}
+
+ /**
+ * Indicates there was a permanent failure. This is not generally expected, and probably means a
+ * required backend service has been turned down, or the client is unreasonably old.
+ */
+ public static final int EVENT_TYPE_PERMANENT_FAILURE =
+ LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
+
+ /**
+ * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
+ * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
+ */
+ public static final int EVENT_TYPE_SUCCESS = LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
+
+ /**
+ * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
+ * the provider is unable to detect location, or there was a problem when resolving the location
+ * to a time zone.
+ */
+ public static final int EVENT_TYPE_UNCERTAIN = LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
+
+ @NonNull
+ private final LocationTimeZoneEvent mDelegate;
+
+ private LocationTimeZoneEventUnbundled(@NonNull LocationTimeZoneEvent delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the event type.
+ */
+ public @EventType int getEventType() {
+ return mDelegate.getEventType();
+ }
+
+ /**
+ * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
+ * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
+ */
+ @NonNull
+ public List<String> getTimeZoneIds() {
+ return mDelegate.getTimeZoneIds();
+ }
+
+ /**
+ * Returns the information from this as a {@link LocationTimeZoneEvent}.
+ * @hide
+ */
+ @NonNull
+ public LocationTimeZoneEvent getInternalLocationTimeZoneEvent() {
+ return mDelegate;
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneEventUnbundled{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationTimeZoneEventUnbundled that = (LocationTimeZoneEventUnbundled) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDelegate.hashCode();
+ }
+
+ /**
+ * A builder of {@link LocationTimeZoneEventUnbundled} instances.
+ */
+ public static final class Builder {
+
+ private @EventType int mEventType;
+ private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
+
+ /**
+ * Set the time zone ID of this event.
+ */
+ @NonNull
+ public Builder setEventType(@EventType int eventType) {
+ checkValidEventType(eventType);
+ mEventType = eventType;
+ return this;
+ }
+
+ /**
+ * Sets the time zone IDs of this event.
+ */
+ @NonNull
+ public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
+ mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
+ return this;
+ }
+
+ /**
+ * Builds a {@link LocationTimeZoneEventUnbundled} instance.
+ */
+ @NonNull
+ public LocationTimeZoneEventUnbundled build() {
+ final int internalEventType = this.mEventType;
+ LocationTimeZoneEvent event = new LocationTimeZoneEvent.Builder()
+ .setUserHandle(UserHandle.of(ActivityManager.getCurrentUser()))
+ .setEventType(internalEventType)
+ .setTimeZoneIds(mTimeZoneIds)
+ .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+ .build();
+ return new LocationTimeZoneEventUnbundled(event);
+ }
+ }
+
+ private static int checkValidEventType(int eventType) {
+ if (eventType != EVENT_TYPE_SUCCESS
+ && eventType != EVENT_TYPE_UNCERTAIN
+ && eventType != EVENT_TYPE_PERMANENT_FAILURE) {
+ throw new IllegalStateException("eventType=" + eventType);
+ }
+ return eventType;
+ }
+}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
index c533c20d07e3..68ae722d703d 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
@@ -16,9 +16,9 @@
package com.android.location.timezone.provider;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.location.timezone.LocationTimeZoneEvent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -30,9 +30,29 @@ import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
import java.util.Objects;
/**
- * Base class for location time zone providers implemented as unbundled services.
+ * A base class for location time zone providers implemented as unbundled services.
*
- * TODO(b/152744911): Provide details of the expected service actions and threading.
+ * <p>Provider implementations are enabled / disabled via a call to {@link
+ * #onSetRequest(LocationTimeZoneProviderRequestUnbundled)}.
+ *
+ * <p>Once enabled, providers are expected to detect the time zone if possible, and report the
+ * result via {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} with a type of
+ * either {@link LocationTimeZoneEventUnbundled#EVENT_TYPE_UNCERTAIN} or {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_SUCCESS}. Providers may also report that they have
+ * permanently failed by sending an event of type {@link
+ * LocationTimeZoneEventUnbundled#EVENT_TYPE_PERMANENT_FAILURE}. See the javadocs for each event
+ * type for details.
+ *
+ * <p>Providers are expected to issue their first event within {@link
+ * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()}.
+ *
+ * <p>Once disabled or have failed, providers are required to stop producing events.
+ *
+ * <p>Threading:
+ *
+ * <p>Calls to {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} can be made on
+ * on any thread, but may be processed asynchronously by the system server. Similarly, calls to
+ * {@link #onSetRequest(LocationTimeZoneProviderRequestUnbundled)} may occur on any thread.
*
* <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
* API stable.
@@ -64,11 +84,12 @@ public abstract class LocationTimeZoneProviderBase {
/**
* Reports a new location time zone event from this provider.
*/
- public void reportLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) {
+ protected final void reportLocationTimeZoneEvent(
+ @NonNull LocationTimeZoneEventUnbundled event) {
ILocationTimeZoneProviderManager manager = mManager;
if (manager != null) {
try {
- manager.onLocationTimeZoneEvent(locationTimeZoneEvent);
+ manager.onLocationTimeZoneEvent(event.getInternalLocationTimeZoneEvent());
} catch (RemoteException | RuntimeException e) {
Log.w(mTag, e);
}
@@ -81,7 +102,7 @@ public abstract class LocationTimeZoneProviderBase {
* to start returning location time zones, or to stop returning location time zones, depending
* on the parameters in the request.
*/
- protected abstract void onSetRequest(LocationTimeZoneProviderRequestUnbundled request);
+ protected abstract void onSetRequest(@NonNull LocationTimeZoneProviderRequestUnbundled request);
private final class Service extends ILocationTimeZoneProvider.Stub {
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
index e898bbf3ecc0..10d103841bab 100644
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
@@ -16,6 +16,7 @@
package com.android.location.timezone.provider;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
@@ -27,22 +28,36 @@ import java.util.Objects;
*
* <p>IMPORTANT: This class is effectively a public API for unbundled code, and must remain API
* stable.
- *
- * @hide
*/
public final class LocationTimeZoneProviderRequestUnbundled {
private final LocationTimeZoneProviderRequest mRequest;
+ /** @hide */
public LocationTimeZoneProviderRequestUnbundled(
@NonNull LocationTimeZoneProviderRequest request) {
mRequest = Objects.requireNonNull(request);
}
+ /**
+ * Returns {@code true} if the provider should report events related to the device's current
+ * time zone, {@code false} otherwise.
+ */
public boolean getReportLocationTimeZone() {
return mRequest.getReportLocationTimeZone();
}
+ /**
+ * Returns the maximum time that the provider is allowed to initialize before it is expected to
+ * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
+ * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
+ * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
+ */
+ @IntRange(from = 0)
+ public long getInitializationTimeoutMillis() {
+ return mRequest.getInitializationTimeoutMillis();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/media/Android.bp b/media/Android.bp
index 0ed10472561d..828707b70e7b 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -1,65 +1,54 @@
aidl_interface {
name: "audio_common-aidl",
unstable: true,
- local_include_dir: "java",
+ local_include_dir: "aidl",
srcs: [
- "java/android/media/audio/common/AudioChannelMask.aidl",
- "java/android/media/audio/common/AudioConfig.aidl",
- "java/android/media/audio/common/AudioFormat.aidl",
- "java/android/media/audio/common/AudioOffloadInfo.aidl",
- "java/android/media/audio/common/AudioStreamType.aidl",
- "java/android/media/audio/common/AudioUsage.aidl",
+ "aidl/android/media/audio/common/AudioChannelMask.aidl",
+ "aidl/android/media/audio/common/AudioConfig.aidl",
+ "aidl/android/media/audio/common/AudioFormat.aidl",
+ "aidl/android/media/audio/common/AudioOffloadInfo.aidl",
+ "aidl/android/media/audio/common/AudioStreamType.aidl",
+ "aidl/android/media/audio/common/AudioUsage.aidl",
+ ],
+}
+
+aidl_interface {
+ name: "media_permission-aidl",
+ unstable: true,
+ local_include_dir: "aidl",
+ srcs: [
+ "aidl/android/media/permission/Identity.aidl",
],
- backend:
- {
- cpp: {
- enabled: true,
- },
- java: {
- // Already generated as part of the entire media java library.
- enabled: false,
- },
- },
}
aidl_interface {
name: "soundtrigger_middleware-aidl",
unstable: true,
- local_include_dir: "java",
+ local_include_dir: "aidl",
srcs: [
- "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
- "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
- "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
- "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
- "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
- "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
- "java/android/media/soundtrigger_middleware/Phrase.aidl",
- "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
- "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
- "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
- "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
- "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
- "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
- "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
- "java/android/media/soundtrigger_middleware/SoundModel.aidl",
- "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
- "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
- "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
- "java/android/media/soundtrigger_middleware/Status.aidl",
+ "aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl",
+ "aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+ "aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+ "aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+ "aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+ "aidl/android/media/soundtrigger_middleware/ModelParameter.aidl",
+ "aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+ "aidl/android/media/soundtrigger_middleware/Phrase.aidl",
+ "aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+ "aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+ "aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+ "aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+ "aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+ "aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+ "aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+ "aidl/android/media/soundtrigger_middleware/SoundModel.aidl",
+ "aidl/android/media/soundtrigger_middleware/SoundModelType.aidl",
+ "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+ "aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+ "aidl/android/media/soundtrigger_middleware/Status.aidl",
+ ],
+ imports: [
+ "audio_common-aidl",
+ "media_permission-aidl",
],
- backend:
- {
- cpp: {
- enabled: true,
- },
- java: {
- // Already generated as part of the entire media java library.
- enabled: false,
- },
- ndk: {
- // Not currently needed, and disabled because of b/146172425
- enabled: false,
- },
- },
- imports: [ "audio_common-aidl" ],
}
diff --git a/media/OWNERS b/media/OWNERS
index c95ac6c210c0..0fc781c848f7 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -9,14 +9,14 @@ hkuang@google.com
hunga@google.com
insun@google.com
jaewan@google.com
+jinpark@google.com
jmtrivi@google.com
jsharkey@android.com
klhyun@google.com
lajos@google.com
marcone@google.com
+nchalko@google.com
philburk@google.com
+quxiangfang@google.com
sungsoo@google.com
wonsik@google.com
-
-# For maintaining sync with AndroidX code
-per-file ExifInterface.java = jinpark@google.com, sungsoo@google.com
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/aidl/android/media/audio/common/AudioChannelMask.aidl
index b9b08e6921bc..b9b08e6921bc 100644
--- a/media/java/android/media/audio/common/AudioChannelMask.aidl
+++ b/media/aidl/android/media/audio/common/AudioChannelMask.aidl
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/aidl/android/media/audio/common/AudioConfig.aidl
index 50dd796e1fa0..50dd796e1fa0 100644
--- a/media/java/android/media/audio/common/AudioConfig.aidl
+++ b/media/aidl/android/media/audio/common/AudioConfig.aidl
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/aidl/android/media/audio/common/AudioFormat.aidl
index aadc8e26cce3..aadc8e26cce3 100644
--- a/media/java/android/media/audio/common/AudioFormat.aidl
+++ b/media/aidl/android/media/audio/common/AudioFormat.aidl
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl
index ec10d71135ae..ec10d71135ae 100644
--- a/media/java/android/media/audio/common/AudioOffloadInfo.aidl
+++ b/media/aidl/android/media/audio/common/AudioOffloadInfo.aidl
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/aidl/android/media/audio/common/AudioStreamType.aidl
index c54566726350..c54566726350 100644
--- a/media/java/android/media/audio/common/AudioStreamType.aidl
+++ b/media/aidl/android/media/audio/common/AudioStreamType.aidl
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/aidl/android/media/audio/common/AudioUsage.aidl
index ef348165b22c..ef348165b22c 100644
--- a/media/java/android/media/audio/common/AudioUsage.aidl
+++ b/media/aidl/android/media/audio/common/AudioUsage.aidl
diff --git a/media/aidl/android/media/permission/Identity.aidl b/media/aidl/android/media/permission/Identity.aidl
new file mode 100644
index 000000000000..361497d59ea9
--- /dev/null
+++ b/media/aidl/android/media/permission/Identity.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.media.permission;
+
+/**
+ * A collection of identity-related information, required for permission enforcement.
+ *
+ * {@hide}
+ */
+parcelable Identity {
+ /** Linux user ID. */
+ int uid;
+ /** Linux process ID. */
+ int pid;
+ /** Package name. If null, the first package owned by the given uid will be assumed. */
+ @nullable String packageName;
+ /** Attribution tag. Mostly used for diagnostic purposes. */
+ @nullable String attributionTag;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl
index 97a8849c7b07..97a8849c7b07 100644
--- a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/AudioCapabilities.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
index 3dbc70556bd3..3dbc70556bd3 100644
--- a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 726af7681979..726af7681979 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
diff --git a/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 000000000000..d1126b9006e0
--- /dev/null
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -0,0 +1,89 @@
+/*
+ * 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.media.soundtrigger_middleware;
+
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+ /**
+ * Query the available modules and their capabilities.
+ *
+ * This variant is intended for use by the originator of the operations for permission
+ * enforcement purposes. The provided identity's uid/pid fields will be ignored and overridden
+ * by the ones provided by Binder.getCallingUid() / Binder.getCallingPid().
+ */
+ SoundTriggerModuleDescriptor[] listModulesAsOriginator(in Identity identity);
+
+ /**
+ * Query the available modules and their capabilities.
+ *
+ * This variant is intended for use by a trusted "middleman", acting on behalf of some identity
+ * other than itself. The caller must provide:
+ * - Its own identity, which will be used to establish trust via the
+ * SOUNDTRIGGER_DELEGATE_IDENTITY permission. This identity's uid/pid fields will be ignored
+ * and overridden by the ones provided by Binder.getCallingUid() / Binder.getCallingPid().
+ * This implies that the caller must clear its caller identity to protect from the case where
+ * it resides in the same process as the callee.
+ * - The identity of the entity on behalf of which module operations are to be performed.
+ */
+ SoundTriggerModuleDescriptor[] listModulesAsMiddleman(in Identity middlemanIdentity,
+ in Identity originatorIdentity);
+
+ /**
+ * Attach to one of the available modules.
+ *
+ * This variant is intended for use by the originator of the operations for permission
+ * enforcement purposes. The provided identity's uid/pid fields will be ignored and overridden
+ * by the ones provided by Binder.getCallingUid() / Binder.getCallingPid().
+ *
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attachAsOriginator(int handle,
+ in Identity identity,
+ ISoundTriggerCallback callback);
+
+ /**
+ * Attach to one of the available modules.
+ *
+ * This variant is intended for use by a trusted "middleman", acting on behalf of some identity
+ * other than itself. The caller must provide:
+ * - Its own identity, which will be used to establish trust via the
+ * SOUNDTRIGGER_DELEGATE_IDENTITY permission. This identity's uid/pid fields will be ignored
+ * and overridden by the ones provided by Binder.getCallingUid() / Binder.getCallingPid().
+ * This implies that the caller must clear its caller identity to protect from the case where
+ * it resides in the same process as the callee.
+ * - The identity of the entity on behalf of which module operations are to be performed.
+ *
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attachAsMiddleman(int handle,
+ in Identity middlemanIdentity,
+ in Identity originatorIdentity,
+ ISoundTriggerCallback callback);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
index c4a57857dd3d..c4a57857dd3d 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl
index 09936278e93a..09936278e93a 100644
--- a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ModelParameter.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl
index d6948a87dc6d..d6948a87dc6d 100644
--- a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/ModelParameterRange.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/OWNERS b/media/aidl/android/media/soundtrigger_middleware/OWNERS
index e5d037003ac4..e5d037003ac4 100644
--- a/media/java/android/media/soundtrigger_middleware/OWNERS
+++ b/media/aidl/android/media/soundtrigger_middleware/OWNERS
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl
index 98a489f8a6a9..98a489f8a6a9 100644
--- a/media/java/android/media/soundtrigger_middleware/Phrase.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/Phrase.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
index 6a3ec61d1ebf..6a3ec61d1ebf 100644
--- a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
index cb96bf37a95d..cb96bf37a95d 100644
--- a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
index 81028c1608ea..81028c1608ea 100644
--- a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index 5c0eeb1e32b1..5c0eeb1e32b1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionConfig.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl
index a237ec1aa3b3..a237ec1aa3b3 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEvent.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl
index d8bfff4bec6f..d8bfff4bec6f 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionMode.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl
index d563edca547d..d563edca547d 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionStatus.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
index 81d8291e85aa..cee3635a1e3b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -16,6 +16,7 @@
package android.media.soundtrigger_middleware;
import android.media.soundtrigger_middleware.SoundModelType;
+import android.os.ParcelFileDescriptor;
/**
* Base sound model descriptor. This struct can be extended for various specific types by way of
@@ -32,7 +33,7 @@ parcelable SoundModel {
* was build for */
String vendorUuid;
/** Opaque data transparent to Android framework */
- FileDescriptor data;
+ ParcelFileDescriptor data;
/** Size of the above data, in bytes. */
int dataSize;
}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl
index f2abc9af7780..f2abc9af7780 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundModelType.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
index 667135ff61b9..667135ff61b9 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 9c56e7b98b3f..9c56e7b98b3f 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/aidl/android/media/soundtrigger_middleware/Status.aidl
index c7623f5bf491..c7623f5bf491 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/Status.aidl
diff --git a/media/java/android/media/AudioAttributes.aidl b/media/java/android/media/AudioAttributes.aidl
index 04587f9d48d4..88a6f50d2d3c 100644
--- a/media/java/android/media/AudioAttributes.aidl
+++ b/media/java/android/media/AudioAttributes.aidl
@@ -15,4 +15,4 @@
package android.media;
-parcelable AudioAttributes;
+@JavaOnlyStableParcelable parcelable AudioAttributes;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 48a3431f9b74..94e834f00ef9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -96,8 +96,8 @@ public class AudioManager {
private Context mOriginalContext;
private Context mApplicationContext;
private long mVolumeKeyUpTime;
- private final boolean mUseVolumeKeySounds;
- private final boolean mUseFixedVolume;
+ private boolean mUseFixedVolumeInitialized;
+ private boolean mUseFixedVolume;
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -711,8 +711,6 @@ public class AudioManager {
*/
@UnsupportedAppUsage
public AudioManager() {
- mUseVolumeKeySounds = true;
- mUseFixedVolume = false;
}
/**
@@ -721,10 +719,6 @@ public class AudioManager {
@UnsupportedAppUsage
public AudioManager(Context context) {
setContext(context);
- mUseVolumeKeySounds = getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_useVolumeKeySounds);
- mUseFixedVolume = getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_useFixedVolume);
}
private Context getContext() {
@@ -823,6 +817,18 @@ public class AudioManager {
* </ul>
*/
public boolean isVolumeFixed() {
+ synchronized (this) {
+ try {
+ if (!mUseFixedVolumeInitialized) {
+ mUseFixedVolume = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_useFixedVolume);
+ }
+ } catch (Exception e) {
+ } finally {
+ // only ever try once, so always consider initialized even if query failed
+ mUseFixedVolumeInitialized = true;
+ }
+ }
return mUseFixedVolume;
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 57e8e438d592..8845d6954db2 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -17,6 +17,7 @@
package android.media;
import static android.media.ExifInterfaceUtils.byteArrayToHexString;
+import static android.media.ExifInterfaceUtils.closeFileDescriptor;
import static android.media.ExifInterfaceUtils.closeQuietly;
import static android.media.ExifInterfaceUtils.convertToLongArray;
import static android.media.ExifInterfaceUtils.copy;
@@ -30,7 +31,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -598,7 +598,6 @@ public class ExifInterface {
private static final int WEBP_CHUNK_TYPE_BYTE_LENGTH = 4;
private static final int WEBP_CHUNK_SIZE_BYTE_LENGTH = 4;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@GuardedBy("sFormatter")
private static SimpleDateFormat sFormatter;
@GuardedBy("sFormatterTz")
@@ -1445,18 +1444,17 @@ public class ExifInterface {
sExifPointerTagMap.put(EXIF_POINTER_TAGS[5].number, IFD_TYPE_ORF_IMAGE_PROCESSING); // 8256
}
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mFilename;
private FileDescriptor mSeekableFileDescriptor;
private AssetManager.AssetInputStream mAssetInputStream;
private boolean mIsInputStream;
private int mMimeType;
private boolean mIsExifDataOnly;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(publicAlternatives = "Use {@link #getAttribute(java.lang.String)} "
+ + "instead.")
private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length];
private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length);
private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mHasThumbnail;
private boolean mHasThumbnailStrips;
private boolean mAreThumbnailStripsConsecutive;
@@ -1529,9 +1527,8 @@ public class ExifInterface {
mAssetInputStream = null;
mFilename = null;
- // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be
- // clarified in order for garbage collection to take place.
- boolean isFdOwner = false;
+
+ boolean isFdDuped = false;
if (isSeekableFD(fileDescriptor)) {
mSeekableFileDescriptor = fileDescriptor;
// Keep the original file descriptor in order to save attributes when it's seekable.
@@ -1539,7 +1536,7 @@ public class ExifInterface {
// feature won't be working.
try {
fileDescriptor = Os.dup(fileDescriptor);
- isFdOwner = true;
+ isFdDuped = true;
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
@@ -1549,10 +1546,13 @@ public class ExifInterface {
mIsInputStream = false;
FileInputStream in = null;
try {
- in = new FileInputStream(fileDescriptor, isFdOwner);
+ in = new FileInputStream(fileDescriptor);
loadAttributes(in);
} finally {
closeQuietly(in);
+ if (isFdDuped) {
+ closeFileDescriptor(fileDescriptor);
+ }
}
}
@@ -2199,6 +2199,7 @@ public class ExifInterface {
// Read the thumbnail.
InputStream in = null;
+ FileDescriptor newFileDescriptor = null;
try {
if (mAssetInputStream != null) {
in = mAssetInputStream;
@@ -2211,9 +2212,9 @@ public class ExifInterface {
} else if (mFilename != null) {
in = new FileInputStream(mFilename);
} else if (mSeekableFileDescriptor != null) {
- FileDescriptor fileDescriptor = Os.dup(mSeekableFileDescriptor);
- Os.lseek(fileDescriptor, 0, OsConstants.SEEK_SET);
- in = new FileInputStream(fileDescriptor, true);
+ newFileDescriptor = Os.dup(mSeekableFileDescriptor);
+ Os.lseek(newFileDescriptor, 0, OsConstants.SEEK_SET);
+ in = new FileInputStream(newFileDescriptor);
}
if (in == null) {
// Should not be reached this.
@@ -2234,6 +2235,9 @@ public class ExifInterface {
Log.d(TAG, "Encountered exception while getting thumbnail", e);
} finally {
closeQuietly(in);
+ if (newFileDescriptor != null) {
+ closeFileDescriptor(newFileDescriptor);
+ }
}
return null;
}
@@ -2402,11 +2406,8 @@ public class ExifInterface {
}
/**
- * Returns parsed {@code DateTime} value, or -1 if unavailable or invalid.
- *
- * @hide
+ * Returns parsed {@link #TAG_DATETIME} value, or -1 if unavailable or invalid.
*/
- @UnsupportedAppUsage
public @CurrentTimeMillisLong long getDateTime() {
return parseDateTime(getAttribute(TAG_DATETIME),
getAttribute(TAG_SUBSEC_TIME),
@@ -2414,10 +2415,7 @@ public class ExifInterface {
}
/**
- * Returns parsed {@code DateTimeDigitized} value, or -1 if unavailable or
- * invalid.
- *
- * @hide
+ * Returns parsed {@link #TAG_DATETIME_DIGITIZED} value, or -1 if unavailable or invalid.
*/
public @CurrentTimeMillisLong long getDateTimeDigitized() {
return parseDateTime(getAttribute(TAG_DATETIME_DIGITIZED),
@@ -2426,12 +2424,8 @@ public class ExifInterface {
}
/**
- * Returns parsed {@code DateTimeOriginal} value, or -1 if unavailable or
- * invalid.
- *
- * @hide
+ * Returns parsed {@link #TAG_DATETIME_ORIGINAL} value, or -1 if unavailable or invalid.
*/
- @UnsupportedAppUsage
public @CurrentTimeMillisLong long getDateTimeOriginal() {
return parseDateTime(getAttribute(TAG_DATETIME_ORIGINAL),
getAttribute(TAG_SUBSEC_TIME_ORIGINAL),
@@ -2483,9 +2477,7 @@ public class ExifInterface {
/**
* Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
* Returns -1 if the date time information if not available.
- * @hide
*/
- @UnsupportedAppUsage
public long getGpsDateTime() {
String date = getAttribute(TAG_GPS_DATESTAMP);
String time = getAttribute(TAG_GPS_TIMESTAMP);
@@ -2511,7 +2503,6 @@ public class ExifInterface {
}
/** {@hide} */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static float convertRationalLatLonToFloat(String rationalString, String ref) {
try {
String [] parts = rationalString.split(",");
diff --git a/media/java/android/media/ExifInterfaceUtils.java b/media/java/android/media/ExifInterfaceUtils.java
index 6ff706e8041f..491fe1d64ab9 100644
--- a/media/java/android/media/ExifInterfaceUtils.java
+++ b/media/java/android/media/ExifInterfaceUtils.java
@@ -16,7 +16,12 @@
package android.media;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
import java.io.Closeable;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -25,6 +30,8 @@ import java.io.OutputStream;
* Package private utility class for ExifInterface.
*/
class ExifInterfaceUtils {
+ private static final String TAG = "ExifInterface";
+
/**
* Copies all of the bytes from {@code in} to {@code out}. Neither stream is closed.
* Returns the total number of bytes transferred.
@@ -114,4 +121,15 @@ class ExifInterfaceUtils {
}
}
}
+
+ /**
+ * Closes a file descriptor that has been duplicated.
+ */
+ public static void closeFileDescriptor(FileDescriptor fd) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ex) {
+ Log.e(TAG, "Error closing fd.", ex);
+ }
+ }
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 0747ab13981e..ae97a71bd93a 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2711,12 +2711,12 @@ final public class MediaCodec {
}
};
- private final Pattern zeroPattern = new Pattern(0, 0);
+ private static final Pattern ZERO_PATTERN = new Pattern(0, 0);
/**
* The pattern applicable to the protected data in each subsample.
*/
- private Pattern pattern;
+ private Pattern mPattern = ZERO_PATTERN;
/**
* Set the subsample count, clear/encrypted sizes, key, IV and mode fields of
@@ -2735,22 +2735,30 @@ final public class MediaCodec {
key = newKey;
iv = newIV;
mode = newMode;
- pattern = zeroPattern;
+ mPattern = ZERO_PATTERN;
+ }
+
+ /**
+ * Returns the {@link Pattern encryption pattern}.
+ */
+ public @NonNull Pattern getPattern() {
+ return new Pattern(mPattern.getEncryptBlocks(), mPattern.getSkipBlocks());
}
/**
* Set the encryption pattern on a {@link MediaCodec.CryptoInfo} instance.
- * See {@link MediaCodec.CryptoInfo.Pattern}.
+ * See {@link Pattern}.
*/
public void setPattern(Pattern newPattern) {
if (newPattern == null) {
- newPattern = zeroPattern;
+ newPattern = ZERO_PATTERN;
}
- pattern = newPattern;
+ setPattern(newPattern.getEncryptBlocks(), newPattern.getSkipBlocks());
}
+ // Accessed from android_media_MediaExtractor.cpp.
private void setPattern(int blocksToEncrypt, int blocksToSkip) {
- pattern = new Pattern(blocksToEncrypt, blocksToSkip);
+ mPattern = new Pattern(blocksToEncrypt, blocksToSkip);
}
@Override
@@ -2772,9 +2780,9 @@ final public class MediaCodec {
builder.append(", encrypted ");
builder.append(Arrays.toString(numBytesOfEncryptedData));
builder.append(", pattern (encrypt: ");
- builder.append(pattern.mEncryptBlocks);
+ builder.append(mPattern.mEncryptBlocks);
builder.append(", skip: ");
- builder.append(pattern.mSkipBlocks);
+ builder.append(mPattern.mSkipBlocks);
builder.append(")");
return builder.toString();
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 755bbfb94362..1a49b85403e4 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -20,10 +20,12 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
@@ -436,6 +438,52 @@ public final class MediaFormat {
public static final String KEY_CAPTURE_RATE = "capture-rate";
/**
+ * A key for retrieving the slow-motion marker information associated with a video track.
+ * <p>
+ * The associated value is a ByteBuffer in {@link ByteOrder#BIG_ENDIAN}
+ * (networking order) of the following format:
+ * </p>
+ * <pre class="prettyprint">
+ * float(32) playbackRate;
+ * unsigned int(32) numMarkers;
+ * for (i = 0;i < numMarkers; i++) {
+ * int(64) timestampUs;
+ * float(32) speedRatio;
+ * }</pre>
+ * The meaning of each field is as follows:
+ * <table border="1" width="90%" align="center" cellpadding="5">
+ * <tbody>
+ * <tr>
+ * <td>playbackRate</td>
+ * <td>The frame rate at which the playback should happen (or the flattened
+ * clip should be).</td>
+ * </tr>
+ * <tr>
+ * <td>numMarkers</td>
+ * <td>The number of slow-motion markers that follows.</td>
+ * </tr>
+ * <tr>
+ * <td>timestampUs</td>
+ * <td>The starting point of a new segment.</td>
+ * </tr>
+ * <tr>
+ * <td>speedRatio</td>
+ * <td>The playback speed for that segment. The playback speed is a floating
+ * point number, indicating how fast the time progresses relative to that
+ * written in the container. (Eg. 4.0 means time goes 4x as fast, which
+ * makes 30fps become 120fps.)</td>
+ * </tr>
+ * </table>
+ * <p>
+ * The following constraints apply to the timestampUs of the markers:
+ * </p>
+ * <li>The timestampUs shall be monotonically increasing.</li>
+ * <li>The timestampUs shall fall within the time span of the video track.</li>
+ * <li>The first timestampUs should match that of the first video sample.</li>
+ */
+ public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
+
+ /**
* A key describing the frequency of key frames expressed in seconds between key frames.
* <p>
* This key is used by video encoders.
diff --git a/media/java/android/media/MediaMetadata.aidl b/media/java/android/media/MediaMetadata.aidl
index 66ee48304168..1d78da2a1b8e 100644
--- a/media/java/android/media/MediaMetadata.aidl
+++ b/media/java/android/media/MediaMetadata.aidl
@@ -15,4 +15,4 @@
package android.media;
-parcelable MediaMetadata;
+@JavaOnlyStableParcelable parcelable MediaMetadata;
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 523a072b957a..78eeca1ccc05 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -18,13 +18,13 @@ package android.media;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.browse.MediaBrowser;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -417,14 +417,17 @@ public final class MediaMetadata implements Parcelable {
}
private final Bundle mBundle;
+ private final int mBitmapDimensionLimit;
private MediaDescription mDescription;
- private MediaMetadata(Bundle bundle) {
+ private MediaMetadata(Bundle bundle, int bitmapDimensionLimit) {
mBundle = new Bundle(bundle);
+ mBitmapDimensionLimit = bitmapDimensionLimit;
}
private MediaMetadata(Parcel in) {
mBundle = in.readBundle();
+ mBitmapDimensionLimit = Math.max(in.readInt(), 1);
}
/**
@@ -513,6 +516,23 @@ public final class MediaMetadata implements Parcelable {
return bmp;
}
+ /**
+ * Gets the width/height limit (in pixels) for the bitmaps when this metadata was created.
+ * This method always returns a positive value.
+ * <p>
+ * If it returns {@link Integer#MAX_VALUE}, then no scaling down was applied to the bitmaps
+ * when this metadata was created.
+ * <p>
+ * If it returns another positive value, then all the bitmaps in this metadata has width/height
+ * not greater than this limit. Bitmaps may have been scaled down according to the limit.
+ * <p>
+ *
+ * @see Builder#setBitmapDimensionLimit(int)
+ */
+ public @IntRange(from = 1) int getBitmapDimensionLimit() {
+ return mBitmapDimensionLimit;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -521,6 +541,7 @@ public final class MediaMetadata implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBundle(mBundle);
+ dest.writeInt(mBitmapDimensionLimit);
}
/**
@@ -718,6 +739,7 @@ public final class MediaMetadata implements Parcelable {
*/
public static final class Builder {
private final Bundle mBundle;
+ private int mBitmapDimensionLimit = Integer.MAX_VALUE;
/**
* Create an empty Builder. Any field that should be included in the
@@ -736,30 +758,7 @@ public final class MediaMetadata implements Parcelable {
*/
public Builder(MediaMetadata source) {
mBundle = new Bundle(source.mBundle);
- }
-
- /**
- * Create a Builder using a {@link MediaMetadata} instance to set
- * initial values, but replace bitmaps with a scaled down copy if their width (or height)
- * is larger than maxBitmapSize.
- *
- * @param source The original metadata to copy.
- * @param maxBitmapSize The maximum height/width for bitmaps contained
- * in the metadata.
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public Builder(@NonNull MediaMetadata source, @IntRange(from = 1) int maxBitmapSize) {
- this(source);
- for (String key : mBundle.keySet()) {
- Object value = mBundle.get(key);
- if (value != null && value instanceof Bitmap) {
- Bitmap bmp = (Bitmap) value;
- if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
- putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
- }
- }
- }
+ mBitmapDimensionLimit = source.mBitmapDimensionLimit;
}
/**
@@ -902,9 +901,9 @@ public final class MediaMetadata implements Parcelable {
* <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
* </ul>
* <p>
- * Large bitmaps may be scaled down by the system when
- * {@link android.media.session.MediaSession#setMetadata} is called.
- * To pass full resolution images {@link Uri Uris} should be used with
+ * Large bitmaps may be scaled down by the system with
+ * {@link Builder#setBitmapDimensionLimit(int)} when {@link MediaSession#setMetadata}
+ * is called. To pass full resolution images {@link Uri Uris} should be used with
* {@link #putString}.
*
* @param key The key for referencing this value
@@ -923,18 +922,53 @@ public final class MediaMetadata implements Parcelable {
}
/**
+ * Sets the maximum width/height (in pixels) for the bitmaps in the metadata.
+ * Bitmaps will be replaced with scaled down copies if their width (or height) is
+ * larger than {@code bitmapDimensionLimit}.
+ * <p>
+ * In order to unset the limit, pass {@link Integer#MAX_VALUE} as
+ * {@code bitmapDimensionLimit}.
+ *
+ * @param bitmapDimensionLimit The maximum width/height (in pixels) for bitmaps
+ * contained in the metadata. Non-positive values are ignored.
+ * Pass {@link Integer#MAX_VALUE} to unset the limit.
+ */
+ @NonNull
+ public Builder setBitmapDimensionLimit(@IntRange(from = 1) int bitmapDimensionLimit) {
+ if (bitmapDimensionLimit > 0) {
+ mBitmapDimensionLimit = bitmapDimensionLimit;
+ } else {
+ Log.w(TAG, "setBitmapDimensionLimit(): Ignoring non-positive bitmapDimensionLimit: "
+ + bitmapDimensionLimit);
+ }
+ return this;
+ }
+
+ /**
* Creates a {@link MediaMetadata} instance with the specified fields.
*
* @return The new MediaMetadata instance
*/
public MediaMetadata build() {
- return new MediaMetadata(mBundle);
+ if (mBitmapDimensionLimit != Integer.MAX_VALUE) {
+ for (String key : mBundle.keySet()) {
+ Object value = mBundle.get(key);
+ if (value instanceof Bitmap) {
+ Bitmap bmp = (Bitmap) value;
+ if (bmp.getHeight() > mBitmapDimensionLimit
+ || bmp.getWidth() > mBitmapDimensionLimit) {
+ putBitmap(key, scaleBitmap(bmp, mBitmapDimensionLimit));
+ }
+ }
+ }
+ }
+ return new MediaMetadata(mBundle, mBitmapDimensionLimit);
}
- private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
- float maxSizeF = maxSize;
- float widthScale = maxSizeF / bmp.getWidth();
- float heightScale = maxSizeF / bmp.getHeight();
+ private Bitmap scaleBitmap(Bitmap bmp, int maxDimension) {
+ float maxDimensionF = maxDimension;
+ float widthScale = maxDimensionF / bmp.getWidth();
+ float heightScale = maxDimensionF / bmp.getHeight();
float scale = Math.min(widthScale, heightScale);
int height = (int) (bmp.getHeight() * scale);
int width = (int) (bmp.getWidth() * scale);
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index cf03b06c1fbd..835a7091bb70 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -16,10 +16,13 @@
package android.media;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
@@ -1331,7 +1334,6 @@ public class MediaMetadataRetriever implements AutoCloseable {
* @see MediaFormat#COLOR_RANGE_FULL
*/
public static final int METADATA_KEY_COLOR_RANGE = 37;
- // Add more here...
/**
* This key retrieves the sample rate in Hz, if available.
@@ -1344,4 +1346,13 @@ public class MediaMetadataRetriever implements AutoCloseable {
* This is a signed 32-bit integer formatted as a string in base 10.
*/
public static final int METADATA_KEY_BITS_PER_SAMPLE = 39;
+
+ /**
+ * This key retrieves the video codec mimetype if available.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40;
+
+ // Add more here...
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 2b3f420cd834..4d87fb3fa81f 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -45,6 +45,9 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
+import android.view.DisplayAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1737,7 +1740,9 @@ public class MediaRouter {
*/
private static final int DEFAULT_PLAYBACK_VOLUME = DEFAULT_PLAYBACK_MAX_VOLUME;
- RouteInfo(RouteCategory category) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public RouteInfo(RouteCategory category) {
mCategory = category;
mDeviceType = DEVICE_TYPE_UNKNOWN;
}
@@ -2078,7 +2083,9 @@ public class MediaRouter {
return mPresentationDisplay;
}
- boolean updatePresentationDisplay() {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updatePresentationDisplay() {
Display display = choosePresentationDisplay();
if (mPresentationDisplay != display) {
mPresentationDisplay = display;
@@ -2088,41 +2095,81 @@ public class MediaRouter {
}
private Display choosePresentationDisplay() {
- if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) {
- Display[] displays = sStatic.getAllPresentationDisplays();
-
- // Ensure that the specified display is valid for presentations.
- // This check will normally disallow the default display unless it was
- // configured as a presentation display for some reason.
- if (mPresentationDisplayId >= 0) {
- for (Display display : displays) {
- if (display.getDisplayId() == mPresentationDisplayId) {
- return display;
- }
+ if ((getSupportedTypes() & ROUTE_TYPE_LIVE_VIDEO) == 0) {
+ return null;
+ }
+ final Display[] displays = getAllPresentationDisplays();
+ if (displays == null || displays.length == 0) {
+ return null;
+ }
+
+ // Ensure that the specified display is valid for presentations.
+ // This check will normally disallow the default display unless it was
+ // configured as a presentation display for some reason.
+ if (mPresentationDisplayId >= 0) {
+ for (Display display : displays) {
+ if (display.getDisplayId() == mPresentationDisplayId) {
+ return display;
}
- return null;
}
+ return null;
+ }
- // Find the indicated Wifi display by its address.
- if (mDeviceAddress != null) {
- for (Display display : displays) {
- if (display.getType() == Display.TYPE_WIFI
- && mDeviceAddress.equals(display.getAddress())) {
- return display;
- }
+ // Find the indicated Wifi display by its address.
+ if (getDeviceAddress() != null) {
+ for (Display display : displays) {
+ if (display.getType() == Display.TYPE_WIFI
+ && displayAddressEquals(display)) {
+ return display;
}
- return null;
}
+ }
+
+ // Returns the first hard-wired display.
+ for (Display display : displays) {
+ if (display.getType() == Display.TYPE_EXTERNAL) {
+ return display;
+ }
+ }
- // For the default route, choose the first presentation display from the list.
- if (this == sStatic.mDefaultAudioVideo && displays.length > 0) {
- return displays[0];
+ // Returns the first non-default built-in display.
+ for (Display display : displays) {
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ return display;
}
}
+
+ // For the default route, choose the first presentation display from the list.
+ if (this == getDefaultAudioVideo()) {
+ return displays[0];
+ }
return null;
}
/** @hide */
+ @VisibleForTesting
+ public Display[] getAllPresentationDisplays() {
+ return sStatic.getAllPresentationDisplays();
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public RouteInfo getDefaultAudioVideo() {
+ return sStatic.mDefaultAudioVideo;
+ }
+
+ private boolean displayAddressEquals(Display display) {
+ final DisplayAddress displayAddress = display.getAddress();
+ // mDeviceAddress recorded mac address. If displayAddress is not a kind of Network,
+ // return false early.
+ if (!(displayAddress instanceof DisplayAddress.Network)) {
+ return false;
+ }
+ final DisplayAddress.Network networkAddress = (DisplayAddress.Network) displayAddress;
+ return getDeviceAddress().equals(networkAddress.toString());
+ }
+
+ /** @hide */
@UnsupportedAppUsage
public String getDeviceAddress() {
return mDeviceAddress;
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
index e09241137541..8cbe52f7c481 100644
--- a/media/java/android/media/MediaTranscodeManager.java
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -83,13 +83,13 @@ import java.util.concurrent.Executors;
<pre class=prettyprint>
TranscodingRequest request =
- new TranscodingRequest.Builder()
- .setSourceUri(srcUri)
- .setDestinationUri(dstUri)
- .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
- .setPriority(REALTIME)
- .setVideoTrackFormat(videoFormat)
- .build();
+ new TranscodingRequest.Builder()
+ .setSourceUri(srcUri)
+ .setDestinationUri(dstUri)
+ .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ .setPriority(REALTIME)
+ .setVideoTrackFormat(videoFormat)
+ .build();
}</pre>
TODO(hkuang): Add architecture diagram showing the transcoding service and api.
@@ -462,9 +462,7 @@ public final class MediaTranscodeManager {
mTranscodingClient = service.registerClient(
mTranscodingClientCallback,
mPackageName,
- mPackageName,
- IMediaTranscodingService.USE_CALLING_UID,
- IMediaTranscodingService.USE_CALLING_PID);
+ mPackageName);
if (mTranscodingClient != null) {
mTranscodingClient.asBinder().linkToDeath(() -> onClientDied(), /* flags */ 0);
@@ -498,6 +496,20 @@ public final class MediaTranscodeManager {
/** Uri of the destination media file. */
private @NonNull Uri mDestinationUri;
+ /**
+ * The UID of the client that the TranscodingRequest is for. Only privileged caller could
+ * set this Uid as only they could do the transcoding on behalf of the client.
+ * -1 means not available.
+ */
+ private int mClientUid = -1;
+
+ /**
+ * The Pid of the client that the TranscodingRequest is for. Only privileged caller could
+ * set this Uid as only they could do the transcoding on behalf of the client.
+ * -1 means not available.
+ */
+ private int mClientPid = -1;
+
/** Type of the transcoding. */
private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
@@ -534,6 +546,8 @@ public final class MediaTranscodeManager {
private TranscodingRequest(Builder b) {
mSourceUri = b.mSourceUri;
mDestinationUri = b.mDestinationUri;
+ mClientUid = b.mClientUid;
+ mClientPid = b.mClientPid;
mPriority = b.mPriority;
mType = b.mType;
mVideoTrackFormat = b.mVideoTrackFormat;
@@ -554,6 +568,16 @@ public final class MediaTranscodeManager {
return mSourceUri;
}
+ /** Return the UID of the client that this request is for. -1 means not available. */
+ public int getClientUid() {
+ return mClientUid;
+ }
+
+ /** Return the PID of the client that this request is for. -1 means not available. */
+ public int getClientPid() {
+ return mClientPid;
+ }
+
/** Return destination uri of the transcoding. */
@NonNull
public Uri getDestinationUri() {
@@ -592,6 +616,8 @@ public final class MediaTranscodeManager {
parcel.transcodingType = mType;
parcel.sourceFilePath = mSourceUri.toString();
parcel.destinationFilePath = mDestinationUri.toString();
+ parcel.clientUid = mClientUid;
+ parcel.clientPid = mClientPid;
parcel.requestedVideoTrackFormat = convertToVideoTrackFormat(mVideoTrackFormat);
if (mTestConfig != null) {
parcel.isForTesting = true;
@@ -667,6 +693,8 @@ public final class MediaTranscodeManager {
public static final class Builder {
private @NonNull Uri mSourceUri;
private @NonNull Uri mDestinationUri;
+ private int mClientUid = -1;
+ private int mClientPid = -1;
private @TranscodingType int mType = TRANSCODING_TYPE_UNKNOWN;
private @TranscodingPriority int mPriority = PRIORITY_UNKNOWN;
private @Nullable MediaFormat mVideoTrackFormat;
@@ -710,6 +738,38 @@ public final class MediaTranscodeManager {
}
/**
+ * Specify the UID of the client that this request is for.
+ * @param uid client Uid.
+ * @return The same builder instance.
+ * @throws IllegalArgumentException if uid is invalid.
+ * TODO(hkuang): Check the permission if it is allowed.
+ */
+ @NonNull
+ public Builder setClientUid(int uid) {
+ if (uid <= 0) {
+ throw new IllegalArgumentException("Invalid Uid");
+ }
+ mClientUid = uid;
+ return this;
+ }
+
+ /**
+ * Specify the PID of the client that this request is for.
+ * @param pid client Pid.
+ * @return The same builder instance.
+ * @throws IllegalArgumentException if pid is invalid.
+ * TODO(hkuang): Check the permission if it is allowed.
+ */
+ @NonNull
+ public Builder setClientPid(int pid) {
+ if (pid <= 0) {
+ throw new IllegalArgumentException("Invalid pid");
+ }
+ mClientPid = pid;
+ return this;
+ }
+
+ /**
* Specifies the priority of the transcoding.
*
* @param priority Must be one of the {@code PRIORITY_*}
@@ -1225,6 +1285,50 @@ public final class MediaTranscodeManager {
}
}
+ @Override
+ public String toString() {
+ String result;
+ String status;
+
+ switch (mResult) {
+ case RESULT_NONE:
+ result = "RESULT_NONE";
+ break;
+ case RESULT_SUCCESS:
+ result = "RESULT_SUCCESS";
+ break;
+ case RESULT_ERROR:
+ result = "RESULT_ERROR";
+ break;
+ case RESULT_CANCELED:
+ result = "RESULT_CANCELED";
+ break;
+ default:
+ result = String.valueOf(mResult);
+ break;
+ }
+
+ switch (mStatus) {
+ case STATUS_PENDING:
+ status = "STATUS_PENDING";
+ break;
+ case STATUS_PAUSED:
+ status = "STATUS_PAUSED";
+ break;
+ case STATUS_RUNNING:
+ status = "STATUS_RUNNING";
+ break;
+ case STATUS_FINISHED:
+ status = "STATUS_FINISHED";
+ break;
+ default:
+ status = String.valueOf(mStatus);
+ break;
+ }
+ return String.format(" Job: {id: %d, status: %s, result: %s, progress: %d}",
+ mJobId, status, result, mProgress);
+ }
+
private void updateProgress(int newProgress) {
synchronized (mLock) {
mProgress = newProgress;
@@ -1275,6 +1379,8 @@ public final class MediaTranscodeManager {
// Converts the request to TranscodingRequestParcel.
TranscodingRequestParcel requestParcel = transcodingRequest.writeToParcel();
+ Log.i(TAG, "Getting transcoding request " + transcodingRequest.getSourceUri());
+
// Submits the request to MediaTranscoding service.
try {
TranscodingJobParcel jobParcel = new TranscodingJobParcel();
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index 9e48f1e05391..35cfaca8562a 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -538,7 +538,7 @@ import java.util.List;
handler = new Handler(Looper.getMainLooper());
}
mSessionManager.addOnActiveSessionsChangedListener(mSessionListener, listenerComponent,
- UserHandle.myUserId(), handler);
+ handler);
mSessionListener.onActiveSessionsChanged(mSessionManager
.getActiveSessions(listenerComponent));
if (DEBUG) {
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index d35bc4176cb3..d02b49697821 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -372,7 +372,7 @@ public class Ringtone {
AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
startLocalPlayer();
}
- } else if (mAllowRemote && (mRemotePlayer != null)) {
+ } else if (mAllowRemote && (mRemotePlayer != null) && (mUri != null)) {
final Uri canonicalUri = mUri.getCanonicalUri();
final boolean looping;
final float volume;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 8deb0c4451ea..9deeb8fbab16 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
+import android.database.StaleDataException;
import android.net.Uri;
import android.os.Environment;
import android.os.FileUtils;
@@ -492,7 +493,12 @@ public class RingtoneManager {
public Uri getRingtoneUri(int position) {
// use cursor directly instead of requerying it, which could easily
// cause position to shuffle.
- if (mCursor == null || !mCursor.moveToPosition(position)) {
+ try {
+ if (mCursor == null || !mCursor.moveToPosition(position)) {
+ return null;
+ }
+ } catch (StaleDataException | IllegalStateException e) {
+ Log.e(TAG, "Unexpected Exception has been catched.", e);
return null;
}
@@ -1130,11 +1136,13 @@ public class RingtoneManager {
// Try finding the scanned ringtone
final String filename = getDefaultRingtoneFilename(type);
+ final String whichAudio = getQueryStringForType(type);
+ final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?";
final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
try (Cursor cursor = context.getContentResolver().query(baseUri,
new String[] { MediaColumns._ID },
- MediaColumns.DISPLAY_NAME + "=?",
- new String[] { filename }, null)) {
+ where,
+ new String[] { filename, "1" }, null)) {
if (cursor.moveToFirst()) {
final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
@@ -1162,4 +1170,13 @@ public class RingtoneManager {
default: throw new IllegalArgumentException();
}
}
+
+ private static String getQueryStringForType(int type) {
+ switch (type) {
+ case TYPE_RINGTONE: return MediaStore.Audio.AudioColumns.IS_RINGTONE;
+ case TYPE_NOTIFICATION: return MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+ case TYPE_ALARM: return MediaStore.Audio.AudioColumns.IS_ALARM;
+ default: throw new IllegalArgumentException();
+ }
+ }
}
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 011e835e02ec..b662901176e6 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -184,7 +184,7 @@ public final class MediaBrowser {
boolean bound = false;
try {
bound = mContext.bindService(intent, mServiceConnection,
- Context.BIND_AUTO_CREATE);
+ Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES);
} catch (Exception ex) {
Log.e(TAG, "Failed binding to service " + mServiceComponent);
}
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index 51d552066bc6..d5916b9bd6ab 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -384,14 +384,14 @@ public final class MidiDeviceServer implements Closeable {
private void updateDeviceStatus() {
// clear calling identity, since we may be in a Binder call from one of our clients
- long identityToken = Binder.clearCallingIdentity();
-
- MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
- mOutputPortOpenCount);
- if (mCallback != null) {
- mCallback.onDeviceStatusChanged(this, status);
- }
+ final long identityToken = Binder.clearCallingIdentity();
try {
+ MidiDeviceStatus status = new MidiDeviceStatus(mDeviceInfo, mInputPortOpen,
+ mOutputPortOpenCount);
+ if (mCallback != null) {
+ mCallback.onDeviceStatusChanged(this, status);
+ }
+
mMidiManager.setDeviceStatus(mServer, status);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in updateDeviceStatus");
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionManager.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionManager.aidl
new file mode 100644
index 000000000000..9e71afaa5023
--- /dev/null
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionManager.aidl
@@ -0,0 +1,14 @@
+package android.media.musicrecognition;
+
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.IBinder;
+
+/**
+ * Used by {@link MusicRecognitionManager} to tell system server to begin open an audio stream to
+ * the designated lookup service.
+ *
+ * @hide
+ */
+interface IMusicRecognitionManager {
+ void beginRecognition(in RecognitionRequest recognitionRequest, in IBinder callback);
+} \ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl
new file mode 100644
index 000000000000..e70504f0bd07
--- /dev/null
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionManagerCallback.aidl
@@ -0,0 +1,15 @@
+package android.media.musicrecognition;
+
+import android.os.Bundle;
+import android.media.MediaMetadata;
+
+/**
+ * Callback used by system server to notify invoker of {@link MusicRecognitionManager} of the result
+ *
+ * @hide
+ */
+oneway interface IMusicRecognitionManagerCallback {
+ void onRecognitionSucceeded(in MediaMetadata result, in Bundle extras);
+ void onRecognitionFailed(int failureCode);
+ void onAudioStreamClosed();
+} \ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl
new file mode 100644
index 000000000000..26543ed8bf8c
--- /dev/null
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionService.aidl
@@ -0,0 +1,18 @@
+package android.media.musicrecognition;
+
+import android.media.AudioFormat;
+import android.os.ParcelFileDescriptor;
+import android.os.IBinder;
+import android.media.musicrecognition.IMusicRecognitionServiceCallback;
+
+/**
+ * Interface from the system to a {@link MusicRecognitionService}.
+ *
+ * @hide
+ */
+oneway interface IMusicRecognitionService {
+ void onAudioStreamStarted(
+ in ParcelFileDescriptor fd,
+ in AudioFormat audioFormat,
+ in IMusicRecognitionServiceCallback callback);
+} \ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl b/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl
new file mode 100644
index 000000000000..15215c4e15f1
--- /dev/null
+++ b/media/java/android/media/musicrecognition/IMusicRecognitionServiceCallback.aidl
@@ -0,0 +1,15 @@
+package android.media.musicrecognition;
+
+import android.os.Bundle;
+import android.media.MediaMetadata;
+
+/**
+ * Interface from a {@MusicRecognitionService} the system.
+ *
+ * @hide
+ */
+oneway interface IMusicRecognitionServiceCallback {
+ void onRecognitionSucceeded(in MediaMetadata result, in Bundle extras);
+
+ void onRecognitionFailed(int failureCode);
+} \ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionManager.java b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
new file mode 100644
index 000000000000..c7f55dfe1c76
--- /dev/null
+++ b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
@@ -0,0 +1,186 @@
+/*
+ * 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.media.musicrecognition;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * System service that manages music recognition.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+@SystemService(Context.MUSIC_RECOGNITION_SERVICE)
+public class MusicRecognitionManager {
+
+ /**
+ * Error code provided by RecognitionCallback#onRecognitionFailed()
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"RECOGNITION_FAILED_"},
+ value = {RECOGNITION_FAILED_UNKNOWN,
+ RECOGNITION_FAILED_NOT_FOUND,
+ RECOGNITION_FAILED_NO_CONNECTIVITY,
+ RECOGNITION_FAILED_SERVICE_UNAVAILABLE,
+ RECOGNITION_FAILED_SERVICE_KILLED,
+ RECOGNITION_FAILED_TIMEOUT,
+ RECOGNITION_FAILED_AUDIO_UNAVAILABLE})
+ public @interface RecognitionFailureCode {
+ }
+
+ /** Catchall error code. */
+ public static final int RECOGNITION_FAILED_UNKNOWN = -1;
+ /** Recognition was performed but no result could be identified. */
+ public static final int RECOGNITION_FAILED_NOT_FOUND = 1;
+ /** Recognition failed because the server couldn't be reached. */
+ public static final int RECOGNITION_FAILED_NO_CONNECTIVITY = 2;
+ /**
+ * Recognition was not possible because the application which provides it is not available (for
+ * example, disabled).
+ */
+ public static final int RECOGNITION_FAILED_SERVICE_UNAVAILABLE = 3;
+ /** Recognition failed because the recognizer was killed. */
+ public static final int RECOGNITION_FAILED_SERVICE_KILLED = 5;
+ /** Recognition attempt timed out. */
+ public static final int RECOGNITION_FAILED_TIMEOUT = 6;
+ /** Recognition failed due to an issue with obtaining an audio stream. */
+ public static final int RECOGNITION_FAILED_AUDIO_UNAVAILABLE = 7;
+
+ /** Callback interface for the caller of this api. */
+ public interface RecognitionCallback {
+ /**
+ * Should be invoked by receiving app with the result of the search.
+ *
+ * @param recognitionRequest original request that started the recognition
+ * @param result result of the search
+ * @param extras extra data to be supplied back to the caller. Note that all
+ * executable parameters and file descriptors would be removed from the
+ * supplied bundle
+ */
+ void onRecognitionSucceeded(@NonNull RecognitionRequest recognitionRequest,
+ @NonNull MediaMetadata result, @Nullable Bundle extras);
+
+ /**
+ * Invoked when the search is not successful (possibly but not necessarily due to error).
+ *
+ * @param recognitionRequest original request that started the recognition
+ * @param failureCode failure code describing reason for failure
+ */
+ void onRecognitionFailed(@NonNull RecognitionRequest recognitionRequest,
+ @RecognitionFailureCode int failureCode);
+
+ /**
+ * Invoked by the system once the audio stream is closed either due to error, reaching the
+ * limit, or the remote service closing the stream. Always called per
+ * #beingStreamingSearch() invocation.
+ */
+ void onAudioStreamClosed();
+ }
+
+ private final IMusicRecognitionManager mService;
+
+ /** @hide */
+ public MusicRecognitionManager(IMusicRecognitionManager service) {
+ mService = service;
+ }
+
+ /**
+ * Constructs an {@link android.media.AudioRecord} from the given parameters and streams the
+ * audio bytes to the designated cloud lookup service. After the lookup is done, the given
+ * callback will be invoked by the system with the result or lack thereof.
+ *
+ * @param recognitionRequest audio parameters for the stream to search
+ * @param callbackExecutor where the callback is invoked
+ * @param callback invoked when the result is available
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_MUSIC_RECOGNITION)
+ public void beginStreamingSearch(
+ @NonNull RecognitionRequest recognitionRequest,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull RecognitionCallback callback) {
+ try {
+ mService.beginRecognition(
+ requireNonNull(recognitionRequest),
+ new MusicRecognitionCallbackWrapper(
+ requireNonNull(recognitionRequest),
+ requireNonNull(callback),
+ requireNonNull(callbackExecutor)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private final class MusicRecognitionCallbackWrapper extends
+ IMusicRecognitionManagerCallback.Stub {
+
+ @NonNull
+ private final RecognitionRequest mRecognitionRequest;
+ @NonNull
+ private final RecognitionCallback mCallback;
+ @NonNull
+ private final Executor mCallbackExecutor;
+
+ MusicRecognitionCallbackWrapper(
+ RecognitionRequest recognitionRequest,
+ RecognitionCallback callback,
+ Executor callbackExecutor) {
+ mRecognitionRequest = recognitionRequest;
+ mCallback = callback;
+ mCallbackExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onRecognitionSucceeded(MediaMetadata result, Bundle extras) {
+ mCallbackExecutor.execute(
+ () -> mCallback.onRecognitionSucceeded(mRecognitionRequest, result, extras));
+ }
+
+ @Override
+ public void onRecognitionFailed(@RecognitionFailureCode int failureCode) {
+ mCallbackExecutor.execute(
+ () -> mCallback.onRecognitionFailed(mRecognitionRequest, failureCode));
+ }
+
+ @Override
+ public void onAudioStreamClosed() {
+ mCallbackExecutor.execute(mCallback::onAudioStreamClosed);
+ }
+ }
+}
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
new file mode 100644
index 000000000000..b75d2c4f1e50
--- /dev/null
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -0,0 +1,140 @@
+/*
+ * 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.media.musicrecognition;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Intent;
+import android.media.AudioFormat;
+import android.media.MediaMetadata;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Implemented by an app that wants to offer music search lookups. The system will start the
+ * service and stream up to 16 seconds of audio over the given file descriptor.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class MusicRecognitionService extends Service {
+
+ private static final String TAG = MusicRecognitionService.class.getSimpleName();
+
+ /** Callback for the result of the remote search. */
+ public interface Callback {
+ /**
+ * Call this method to pass back a successful search result.
+ *
+ * @param result successful result of the search
+ * @param extras extra data to be supplied back to the caller. Note that all executable
+ * parameters and file descriptors would be removed from the supplied bundle
+ */
+ void onRecognitionSucceeded(@NonNull MediaMetadata result, @Nullable Bundle extras);
+
+ /**
+ * Call this method if the search does not find a result on an error occurred.
+ */
+ void onRecognitionFailed(@MusicRecognitionManager.RecognitionFailureCode int failureCode);
+ }
+
+ /**
+ * Action used to start this service.
+ *
+ * @hide
+ */
+ public static final String ACTION_MUSIC_SEARCH_LOOKUP =
+ "android.service.musicrecognition.MUSIC_RECOGNITION";
+
+ private Handler mHandler;
+ private final IMusicRecognitionService mServiceInterface =
+ new IMusicRecognitionService.Stub() {
+ @Override
+ public void onAudioStreamStarted(ParcelFileDescriptor fd,
+ AudioFormat audioFormat,
+ IMusicRecognitionServiceCallback callback) {
+ mHandler.sendMessage(
+ obtainMessage(MusicRecognitionService.this::onRecognize, fd,
+ audioFormat,
+ new Callback() {
+ @Override
+ public void onRecognitionSucceeded(
+ @NonNull MediaMetadata result,
+ @Nullable Bundle extras) {
+ try {
+ callback.onRecognitionSucceeded(result, extras);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onRecognitionFailed(int failureCode) {
+ try {
+ callback.onRecognitionFailed(failureCode);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }));
+ }
+ };
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ /**
+ * Read audio from this stream. You must invoke the callback whether the music is recognized or
+ * not.
+ *
+ * @param stream containing music to be recognized. Close when you are finished.
+ * @param audioFormat describes sample rate, channels and endianness of the stream
+ * @param callback to invoke after lookup is finished. Must always be called.
+ */
+ public abstract void onRecognize(@NonNull ParcelFileDescriptor stream,
+ @NonNull AudioFormat audioFormat,
+ @NonNull Callback callback);
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public IBinder onBind(@NonNull Intent intent) {
+ if (ACTION_MUSIC_SEARCH_LOOKUP.equals(intent.getAction())) {
+ return mServiceInterface.asBinder();
+ }
+ Log.w(TAG,
+ "Tried to bind to wrong intent (should be " + ACTION_MUSIC_SEARCH_LOOKUP + ": "
+ + intent);
+ return null;
+ }
+}
diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.aidl b/media/java/android/media/musicrecognition/RecognitionRequest.aidl
new file mode 100644
index 000000000000..757b57010e23
--- /dev/null
+++ b/media/java/android/media/musicrecognition/RecognitionRequest.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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.media.musicrecognition;
+
+parcelable RecognitionRequest; \ No newline at end of file
diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.java b/media/java/android/media/musicrecognition/RecognitionRequest.java
new file mode 100644
index 000000000000..65b2ccd37f79
--- /dev/null
+++ b/media/java/android/media/musicrecognition/RecognitionRequest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.media.musicrecognition;
+
+import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Encapsulates parameters for making music recognition queries via {@link MusicRecognitionManager}.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class RecognitionRequest implements Parcelable {
+ @NonNull private final AudioAttributes mAudioAttributes;
+ @NonNull private final AudioFormat mAudioFormat;
+ private final int mCaptureSession;
+ private final int mMaxAudioLengthSeconds;
+ private final int mIgnoreBeginningFrames;
+
+ private RecognitionRequest(Builder b) {
+ mAudioAttributes = requireNonNull(b.mAudioAttributes);
+ mAudioFormat = requireNonNull(b.mAudioFormat);
+ mCaptureSession = b.mCaptureSession;
+ mMaxAudioLengthSeconds = b.mMaxAudioLengthSeconds;
+ mIgnoreBeginningFrames = b.mIgnoreBeginningFrames;
+ }
+
+ @NonNull
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttributes;
+ }
+
+ @NonNull
+ public AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ public int getCaptureSession() {
+ return mCaptureSession;
+ }
+
+ @SuppressWarnings("MethodNameUnits")
+ public int getMaxAudioLengthSeconds() {
+ return mMaxAudioLengthSeconds;
+ }
+
+ public int getIgnoreBeginningFrames() {
+ return mIgnoreBeginningFrames;
+ }
+
+ /**
+ * Builder for constructing StreamSearchRequest objects.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final class Builder {
+ private AudioFormat mAudioFormat = new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setEncoding(ENCODING_PCM_16BIT)
+ .build();
+ private AudioAttributes mAudioAttributes = new AudioAttributes.Builder()
+ .setContentType(CONTENT_TYPE_MUSIC)
+ .build();
+ private int mCaptureSession = MediaRecorder.AudioSource.MIC;
+ private int mMaxAudioLengthSeconds = 24; // Max enforced in system server.
+ private int mIgnoreBeginningFrames = 0;
+
+ /** Attributes passed to the constructed {@link AudioRecord}. */
+ @NonNull
+ public Builder setAudioAttributes(@NonNull AudioAttributes audioAttributes) {
+ mAudioAttributes = audioAttributes;
+ return this;
+ }
+
+ /** AudioFormat passed to the constructed {@link AudioRecord}. */
+ @NonNull
+ public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
+ mAudioFormat = audioFormat;
+ return this;
+ }
+
+ /** Constant from {@link android.media.MediaRecorder.AudioSource}. */
+ @NonNull
+ public Builder setCaptureSession(int captureSession) {
+ mCaptureSession = captureSession;
+ return this;
+ }
+
+ /** Maximum number of seconds to stream from the audio source. */
+ @NonNull
+ public Builder setMaxAudioLengthSeconds(int maxAudioLengthSeconds) {
+ mMaxAudioLengthSeconds = maxAudioLengthSeconds;
+ return this;
+ }
+
+ /** Number of samples to drop from the start of the stream. */
+ @NonNull
+ public Builder setIgnoreBeginningFrames(int ignoreBeginningFrames) {
+ mIgnoreBeginningFrames = ignoreBeginningFrames;
+ return this;
+ }
+
+ /** Returns the constructed request. */
+ @NonNull
+ public RecognitionRequest build() {
+ return new RecognitionRequest(this);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mAudioFormat, flags);
+ dest.writeParcelable(mAudioAttributes, flags);
+ dest.writeInt(mCaptureSession);
+ dest.writeInt(mMaxAudioLengthSeconds);
+ dest.writeInt(mIgnoreBeginningFrames);
+ }
+
+ private RecognitionRequest(Parcel in) {
+ mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader());
+ mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader());
+ mCaptureSession = in.readInt();
+ mMaxAudioLengthSeconds = in.readInt();
+ mIgnoreBeginningFrames = in.readInt();
+ }
+
+ @NonNull public static final Creator<RecognitionRequest> CREATOR =
+ new Creator<RecognitionRequest>() {
+
+ @Override
+ public RecognitionRequest createFromParcel(Parcel p) {
+ return new RecognitionRequest(p);
+ }
+
+ @Override
+ public RecognitionRequest[] newArray(int size) {
+ return new RecognitionRequest[size];
+ }
+ };
+}
diff --git a/media/java/android/media/permission/ClearCallingIdentityContext.java b/media/java/android/media/permission/ClearCallingIdentityContext.java
new file mode 100644
index 000000000000..2d58b246a3c6
--- /dev/null
+++ b/media/java/android/media/permission/ClearCallingIdentityContext.java
@@ -0,0 +1,60 @@
+/*
+ * 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.media.permission;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+
+/**
+ * An RAII-style object, used to establish a scope in which the binder calling identity is cleared.
+ *
+ * <p>
+ * Intended usage:
+ * <pre>
+ * void caller() {
+ * try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ * // Within this scope the binder calling identity is cleared.
+ * ...
+ * }
+ * // Outside the scope the calling identity is restored to its prior state.
+ * </pre>
+ *
+ * @hide
+ */
+public class ClearCallingIdentityContext implements SafeCloseable {
+ private final long mRestoreKey;
+
+ /**
+ * Creates a new instance.
+ * @return A {@link SafeCloseable}, intended to be used in a try-with-resource block.
+ */
+ public static @NonNull
+ SafeCloseable create() {
+ return new ClearCallingIdentityContext();
+ }
+
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
+ private ClearCallingIdentityContext() {
+ mRestoreKey = Binder.clearCallingIdentity();
+ }
+
+ @Override
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
+ public void close() {
+ Binder.restoreCallingIdentity(mRestoreKey);
+ }
+}
diff --git a/media/java/android/media/permission/CompositeSafeCloseable.java b/media/java/android/media/permission/CompositeSafeCloseable.java
new file mode 100644
index 000000000000..08990ebd5057
--- /dev/null
+++ b/media/java/android/media/permission/CompositeSafeCloseable.java
@@ -0,0 +1,40 @@
+/*
+ * 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.media.permission;
+
+import android.annotation.NonNull;
+
+/**
+ * A composite {@link SafeCloseable}. Will close its children in reverse order.
+ *
+ * @hide
+ */
+class CompositeSafeCloseable implements SafeCloseable {
+ private final @NonNull SafeCloseable[] mChildren;
+
+ CompositeSafeCloseable(@NonNull SafeCloseable... children) {
+ mChildren = children;
+ }
+
+ @Override
+ public void close() {
+ // Close in reverse order.
+ for (int i = mChildren.length - 1; i >= 0; --i) {
+ mChildren[i].close();
+ }
+ }
+}
diff --git a/media/java/android/media/permission/IdentityContext.java b/media/java/android/media/permission/IdentityContext.java
new file mode 100644
index 000000000000..d10654fbe684
--- /dev/null
+++ b/media/java/android/media/permission/IdentityContext.java
@@ -0,0 +1,100 @@
+/*
+ * 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.media.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * An RAII-style object, used to establish a scope in which a single identity is part of the
+ * context. This is used in order to avoid having to explicitly pass identity information through
+ * deep call-stacks.
+ * <p>
+ * Intended usage:
+ * <pre>
+ * void caller() {
+ * Identity originator = ...;
+ * try (SafeCloseable ignored = IdentityContext.create(originator)) {
+ * // Within this scope the context is established.
+ * callee();
+ * }
+ * // Outside the scope the context is restored to its prior state.
+ *
+ * void callee() {
+ * // Here we can access the identity without having to explicitly take it as an argument.
+ * // This is true even if this were a deeply nested call.
+ * Identity originator = IdentityContext.getNonNull();
+ * ...
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public class IdentityContext implements SafeCloseable {
+ private static ThreadLocal<Identity> sThreadLocalIdentity = new ThreadLocal<>();
+ private @Nullable Identity mPrior = get();
+
+ /**
+ * Create a scoped identity context.
+ *
+ * @param identity The identity to establish with the scope.
+ * @return A {@link SafeCloseable}, to be used in a try-with-resources block to establish a
+ * scope.
+ */
+ public static @NonNull
+ SafeCloseable create(@Nullable Identity identity) {
+ return new IdentityContext(identity);
+ }
+
+ /**
+ * Get the current identity context.
+ *
+ * @return The identity, or null if it has not been established.
+ */
+ public static @Nullable
+ Identity get() {
+ return sThreadLocalIdentity.get();
+ }
+
+ /**
+ * Get the current identity context. Throws a {@link NullPointerException} if it has not been
+ * established.
+ *
+ * @return The identity.
+ */
+ public static @NonNull
+ Identity getNonNull() {
+ Identity result = get();
+ if (result == null) {
+ throw new NullPointerException("Identity context is not set");
+ }
+ return result;
+ }
+
+ private IdentityContext(@Nullable Identity identity) {
+ set(identity);
+ }
+
+ @Override
+ public void close() {
+ set(mPrior);
+ }
+
+ private static void set(@Nullable Identity identity) {
+ sThreadLocalIdentity.set(identity);
+ }
+}
diff --git a/media/java/android/media/permission/PermissionUtil.java b/media/java/android/media/permission/PermissionUtil.java
new file mode 100644
index 000000000000..458f11243f68
--- /dev/null
+++ b/media/java/android/media/permission/PermissionUtil.java
@@ -0,0 +1,250 @@
+/*
+ * 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.media.permission;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+
+import java.util.Objects;
+
+/**
+ * This module provides some utility methods for facilitating our permission enforcement patterns.
+ * <p>
+ * <h1>Intended usage:</h1>
+ * Close to the client-facing edge of the server, first authenticate the client, using {@link
+ * #establishIdentityDirect(Identity)}, or {@link #establishIdentityIndirect(Context, String,
+ * Identity, Identity)}, depending on whether the client is trying to authenticate as the
+ * originator or a middleman. Those methods will establish a scope with the originator in the
+ * {@link android.media.permission.IdentityContext} and a cleared binder calling identity.
+ * Typically there would be two distinct API methods for the two different options, and typically
+ * those API methods would be used to establish a client session which is associated with the
+ * originator for the lifetime of the session.
+ * <p>
+ * When performing an operation that requires permissions, use {@link
+ * #checkPermissionForPreflight(Context, Identity, String)} or {@link
+ * #checkPermissionForDataDelivery(Context, Identity, String, String)} on the originator
+ * identity. Note that this won't typically be the identity pulled from the {@link
+ * android.media.permission.IdentityContext}, since we are working with a session-based approach,
+ * the originator identity will be established once upon creation of a session, and then all
+ * interactions with this session will using the identity attached to the session. This also covers
+ * performing checks prior to invoking client callbacks for data delivery.
+ *
+ * @hide
+ */
+public class PermissionUtil {
+ /**
+ * Authenticate an originator, where the binder call is coming from a middleman.
+ *
+ * The middleman is expected to hold a special permission to act as such, or else a
+ * {@link SecurityException} will be thrown. If the call succeeds:
+ * <ul>
+ * <li>The passed middlemanIdentity argument will have its uid/pid fields overridden with
+ * those provided by binder.
+ * <li>An {@link SafeCloseable} is returned, used to established a scope in which the
+ * originator identity is available via {@link android.media.permission.IdentityContext}
+ * and in which the binder
+ * calling ID is cleared.
+ * </ul>
+ * Example usage:
+ * <pre>
+ * try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(...)) {
+ * // Within this scope we have the identity context established, and the binder calling
+ * // identity cleared.
+ * ...
+ * Identity originator = IdentityContext.getNonNull();
+ * ...
+ * }
+ * // outside the scope, everything is back to the prior state.
+ * </pre>
+ * <p>
+ * <b>Important note:</b> The binder calling ID will be used to securely establish the identity
+ * of the middleman. However, if the middleman is on the same process as the server,
+ * the middleman must remember to clear the binder calling identity, or else the binder calling
+ * ID will reflect the process calling into the middleman, not the middleman process itself. If
+ * the middleman itself is using this API, this is typically not an issue, since this method
+ * will take care of that.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param middlemanPermission The permission that will be checked in order to authorize the
+ * middleman to act as such (i.e. be trusted to convey the
+ * originator
+ * identity reliably).
+ * @param middlemanIdentity The identity of the middleman.
+ * @param originatorIdentity The identity of the originator.
+ * @return A {@link SafeCloseable}, used to establish a scope, as mentioned above.
+ */
+ public static @NonNull
+ SafeCloseable establishIdentityIndirect(
+ @NonNull Context context,
+ @NonNull String middlemanPermission,
+ @NonNull Identity middlemanIdentity,
+ @NonNull Identity originatorIdentity) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(middlemanPermission);
+ Objects.requireNonNull(middlemanIdentity);
+ Objects.requireNonNull(originatorIdentity);
+
+ // Override uid/pid with the secure values provided by binder.
+ middlemanIdentity.pid = Binder.getCallingPid();
+ middlemanIdentity.uid = Binder.getCallingUid();
+
+ // Authorize middleman to delegate identity.
+ context.enforcePermission(middlemanPermission, middlemanIdentity.pid,
+ middlemanIdentity.uid,
+ String.format("Middleman must have the %s permision.", middlemanPermission));
+ return new CompositeSafeCloseable(IdentityContext.create(originatorIdentity),
+ ClearCallingIdentityContext.create());
+ }
+
+ /**
+ * Authenticate an originator, where the binder call is coming directly from the originator.
+ *
+ * If the call succeeds:
+ * <ul>
+ * <li>The passed originatorIdentity argument will have its uid/pid fields overridden with
+ * those provided by binder.
+ * <li>A {@link SafeCloseable} is returned, used to established a scope in which the
+ * originator identity is available via {@link IdentityContext} and in which the binder
+ * calling ID is cleared.
+ * </ul>
+ * Example usage:
+ * <pre>
+ * try (AutoClosable ignored = PermissionUtil.establishIdentityDirect(...)) {
+ * // Within this scope we have the identity context established, and the binder calling
+ * // identity cleared.
+ * ...
+ * Identity originator = IdentityContext.getNonNull();
+ * ...
+ * }
+ * // outside the scope, everything is back to the prior state.
+ * </pre>
+ * <p>
+ * <b>Important note:</b> The binder calling ID will be used to securely establish the identity
+ * of the client. However, if the client is on the same process as the server, and is itself a
+ * binder server, it must remember to clear the binder calling identity, or else the binder
+ * calling ID will reflect the process calling into the client, not the client process itself.
+ * If the client itself is using this API, this is typically not an issue, since this method
+ * will take care of that.
+ *
+ * @param originatorIdentity The identity of the originator.
+ * @return A {@link SafeCloseable}, used to establish a scope, as mentioned above.
+ */
+ public static @NonNull
+ SafeCloseable establishIdentityDirect(@NonNull Identity originatorIdentity) {
+ Objects.requireNonNull(originatorIdentity);
+
+ originatorIdentity.uid = Binder.getCallingUid();
+ originatorIdentity.pid = Binder.getCallingPid();
+ return new CompositeSafeCloseable(
+ IdentityContext.create(originatorIdentity),
+ ClearCallingIdentityContext.create());
+ }
+
+ /**
+ * Checks whether the given identity has the given permission to receive data.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ * @param reason The reason why we're requesting the permission, for auditing purposes.
+ * @return The permission check result which is either
+ * {@link PermissionChecker#PERMISSION_GRANTED}
+ * or {@link PermissionChecker#PERMISSION_SOFT_DENIED} or
+ * {@link PermissionChecker#PERMISSION_HARD_DENIED}.
+ */
+ public static int checkPermissionForDataDelivery(@NonNull Context context,
+ @NonNull Identity identity,
+ @NonNull String permission,
+ @NonNull String reason) {
+ return PermissionChecker.checkPermissionForDataDelivery(context, permission,
+ identity.pid, identity.uid, identity.packageName, identity.attributionTag,
+ reason);
+ }
+
+ /**
+ * Checks whether the given identity has the given permission to receive data.
+ *
+ * This variant ignores the proc-state for the sake of the check, i.e. overrides any
+ * restrictions that apply specifically to apps running in the background.
+ *
+ * TODO(ytai): This is a temporary hack until we have permissions that are specifically intended
+ * for background microphone access.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ * @param reason The reason why we're requesting the permission, for auditing purposes.
+ * @return The permission check result which is either
+ * {@link PermissionChecker#PERMISSION_GRANTED}
+ * or {@link PermissionChecker#PERMISSION_SOFT_DENIED} or
+ * {@link PermissionChecker#PERMISSION_HARD_DENIED}.
+ */
+ public static int checkPermissionForDataDeliveryIgnoreProcState(@NonNull Context context,
+ @NonNull Identity identity,
+ @NonNull String permission,
+ @NonNull String reason) {
+ if (context.checkPermission(permission, identity.pid, identity.uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ return PermissionChecker.PERMISSION_HARD_DENIED;
+ }
+
+ String appOpOfPermission = AppOpsManager.permissionToOp(permission);
+ if (appOpOfPermission == null) {
+ // not platform defined
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+
+ String packageName = identity.packageName;
+ if (packageName == null) {
+ String[] packageNames = context.getPackageManager().getPackagesForUid(identity.uid);
+ if (packageNames != null && packageNames.length > 0) {
+ packageName = packageNames[0];
+ }
+ }
+
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+
+ int appOpMode = appOpsManager.unsafeCheckOpRawNoThrow(appOpOfPermission, identity.uid,
+ packageName);
+ if (appOpMode == AppOpsManager.MODE_ALLOWED) {
+ return PermissionChecker.PERMISSION_GRANTED;
+ }
+ return PermissionChecker.PERMISSION_SOFT_DENIED;
+ }
+
+ /**
+ * Checks whether the given identity has the given permission.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ * @return The permission check result which is either
+ * {@link PermissionChecker#PERMISSION_GRANTED}
+ * or {@link PermissionChecker#PERMISSION_SOFT_DENIED} or
+ * {@link PermissionChecker#PERMISSION_HARD_DENIED}.
+ */
+ public static int checkPermissionForPreflight(@NonNull Context context,
+ @NonNull Identity identity,
+ @NonNull String permission) {
+ return PermissionChecker.checkPermissionForPreflight(context, permission,
+ identity.pid, identity.uid, identity.packageName);
+ }
+}
diff --git a/media/java/android/media/permission/SafeCloseable.java b/media/java/android/media/permission/SafeCloseable.java
new file mode 100644
index 000000000000..8ec15b18bd37
--- /dev/null
+++ b/media/java/android/media/permission/SafeCloseable.java
@@ -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.
+ */
+
+package android.media.permission;
+
+/**
+ * An {@link AutoCloseable} that doesn't throw on {@link #close()}.
+ *
+ * @hide
+ */
+public interface SafeCloseable extends AutoCloseable {
+ @Override
+ void close();
+}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 5fd8f7318543..1557ea632b66 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -44,11 +44,11 @@ interface ISessionManager {
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
- in MediaSession.Token sessionToken, in KeyEvent keyEvent);
+ in KeyEvent keyEvent, in MediaSession.Token sessionToken);
void dispatchVolumeKeyEvent(String packageName, String opPackageName, boolean asSystemService,
in KeyEvent keyEvent, int stream, boolean musicOnly);
void dispatchVolumeKeyEventToSessionAsSystemService(String packageName, String opPackageName,
- in MediaSession.Token sessionToken, in KeyEvent keyEvent);
+ in KeyEvent keyEvent, in MediaSession.Token sessionToken);
void dispatchAdjustVolume(String packageName, String opPackageName, int suggestedStream,
int delta, int flags);
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 24dacc48f28e..3af2e17960ae 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -472,7 +472,8 @@ public final class MediaController {
/**
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(publicAlternatives = "Check equality of {@link #getSessionToken() tokens}"
+ + "instead.")
public boolean controlsSameSession(MediaController other) {
if (other == null) return false;
return mSessionBinder.asBinder() == other.getSessionBinder().asBinder();
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 624607b61b8f..e17e069d61d0 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -464,7 +464,9 @@ public final class MediaSession {
int fields = 0;
MediaDescription description = null;
if (metadata != null) {
- metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
+ metadata = new MediaMetadata.Builder(metadata)
+ .setBitmapDimensionLimit(mMaxBitmapSize)
+ .build();
if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 9494295e4bd9..b7b9e4ba6f40 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
@@ -262,18 +263,14 @@ public final class MediaSessionManager {
}
/**
- * Add a listener to be notified when the list of active sessions
- * changes.This requires the
- * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
- * the calling app. You may also retrieve this list if your app is an
- * enabled notification listener using the
- * {@link NotificationListenerService} APIs, in which case you must pass the
- * {@link ComponentName} of your enabled listener. Updates will be posted to
- * the thread that registered the listener.
+ * Add a listener to be notified when the list of active sessions changes. This requires the
+ * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be held by the calling
+ * app. You may also retrieve this list if your app is an enabled notification listener using
+ * the {@link NotificationListenerService} APIs, in which case you must pass the
+ * {@link ComponentName} of your enabled listener.
*
* @param sessionListener The listener to add.
- * @param notificationListener The enabled notification listener component.
- * May be null.
+ * @param notificationListener The enabled notification listener component. May be null.
*/
public void addOnActiveSessionsChangedListener(
@NonNull OnActiveSessionsChangedListener sessionListener,
@@ -282,18 +279,15 @@ public final class MediaSessionManager {
}
/**
- * Add a listener to be notified when the list of active sessions
- * changes.This requires the
- * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
- * the calling app. You may also retrieve this list if your app is an
- * enabled notification listener using the
- * {@link NotificationListenerService} APIs, in which case you must pass the
- * {@link ComponentName} of your enabled listener. Updates will be posted to
- * the handler specified or to the caller's thread if the handler is null.
+ * Add a listener to be notified when the list of active sessions changes. This requires the
+ * {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be held by the calling
+ * app. You may also retrieve this list if your app is an enabled notification listener using
+ * the {@link NotificationListenerService} APIs, in which case you must pass the
+ * {@link ComponentName} of your enabled listener. Updates will be posted to the handler
+ * specified or to the caller's thread if the handler is null.
*
* @param sessionListener The listener to add.
- * @param notificationListener The enabled notification listener component.
- * May be null.
+ * @param notificationListener The enabled notification listener component. May be null.
* @param handler The handler to post events to.
*/
public void addOnActiveSessionsChangedListener(
@@ -304,21 +298,24 @@ public final class MediaSessionManager {
}
/**
- * Add a listener to be notified when the list of active sessions
- * changes.This requires the
- * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by
- * the calling app. You may also retrieve this list if your app is an
- * enabled notification listener using the
- * {@link NotificationListenerService} APIs, in which case you must pass the
- * {@link ComponentName} of your enabled listener.
+ * Add a listener to be notified when the list of active sessions changes for the given user.
+ * The calling app must have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission if it wants to call this method for a user that is not running the app.
+ * <p>
+ * This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
+ * held by the calling app. You may also retrieve this list if your app is an enabled
+ * notification listener using the {@link NotificationListenerService} APIs, in which case you
+ * must pass the {@link ComponentName} of your enabled listener. Updates will be posted to the
+ * handler specified or to the caller's thread if the handler is null.
*
* @param sessionListener The listener to add.
- * @param notificationListener The enabled notification listener component.
- * May be null.
+ * @param notificationListener The enabled notification listener component. May be null.
* @param userId The userId to listen for changes on.
* @param handler The handler to post updates on.
* @hide
*/
+ @SuppressLint({"ExecutorRegistration", "SamShouldBeLast"})
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void addOnActiveSessionsChangedListener(
@NonNull OnActiveSessionsChangedListener sessionListener,
@Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
@@ -496,43 +493,46 @@ public final class MediaSessionManager {
}
/**
- * Send a media key event. The receiver will be selected automatically.
+ * Sends a media key event. The receiver will be selected automatically.
*
- * @param keyEvent The KeyEvent to send.
+ * @param keyEvent the key event to send
* @hide
*/
public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
- dispatchMediaKeyEvent(keyEvent, false);
+ dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, /*needWakeLock=*/false);
}
/**
- * Send a media key event. The receiver will be selected automatically.
+ * Sends a media key event. The receiver will be selected automatically.
*
- * @param keyEvent The KeyEvent to send.
- * @param needWakeLock True if a wake lock should be held while sending the key.
+ * @param keyEvent the key event to send
+ * @param needWakeLock true if a wake lock should be held while sending the key
* @hide
*/
public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
- dispatchMediaKeyEventInternal(false, keyEvent, needWakeLock);
+ dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/false, needWakeLock);
}
/**
- * Send a media key event as system component. The receiver will be selected automatically.
+ * Sends a media key event as system service. The receiver will be selected automatically.
* <p>
* Should be only called by the {@link com.android.internal.policy.PhoneWindow} or
* {@link android.view.FallbackEventHandler} when the foreground activity didn't consume the key
* from the hardware devices.
*
- * @param keyEvent The KeyEvent to send.
+ * @param keyEvent the key event to send
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) {
- dispatchMediaKeyEventInternal(true, keyEvent, false);
+ dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/true, /*needWakeLock=*/false);
}
- private void dispatchMediaKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
+ private void dispatchMediaKeyEventInternal(KeyEvent keyEvent, boolean asSystemService,
boolean needWakeLock) {
+ if (keyEvent == null) {
+ throw new NullPointerException("keyEvent shouldn't be null");
+ }
try {
mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,
needWakeLock);
@@ -542,31 +542,31 @@ public final class MediaSessionManager {
}
/**
- * Dispatches the media button event as system service to the session.
+ * Sends a media key event as system service to the given session.
* <p>
* Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the
* foreground activity didn't consume the key from the hardware devices.
*
- * @param sessionToken session token
- * @param keyEvent media key event
+ * @param keyEvent the key event to send
+ * @param sessionToken the session token to which the key event should be dispatched
* @return {@code true} if the event was sent to the session, {@code false} otherwise
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public boolean dispatchMediaKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
- @NonNull KeyEvent keyEvent) {
+ public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull KeyEvent keyEvent,
+ @NonNull MediaSession.Token sessionToken) {
if (sessionToken == null) {
- throw new IllegalArgumentException("sessionToken shouldn't be null");
+ throw new NullPointerException("sessionToken shouldn't be null");
}
if (keyEvent == null) {
- throw new IllegalArgumentException("keyEvent shouldn't be null");
+ throw new NullPointerException("keyEvent shouldn't be null");
}
if (!KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
return false;
}
try {
- return mService.dispatchMediaKeyEventToSessionAsSystemService(mContext.getPackageName(),
- sessionToken, keyEvent);
+ return mService.dispatchMediaKeyEventToSessionAsSystemService(
+ mContext.getPackageName(), keyEvent, sessionToken);
} catch (RemoteException e) {
Log.e(TAG, "Failed to send key event.", e);
}
@@ -574,13 +574,16 @@ public final class MediaSessionManager {
}
/**
- * Send a volume key event. The receiver will be selected automatically.
+ * Sends a volume key event. The receiver will be selected automatically.
*
- * @param keyEvent The volume KeyEvent to send.
+ * @param keyEvent the volume key event to send
+ * @param streamType type of stream
+ * @param musicOnly true if key event should only be sent to music stream
* @hide
*/
- public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
- dispatchVolumeKeyEventInternal(false, keyEvent, stream, musicOnly);
+ public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int streamType,
+ boolean musicOnly) {
+ dispatchVolumeKeyEventInternal(keyEvent, streamType, musicOnly, /*asSystemService=*/false);
}
/**
@@ -595,17 +598,21 @@ public final class MediaSessionManager {
* Valid stream types include {@link AudioManager.PublicStreamTypes} and
* {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
*
- * @param keyEvent volume key event
+ * @param keyEvent the volume key event to send
* @param streamType type of stream
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public void dispatchVolumeKeyEventAsSystemService(@NonNull KeyEvent keyEvent, int streamType) {
- dispatchVolumeKeyEventInternal(true, keyEvent, streamType, false);
+ dispatchVolumeKeyEventInternal(keyEvent, streamType, /*musicOnly=*/false,
+ /*asSystemService=*/true);
}
- private void dispatchVolumeKeyEventInternal(boolean asSystemService, @NonNull KeyEvent keyEvent,
- int stream, boolean musicOnly) {
+ private void dispatchVolumeKeyEventInternal(@NonNull KeyEvent keyEvent, int stream,
+ boolean musicOnly, boolean asSystemService) {
+ if (keyEvent == null) {
+ throw new NullPointerException("keyEvent shouldn't be null");
+ }
try {
mService.dispatchVolumeKeyEvent(mContext.getPackageName(), mContext.getOpPackageName(),
asSystemService, keyEvent, stream, musicOnly);
@@ -620,22 +627,22 @@ public final class MediaSessionManager {
* Should be only called by the {@link com.android.internal.policy.PhoneWindow} when the
* foreground activity didn't consume the key from the hardware devices.
*
- * @param sessionToken sessionToken
- * @param keyEvent volume key event
+ * @param keyEvent the volume key event to send
+ * @param sessionToken the session token to which the key event should be dispatched
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public void dispatchVolumeKeyEventAsSystemService(@NonNull MediaSession.Token sessionToken,
- @NonNull KeyEvent keyEvent) {
+ public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull KeyEvent keyEvent,
+ @NonNull MediaSession.Token sessionToken) {
if (sessionToken == null) {
- throw new IllegalArgumentException("sessionToken shouldn't be null");
+ throw new NullPointerException("sessionToken shouldn't be null");
}
if (keyEvent == null) {
- throw new IllegalArgumentException("keyEvent shouldn't be null");
+ throw new NullPointerException("keyEvent shouldn't be null");
}
try {
mService.dispatchVolumeKeyEventToSessionAsSystemService(mContext.getPackageName(),
- mContext.getOpPackageName(), sessionToken, keyEvent);
+ mContext.getOpPackageName(), keyEvent, sessionToken);
} catch (RemoteException e) {
Log.wtf(TAG, "Error calling dispatchVolumeKeyEventAsSystemService", e);
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 08953392ca18..16a517b72119 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -35,7 +35,7 @@ import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.internal.app.ISoundTriggerService;
+import com.android.internal.app.ISoundTriggerSession;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -64,7 +64,7 @@ public final class SoundTriggerDetector {
private final Object mLock = new Object();
- private final ISoundTriggerService mSoundTriggerService;
+ private final ISoundTriggerSession mSoundTriggerSession;
private final UUID mSoundModelId;
private final Callback mCallback;
private final Handler mHandler;
@@ -266,9 +266,9 @@ public final class SoundTriggerDetector {
* This class should be constructed by the {@link SoundTriggerManager}.
* @hide
*/
- SoundTriggerDetector(ISoundTriggerService soundTriggerService, UUID soundModelId,
+ SoundTriggerDetector(ISoundTriggerSession soundTriggerSession, UUID soundModelId,
@NonNull Callback callback, @Nullable Handler handler) {
- mSoundTriggerService = soundTriggerService;
+ mSoundTriggerSession = soundTriggerSession;
mSoundModelId = soundModelId;
mCallback = callback;
if (handler == null) {
@@ -305,7 +305,7 @@ public final class SoundTriggerDetector {
int status;
try {
- status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
+ status = mSoundTriggerSession.startRecognition(new ParcelUuid(mSoundModelId),
mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
allowMultipleTriggers, null, null, audioCapabilities));
} catch (RemoteException e) {
@@ -321,7 +321,7 @@ public final class SoundTriggerDetector {
public boolean stopRecognition() {
int status = STATUS_OK;
try {
- status = mSoundTriggerService.stopRecognition(new ParcelUuid(mSoundModelId),
+ status = mSoundTriggerSession.stopRecognition(new ParcelUuid(mSoundModelId),
mRecognitionCallback);
} catch (RemoteException e) {
return false;
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 7d51b104a47d..0ff8d9e96220 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -33,6 +34,10 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.Identity;
+import android.media.permission.SafeCloseable;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
@@ -41,6 +46,7 @@ import android.provider.Settings;
import android.util.Slog;
import com.android.internal.app.ISoundTriggerService;
+import com.android.internal.app.ISoundTriggerSession;
import com.android.internal.util.Preconditions;
import java.util.HashMap;
@@ -61,7 +67,7 @@ public final class SoundTriggerManager {
private static final String TAG = "SoundTriggerManager";
private final Context mContext;
- private final ISoundTriggerService mSoundTriggerService;
+ private final ISoundTriggerSession mSoundTriggerSession;
// Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
// the createSoundTriggerDetector() call.
@@ -74,7 +80,20 @@ public final class SoundTriggerManager {
if (DBG) {
Slog.i(TAG, "SoundTriggerManager created.");
}
- mSoundTriggerService = soundTriggerService;
+ try {
+ // This assumes that whoever is calling this ctor is the originator of the operations,
+ // as opposed to a service acting on behalf of a separate identity.
+ // Services acting on behalf of some other identity should not be using this class at
+ // all, but rather directly connect to the server and attach with explicit credentials.
+ Identity originatorIdentity = new Identity();
+ originatorIdentity.packageName = ActivityThread.currentOpPackageName();
+
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mSoundTriggerSession = soundTriggerService.attachAsOriginator(originatorIdentity);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
mContext = context;
mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
}
@@ -85,7 +104,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public void updateModel(Model model) {
try {
- mSoundTriggerService.updateSoundModel(model.getGenericSoundModel());
+ mSoundTriggerSession.updateSoundModel(model.getGenericSoundModel());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -102,7 +121,7 @@ public final class SoundTriggerManager {
public Model getModel(UUID soundModelId) {
try {
GenericSoundModel model =
- mSoundTriggerService.getSoundModel(new ParcelUuid(soundModelId));
+ mSoundTriggerSession.getSoundModel(new ParcelUuid(soundModelId));
if (model == null) {
return null;
}
@@ -119,7 +138,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public void deleteModel(UUID soundModelId) {
try {
- mSoundTriggerService.deleteSoundModel(new ParcelUuid(soundModelId));
+ mSoundTriggerSession.deleteSoundModel(new ParcelUuid(soundModelId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -149,7 +168,7 @@ public final class SoundTriggerManager {
if (oldInstance != null) {
// Shutdown old instance.
}
- SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerService,
+ SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerSession,
soundModelId, callback, handler);
mReceiverInstanceMap.put(soundModelId, newInstance);
return newInstance;
@@ -309,10 +328,10 @@ public final class SoundTriggerManager {
try {
switch (soundModel.getType()) {
case SoundModel.TYPE_GENERIC_SOUND:
- return mSoundTriggerService.loadGenericSoundModel(
+ return mSoundTriggerSession.loadGenericSoundModel(
(GenericSoundModel) soundModel);
case SoundModel.TYPE_KEYPHRASE:
- return mSoundTriggerService.loadKeyphraseSoundModel(
+ return mSoundTriggerSession.loadKeyphraseSoundModel(
(KeyphraseSoundModel) soundModel);
default:
Slog.e(TAG, "Unkown model type");
@@ -351,7 +370,7 @@ public final class SoundTriggerManager {
Preconditions.checkNotNull(config);
try {
- return mSoundTriggerService.startRecognitionForService(new ParcelUuid(soundModelId),
+ return mSoundTriggerSession.startRecognitionForService(new ParcelUuid(soundModelId),
params, detectionService, config);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -369,7 +388,7 @@ public final class SoundTriggerManager {
return STATUS_ERROR;
}
try {
- return mSoundTriggerService.stopRecognitionForService(new ParcelUuid(soundModelId));
+ return mSoundTriggerSession.stopRecognitionForService(new ParcelUuid(soundModelId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -386,7 +405,7 @@ public final class SoundTriggerManager {
return STATUS_ERROR;
}
try {
- return mSoundTriggerService.unloadSoundModel(
+ return mSoundTriggerSession.unloadSoundModel(
new ParcelUuid(soundModelId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -404,7 +423,7 @@ public final class SoundTriggerManager {
return false;
}
try {
- return mSoundTriggerService.isRecognitionActive(
+ return mSoundTriggerSession.isRecognitionActive(
new ParcelUuid(soundModelId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -439,7 +458,7 @@ public final class SoundTriggerManager {
return STATUS_ERROR;
}
try {
- return mSoundTriggerService.getModelState(new ParcelUuid(soundModelId));
+ return mSoundTriggerSession.getModelState(new ParcelUuid(soundModelId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -455,7 +474,7 @@ public final class SoundTriggerManager {
public SoundTrigger.ModuleProperties getModuleProperties() {
try {
- return mSoundTriggerService.getModuleProperties();
+ return mSoundTriggerSession.getModuleProperties();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -481,7 +500,7 @@ public final class SoundTriggerManager {
public int setParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam, int value) {
try {
- return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
+ return mSoundTriggerSession.setParameter(new ParcelUuid(soundModelId), modelParam,
value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -504,7 +523,7 @@ public final class SoundTriggerManager {
public int getParameter(@NonNull UUID soundModelId,
@ModelParams int modelParam) {
try {
- return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
+ return mSoundTriggerSession.getParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -524,7 +543,7 @@ public final class SoundTriggerManager {
public ModelParamRange queryParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam) {
try {
- return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
+ return mSoundTriggerSession.queryParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
deleted file mode 100644
index 06c39071cdf5..000000000000
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.media.soundtrigger_middleware;
-
-import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-
-/**
- * Main entry point into this module.
- *
- * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
- * attach to either one of them in order to use it.
- *
- * {@hide}
- */
-interface ISoundTriggerMiddlewareService {
- /**
- * Query the available modules and their capabilities.
- */
- SoundTriggerModuleDescriptor[] listModules();
-
- /**
- * Attach to one of the available modules.
- * listModules() must be called prior to calling this method and the provided handle must be
- * one of the handles from the returned list.
- */
- ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
-} \ No newline at end of file
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 1fbb67260895..ed99fad5088a 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -25,6 +25,7 @@ import android.media.tv.ITvInputClient;
import android.media.tv.ITvInputHardware;
import android.media.tv.ITvInputHardwareCallback;
import android.media.tv.ITvInputManagerCallback;
+import android.media.tv.TunedInfo;
import android.media.tv.TvContentRatingSystemInfo;
import android.media.tv.TvInputHardwareInfo;
import android.media.tv.TvInputInfo;
@@ -88,6 +89,8 @@ interface ITvInputManager {
void timeShiftSetPlaybackParams(in IBinder sessionToken, in PlaybackParams params, int userId);
void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId);
+ List<TunedInfo> getCurrentTunedInfos(int userId);
+
// For the recording session
void startRecording(in IBinder sessionToken, in Uri programUri, in Bundle params, int userId);
void stopRecording(in IBinder sessionToken, int userId);
diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl
index 0f8bf2fb3a1d..3128ba78bc3e 100644
--- a/media/java/android/media/tv/ITvInputManagerCallback.aidl
+++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.media.tv.TunedInfo;
import android.media.tv.TvInputInfo;
/**
@@ -28,4 +29,5 @@ oneway interface ITvInputManagerCallback {
void onInputUpdated(in String inputId);
void onInputStateChanged(in String inputId, int state);
void onTvInputInfoUpdated(in TvInputInfo TvInputInfo);
+ void onCurrentTunedInfosUpdated(in List<TunedInfo> currentTunedInfos);
}
diff --git a/media/java/android/media/tv/TunedInfo.aidl b/media/java/android/media/tv/TunedInfo.aidl
new file mode 100644
index 000000000000..b7c1d529b37a
--- /dev/null
+++ b/media/java/android/media/tv/TunedInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.media.tv;
+
+parcelable TunedInfo;
diff --git a/media/java/android/media/tv/TunedInfo.java b/media/java/android/media/tv/TunedInfo.java
new file mode 100644
index 000000000000..6fc57841ed3b
--- /dev/null
+++ b/media/java/android/media/tv/TunedInfo.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 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.media.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+
+/**
+ * Contains information about a {@link TvInputService.Session} that is currently tuned to a channel
+ * or pass-through input.
+ * @hide
+ */
+public final class TunedInfo implements Parcelable {
+ static final String TAG = "TunedInfo";
+
+ /**
+ * App tag for {@link #getAppTag()}: the corresponding application of the channel is the same as
+ * the caller.
+ * <p>{@link #getAppType()} returns {@link #APP_TYPE_SELF} if and only if the app tag is
+ * {@link #APP_TAG_SELF}.
+ */
+ public static final int APP_TAG_SELF = 0;
+ /**
+ * App tag for {@link #getAppType()}: the corresponding application of the channel is the same
+ * as the caller.
+ * <p>{@link #getAppType()} returns {@link #APP_TYPE_SELF} if and only if the app tag is
+ * {@link #APP_TAG_SELF}.
+ */
+ public static final int APP_TYPE_SELF = 1;
+ /**
+ * App tag for {@link #getAppType()}: the corresponding app of the channel is a system
+ * application.
+ */
+ public static final int APP_TYPE_SYSTEM = 2;
+ /**
+ * App tag for {@link #getAppType()}: the corresponding app of the channel is not a system
+ * application.
+ */
+ public static final int APP_TYPE_NON_SYSTEM = 3;
+
+ /** @hide */
+ @IntDef(prefix = "APP_TYPE_", value = {APP_TYPE_SELF, APP_TYPE_SYSTEM, APP_TYPE_NON_SYSTEM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AppType {}
+
+ public static final @NonNull Parcelable.Creator<TunedInfo> CREATOR =
+ new Parcelable.Creator<TunedInfo>() {
+ @Override
+ public TunedInfo createFromParcel(Parcel source) {
+ try {
+ return new TunedInfo(source);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TunedInfo from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TunedInfo[] newArray(int size) {
+ return new TunedInfo[size];
+ }
+ };
+
+
+ private final String mInputId;
+ @Nullable private final Uri mChannelUri;
+ private final boolean mIsRecordingSession;
+ private final boolean mIsForeground;
+ @AppType private final int mAppType;
+ private final int mAppTag;
+
+ /** @hide */
+ public TunedInfo(
+ String inputId, @Nullable Uri channelUri, boolean isRecordingSession,
+ boolean isForeground, @AppType int appType, int appTag) {
+ mInputId = inputId;
+ mChannelUri = channelUri;
+ mIsRecordingSession = isRecordingSession;
+ mIsForeground = isForeground;
+ mAppType = appType;
+ mAppTag = appTag;
+ }
+
+
+ private TunedInfo(Parcel source) {
+ mInputId = source.readString();
+ String uriString = source.readString();
+ mChannelUri = uriString == null ? null : Uri.parse(uriString);
+ mIsRecordingSession = (source.readInt() == 1);
+ mIsForeground = (source.readInt() == 1);
+ mAppType = source.readInt();
+ mAppTag = source.readInt();
+ }
+
+ /**
+ * Returns the TV input ID of the channel.
+ */
+ @NonNull
+ public String getInputId() {
+ return mInputId;
+ }
+
+ /**
+ * Returns the channel URI of the channel.
+ * <p>Returns {@code null} if it's a passthrough input or the permission is not granted.
+ */
+ @Nullable
+ public Uri getChannelUri() {
+ return mChannelUri;
+ }
+
+ /**
+ * Returns {@code true} if the channel session is a recording session.
+ * @see TvInputService.RecordingSession
+ */
+ public boolean isRecordingSession() {
+ return mIsRecordingSession;
+ }
+
+ /**
+ * Returns {@code true} if the application is a foreground application.
+ * @see android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+ */
+ public boolean isForeground() {
+ return mIsForeground;
+ }
+
+ /**
+ * Returns the app tag.
+ * <p>App tag is used to differentiate one app from another.
+ * {@link #APP_TAG_SELF} is for current app.
+ */
+ public int getAppTag() {
+ return mAppTag;
+ }
+
+ /**
+ * Returns the app type.
+ */
+ @AppType
+ public int getAppType() {
+ return mAppType;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mInputId);
+ String uriString = mChannelUri == null ? null : mChannelUri.toString();
+ dest.writeString(uriString);
+ dest.writeInt(mIsRecordingSession ? 1 : 0);
+ dest.writeInt(mIsForeground ? 1 : 0);
+ dest.writeInt(mAppType);
+ dest.writeInt(mAppTag);
+ }
+
+ @Override
+ public String toString() {
+ return "inputID=" + mInputId
+ + ";channelUri=" + mChannelUri
+ + ";isRecording=" + mIsRecordingSession
+ + ";isForeground=" + mIsForeground
+ + ";appType=" + mAppType
+ + ";appTag=" + mAppTag;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TunedInfo)) {
+ return false;
+ }
+
+ TunedInfo other = (TunedInfo) o;
+
+ return TextUtils.equals(mInputId, other.getInputId())
+ && Objects.equals(mChannelUri, other.mChannelUri)
+ && mIsRecordingSession == other.mIsRecordingSession
+ && mIsForeground == other.mIsForeground
+ && mAppType == other.mAppType
+ && mAppTag == other.mAppTag;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mInputId, mChannelUri, mIsRecordingSession, mIsForeground, mAppType, mAppTag);
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 98a01a4cb449..73539fb8a014 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -899,6 +899,16 @@ public final class TvInputManager {
*/
public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
}
+
+ /**
+ * This is called when the information about current tuned information has been updated.
+ *
+ * @param tunedInfos a list of {@link TunedInfo} objects of new tuned information.
+ * @hide
+ */
+ public void onCurrentTunedInfosUpdated(
+ @NonNull List<TunedInfo> tunedInfos) {
+ }
}
private static final class TvInputCallbackRecord {
@@ -958,6 +968,15 @@ public final class TvInputManager {
}
});
}
+
+ public void onCurrentTunedInfosUpdated(final List<TunedInfo> currentTunedInfos) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onCurrentTunedInfosUpdated(currentTunedInfos);
+ }
+ });
+ }
}
/**
@@ -1262,6 +1281,15 @@ public final class TvInputManager {
}
}
}
+
+ @Override
+ public void onCurrentTunedInfosUpdated(List<TunedInfo> currentTunedInfos) {
+ synchronized (mLock) {
+ for (TvInputCallbackRecord record : mCallbackRecords) {
+ record.onCurrentTunedInfosUpdated(currentTunedInfos);
+ }
+ }
+ }
};
try {
if (mService != null) {
@@ -1953,6 +1981,24 @@ public final class TvInputManager {
}
/**
+ * Returns the list of session information for {@link TvInputService.Session} that are
+ * currently in use.
+ * <p> Permission com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS is required to get
+ * the channel URIs. If the permission is not granted,
+ * {@link TunedInfo#getChannelUri()} returns {@code null}.
+ * @hide
+ */
+ @RequiresPermission("com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS")
+ @NonNull
+ public List<TunedInfo> getCurrentTunedInfos() {
+ try {
+ return mService.getCurrentTunedInfos(mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* The Session provides the per-session functionality of TV inputs.
* @hide
*/
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index e148d0e29b5a..867eab017a28 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -98,15 +98,44 @@ public class Tuner implements AutoCloseable {
* Invalid timestamp.
*
* <p>Returned by {@link android.media.tv.tuner.filter.TimeFilter#getSourceTime()},
- * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()}, or
- * {@link Tuner#getAvSyncTime(int)} when the requested timestamp is not available.
+ * {@link android.media.tv.tuner.filter.TimeFilter#getTimeStamp()},
+ * {@link Tuner#getAvSyncTime(int)} or {@link TsRecordEvent#getPts()} and
+ * {@link MmtpRecordEvent#getPts()} when the requested timestamp is not available.
*
* @see android.media.tv.tuner.filter.TimeFilter#getSourceTime()
* @see android.media.tv.tuner.filter.TimeFilter#getTimeStamp()
* @see Tuner#getAvSyncTime(int)
+ * @see android.media.tv.tuner.filter.TsRecordEvent#getPts()
+ * @see android.media.tv.tuner.filter.MmtpRecordEvent#getPts()
*/
- public static final long INVALID_TIMESTAMP = -1L;
-
+ public static final long INVALID_TIMESTAMP =
+ android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_PRESENTATION_TIME_STAMP;
+ /**
+ * Invalid mpu sequence number in MmtpRecordEvent.
+ *
+ * <p>Returned by {@link MmtpRecordEvent#getMpuSequenceNumber()} when the requested sequence
+ * number is not available.
+ *
+ * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMpuSequenceNumber()
+ */
+ public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM =
+ android.hardware.tv.tuner.V1_1.Constants.Constant
+ .INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM;
+ /**
+ * Invalid local transport stream id.
+ *
+ * <p>Returned by {@link #linkFrontendToCiCam(int)} when the requested failed
+ * or the hal implementation does not support the operation.
+ *
+ * @see #linkFrontendToCiCam(int)
+ */
+ public static final int INVALID_LTS_ID =
+ android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_LTS_ID;
+ /**
+ * Invalid 64-bit filter ID.
+ */
+ public static final long INVALID_FILTER_ID_64BIT =
+ android.hardware.tv.tuner.V1_1.Constants.Constant64Bit.INVALID_FILTER_ID_64BIT;
/** @hide */
@IntDef(prefix = "SCAN_TYPE_", value = {SCAN_TYPE_UNDEFINED, SCAN_TYPE_AUTO, SCAN_TYPE_BLIND})
@@ -204,6 +233,7 @@ public class Tuner implements AutoCloseable {
private final Context mContext;
private final TunerResourceManager mTunerResourceManager;
private final int mClientId;
+ private static int sTunerVersion = TunerVersionChecker.TUNER_VERSION_UNKNOWN;
private Frontend mFrontend;
private EventHandler mHandler;
@@ -255,6 +285,14 @@ public class Tuner implements AutoCloseable {
public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
@TvInputService.PriorityHintUseCaseType int useCase) {
nativeSetup();
+ sTunerVersion = nativeGetTunerVersion();
+ if (sTunerVersion == TunerVersionChecker.TUNER_VERSION_UNKNOWN) {
+ Log.e(TAG, "Unknown Tuner version!");
+ } else {
+ Log.d(TAG, "Current Tuner version is "
+ + TunerVersionChecker.getMajorVersion(sTunerVersion) + "."
+ + TunerVersionChecker.getMinorVersion(sTunerVersion) + ".");
+ }
mContext = context;
mTunerResourceManager = (TunerResourceManager)
context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
@@ -295,6 +333,11 @@ public class Tuner implements AutoCloseable {
}
/** @hide */
+ public static int getTunerVersion() {
+ return sTunerVersion;
+ }
+
+ /** @hide */
public List<Integer> getFrontendIds() {
return nativeGetFrontendIds();
}
@@ -419,6 +462,11 @@ public class Tuner implements AutoCloseable {
/**
* Native method to get all frontend IDs.
*/
+ private native int nativeGetTunerVersion();
+
+ /**
+ * Native method to get all frontend IDs.
+ */
private native List<Integer> nativeGetFrontendIds();
/**
@@ -437,7 +485,9 @@ public class Tuner implements AutoCloseable {
private native Integer nativeGetAvSyncHwId(Filter filter);
private native Long nativeGetAvSyncTime(int avSyncId);
private native int nativeConnectCiCam(int ciCamId);
+ private native int nativeLinkCiCam(int ciCamId);
private native int nativeDisconnectCiCam();
+ private native int nativeUnlinkCiCam(int ciCamId);
private native FrontendInfo nativeGetFrontendInfo(int id);
private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
private native TimeFilter nativeOpenTimeFilter();
@@ -760,6 +810,33 @@ public class Tuner implements AutoCloseable {
}
/**
+ * Link Conditional Access Modules (CAM) Frontend to support Common Interface (CI) by-pass mode.
+ *
+ * <p>It is used by the client to link CI-CAM to a Frontend. CI by-pass mode requires that
+ * the CICAM also receives the TS concurrently from the frontend when the Demux is receiving
+ * the TS directly from the frontend.
+ *
+ * <p>Use {@link #unlinkFrontendToCicam(int)} to disconnect.
+ *
+ * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+ * no-op and return {@link INVALID_LTS_ID}. Use {@link TunerVersionChecker.getTunerVersion()} to
+ * check the version.
+ *
+ * @param ciCamId specify CI-CAM Id to link.
+ * @return Local transport stream id when connection is successfully established. Failed
+ * operation returns {@link INVALID_LTS_ID}.
+ */
+ public int linkFrontendToCiCam(int ciCamId) {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+ "linkFrontendToCiCam")) {
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
+ return nativeLinkCiCam(ciCamId);
+ }
+ }
+ return INVALID_LTS_ID;
+ }
+
+ /**
* Disconnects Conditional Access Modules (CAM)
*
* <p>The demux will use the output from the frontend as the input after this call.
@@ -775,6 +852,28 @@ public class Tuner implements AutoCloseable {
}
/**
+ * Unlink Conditional Access Modules (CAM) Frontend.
+ *
+ * <p>It is used by the client to unlink CI-CAM to a Frontend.
+ *
+ * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+ * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+ *
+ * @param ciCamId specify CI-CAM Id to unlink.
+ * @return result status of the operation.
+ */
+ @Result
+ public int unlinkFrontendToCiCam(int ciCamId) {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+ "unlinkFrontendToCiCam")) {
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
+ return nativeUnlinkCiCam(ciCamId);
+ }
+ }
+ return RESULT_UNAVAILABLE;
+ }
+
+ /**
* Gets the frontend information.
*
* @return The frontend information. {@code null} if the operation failed.
diff --git a/media/java/android/media/tv/tuner/TunerVersionChecker.java b/media/java/android/media/tv/tuner/TunerVersionChecker.java
new file mode 100644
index 000000000000..739f87dd711d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/TunerVersionChecker.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class to check the currently running Tuner Hal implementation version.
+ *
+ * APIs that are not supported by the HAL implementation version would be no-op.
+ *
+ * @hide
+ */
+@TestApi
+@SystemApi
+public final class TunerVersionChecker {
+ private static final String TAG = "TunerVersionChecker";
+
+ private TunerVersionChecker() {}
+
+ /** @hide */
+ @IntDef(prefix = "TUNER_VERSION_", value = {TUNER_VERSION_UNKNOWN, TUNER_VERSION_1_0,
+ TUNER_VERSION_1_1})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TunerVersion {}
+ /**
+ * Unknown Tuner version.
+ */
+ public static final int TUNER_VERSION_UNKNOWN = 0;
+ /**
+ * Tuner version 1.0.
+ */
+ public static final int TUNER_VERSION_1_0 = (1 << 16);
+ /**
+ * Tuner version 1.1.
+ */
+ public static final int TUNER_VERSION_1_1 = ((1 << 16) | 1);
+
+ /**
+ * Get the current running Tuner version.
+ *
+ * @return Tuner version.
+ */
+ @TunerVersion
+ public static int getTunerVersion() {
+ return Tuner.getTunerVersion();
+ }
+
+ /**
+ * Check if the current running Tuner version supports the given version.
+ *
+ * <p>Note that we treat different major versions as unsupported among each other. If any
+ * feature could be supported across major versions, please use
+ * {@link #isHigherOrEqualVersionTo(int)} to check.
+ *
+ * @param version the version to support.
+ *
+ * @return true if the current version is under the same major version as the given version
+ * and has higher or the same minor version as the given version.
+ * @hide
+ */
+ @TestApi
+ public static boolean supportTunerVersion(@TunerVersion int version) {
+ int currentVersion = Tuner.getTunerVersion();
+ return isHigherOrEqualVersionTo(version)
+ && (getMajorVersion(version) == getMajorVersion(currentVersion));
+ }
+
+ /**
+ * Check if the current running Tuner version is higher than or equal to a given version.
+ *
+ * @param version the version to compare.
+ *
+ * @return true if the current version is higher or equal to the support version.
+ * @hide
+ */
+ @TestApi
+ public static boolean isHigherOrEqualVersionTo(@TunerVersion int version) {
+ int currentVersion = Tuner.getTunerVersion();
+ return currentVersion >= version;
+ }
+
+ /**
+ * Get the major version from a version number.
+ *
+ * @param version the version to be checked.
+ *
+ * @return the major version number.
+ * @hide
+ */
+ @TestApi
+ public static int getMajorVersion(@TunerVersion int version) {
+ return ((version & 0xFFFF0000) >>> 16);
+ }
+
+ /**
+ * Get the major version from a version number.
+ *
+ * @param version the version to be checked.
+ *
+ * @return the minor version number.
+ * @hide
+ */
+ @TestApi
+ public static int getMinorVersion(@TunerVersion int version) {
+ return (version & 0xFFFF);
+ }
+
+ /** @hide */
+ public static boolean checkHigherOrEqualVersionTo(
+ @TunerVersion int version, String methodName) {
+ if (!TunerVersionChecker.isHigherOrEqualVersionTo(version)) {
+ Log.e(TAG, "Current Tuner version "
+ + TunerVersionChecker.getMajorVersion(Tuner.getTunerVersion()) + "."
+ + TunerVersionChecker.getMinorVersion(Tuner.getTunerVersion())
+ + " does not support " + methodName + ".");
+ return false;
+ }
+ return true;
+ }
+
+ /** @hide */
+ public static boolean checkSupportVersion(@TunerVersion int version, String methodName) {
+ if (!TunerVersionChecker.supportTunerVersion(version)) {
+ Log.e(TAG, "Current Tuner version "
+ + TunerVersionChecker.getMajorVersion(Tuner.getTunerVersion()) + "."
+ + TunerVersionChecker.getMinorVersion(Tuner.getTunerVersion())
+ + " does not support " + methodName + ".");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index f0015b723edb..2f2d8f74c908 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -185,7 +185,7 @@ public class Filter implements AutoCloseable {
private long mNativeContext;
private FilterCallback mCallback;
private Executor mExecutor;
- private final int mId;
+ private final long mId;
private int mMainType;
private int mSubtype;
private Filter mSource;
@@ -196,6 +196,7 @@ public class Filter implements AutoCloseable {
private native int nativeConfigureFilter(
int type, int subType, FilterConfiguration settings);
private native int nativeGetId();
+ private native long nativeGetId64Bit();
private native int nativeSetDataSource(Filter source);
private native int nativeStartFilter();
private native int nativeStopFilter();
@@ -204,7 +205,7 @@ public class Filter implements AutoCloseable {
private native int nativeClose();
// Called by JNI
- private Filter(int id) {
+ private Filter(long id) {
mId = id;
}
@@ -269,6 +270,16 @@ public class Filter implements AutoCloseable {
}
/**
+ * Gets the 64-bit filter Id.
+ */
+ public long getId64Bit() {
+ synchronized (mLock) {
+ TunerUtils.checkResourceState(TAG, mIsClosed);
+ return nativeGetId64Bit();
+ }
+ }
+
+ /**
* Sets the filter's data source.
*
* A filter uses demux as data source by default. If the data was packetized
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index f54b686304c1..2649fcf5a642 100644
--- a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SystemApi;
+import android.media.tv.tuner.TunerVersionChecker;
/**
* Filter configuration for a IP filter.
@@ -28,20 +29,28 @@ import android.annotation.SystemApi;
*/
@SystemApi
public final class IpFilterConfiguration extends FilterConfiguration {
+ /**
+ * Undefined filter type.
+ */
+ public static final int INVALID_IP_FILTER_CONTEXT_ID =
+ android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_IP_FILTER_CONTEXT_ID;
+
private final byte[] mSrcIpAddress;
private final byte[] mDstIpAddress;
private final int mSrcPort;
private final int mDstPort;
private final boolean mPassthrough;
+ private final int mIpFilterContextId;
private IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
- int dstPort, boolean passthrough) {
+ int dstPort, boolean passthrough, int ipCid) {
super(settings);
mSrcIpAddress = srcAddr;
mDstIpAddress = dstAddr;
mSrcPort = srcPort;
mDstPort = dstPort;
mPassthrough = passthrough;
+ mIpFilterContextId = ipCid;
}
@Override
@@ -86,6 +95,15 @@ public final class IpFilterConfiguration extends FilterConfiguration {
public boolean isPassthrough() {
return mPassthrough;
}
+ /**
+ * Gets the ip filter context id. Default value is {@link #INVALID_IP_FILTER_CONTEXT_ID}.
+ *
+ * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would return
+ * default value. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+ */
+ public int getIpFilterContextId() {
+ return mIpFilterContextId;
+ }
/**
* Creates a builder for {@link IpFilterConfiguration}.
@@ -105,6 +123,7 @@ public final class IpFilterConfiguration extends FilterConfiguration {
private int mDstPort = 0;
private boolean mPassthrough = false;
private Settings mSettings;
+ private int mIpCid = INVALID_IP_FILTER_CONTEXT_ID;
private Builder() {
}
@@ -170,6 +189,21 @@ public final class IpFilterConfiguration extends FilterConfiguration {
}
/**
+ * Sets the ip filter context id. Default value is {@link #INVALID_IP_FILTER_CONTEXT_ID}.
+ *
+ * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
+ * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
+ */
+ @NonNull
+ public Builder setIpFilterContextId(int ipContextId) {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1, "setIpFilterContextId")) {
+ mIpCid = ipContextId;
+ }
+ return this;
+ }
+
+ /**
* Builds a {@link IpFilterConfiguration} object.
*/
@NonNull
@@ -180,8 +214,8 @@ public final class IpFilterConfiguration extends FilterConfiguration {
"The lengths of src and dst IP address must be 4 or 16 and must be the same."
+ "srcLength=" + ipAddrLength + ", dstLength=" + mDstIpAddress.length);
}
- return new IpFilterConfiguration(
- mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+ return new IpFilterConfiguration(mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort,
+ mDstPort, mPassthrough, mIpCid);
}
}
}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 466fa3ecb6e7..7060bd722d57 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -29,11 +29,15 @@ import android.media.tv.tuner.filter.RecordSettings.ScHevcIndex;
public class MmtpRecordEvent extends FilterEvent {
private final int mScHevcIndexMask;
private final long mDataLength;
+ private final int mMpuSequenceNumber;
+ private final long mPts;
// This constructor is used by JNI code only
- private MmtpRecordEvent(int scHevcIndexMask, long dataLength) {
+ private MmtpRecordEvent(int scHevcIndexMask, long dataLength, int mpuSequenceNumber, long pts) {
mScHevcIndexMask = scHevcIndexMask;
mDataLength = dataLength;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ mPts = pts;
}
/**
@@ -51,4 +55,20 @@ public class MmtpRecordEvent extends FilterEvent {
public long getDataLength() {
return mDataLength;
}
+
+ /**
+ * Get the MPU sequence number of the filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+
+ /**
+ * Get the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
+ * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
+ * the SC_HEVC.
+ */
+ public long getPts() {
+ return mPts;
+ }
}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 7a14bb863700..258e2f22427c 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -32,13 +32,15 @@ public class TsRecordEvent extends FilterEvent {
private final int mTsIndexMask;
private final int mScIndexMask;
private final long mDataLength;
+ private final long mPts;
// This constructor is used by JNI code only
- private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength) {
+ private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength, long pts) {
mPid = pid;
mTsIndexMask = tsIndexMask;
mScIndexMask = scIndexMask;
mDataLength = dataLength;
+ mPts = pts;
}
/**
@@ -72,4 +74,13 @@ public class TsRecordEvent extends FilterEvent {
public long getDataLength() {
return mDataLength;
}
+
+ /**
+ * Gets the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
+ * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
+ * the SC_HEVC.
+ */
+ public long getPts() {
+ return mPts;
+ }
}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 1e9cf2c24c7c..724965dac947 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -95,6 +95,11 @@ cc_library_shared {
"-Wunused",
"-Wunreachable-code",
],
+
+ // Workaround Clang LTO crash.
+ lto: {
+ never: true,
+ },
}
cc_library_shared {
@@ -137,6 +142,7 @@ cc_library_shared {
shared_libs: [
"android.hardware.graphics.bufferqueue@2.0",
"android.hardware.tv.tuner@1.0",
+ "android.hardware.tv.tuner@1.1",
"libandroid_runtime",
"libcutils",
"libfmq",
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 0b0e162d4faf..71c86cc7d42d 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2662,7 +2662,7 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I");
CHECK(gFields.cryptoInfoModeID != NULL);
- gFields.cryptoInfoPatternID = env->GetFieldID(clazz.get(), "pattern",
+ gFields.cryptoInfoPatternID = env->GetFieldID(clazz.get(), "mPattern",
"Landroid/media/MediaCodec$CryptoInfo$Pattern;");
CHECK(gFields.cryptoInfoPatternID != NULL);
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 2ef7b9e22b84..b6c47fca52f9 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -140,6 +140,27 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx,
fmt = applyFormatOverrides(fmt, containerFormat);
switch (fmt) {
case HAL_PIXEL_FORMAT_YCbCr_420_888:
+ // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+ if (buffer->width % 2 != 0) {
+ ALOGE("YCbCr_420_888: width (%d) should be a multiple of 2", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height % 2 != 0) {
+ ALOGE("YCbCr_420_888: height (%d) should be a multiple of 2", buffer->height);
+ return BAD_VALUE;
+ }
+
+ if (buffer->width <= 0) {
+ ALOGE("YCbCr_420_888: width (%d) should be a > 0", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height <= 0) {
+ ALOGE("YCbCr_420_888: height (%d) should be a > 0", buffer->height);
+ return BAD_VALUE;
+ }
+
pData =
(idx == 0) ?
buffer->data :
@@ -160,6 +181,27 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx,
break;
// NV21
case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+ if (buffer->width % 2 != 0) {
+ ALOGE("YCrCb_420_SP: width (%d) should be a multiple of 2", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height % 2 != 0) {
+ ALOGE("YCrCb_420_SP: height (%d) should be a multiple of 2", buffer->height);
+ return BAD_VALUE;
+ }
+
+ if (buffer->width <= 0) {
+ ALOGE("YCrCb_420_SP: width (%d) should be a > 0", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height <= 0) {
+ ALOGE("YCrCb_420_SP: height (%d) should be a > 0", buffer->height);
+ return BAD_VALUE;
+ }
+
cr = buffer->data + (buffer->stride * buffer->height);
cb = cr + 1;
// only map until last pixel
@@ -178,6 +220,27 @@ status_t getLockedImageInfo(LockedImage* buffer, int idx,
rStride = buffer->width;
break;
case HAL_PIXEL_FORMAT_YV12:
+ // Width and height should be multiple of 2. Wrong dataSize would be returned otherwise.
+ if (buffer->width % 2 != 0) {
+ ALOGE("YV12: width (%d) should be a multiple of 2", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height % 2 != 0) {
+ ALOGE("YV12: height (%d) should be a multiple of 2", buffer->height);
+ return BAD_VALUE;
+ }
+
+ if (buffer->width <= 0) {
+ ALOGE("YV12: width (%d) should be a > 0", buffer->width);
+ return BAD_VALUE;
+ }
+
+ if (buffer->height <= 0) {
+ ALOGE("YV12: height (%d) should be a > 0", buffer->height);
+ return BAD_VALUE;
+ }
+
// Y and C stride need to be 16 pixel aligned.
LOG_ALWAYS_FATAL_IF(buffer->stride % 16,
"Stride is not 16 pixel aligned %d", buffer->stride);
@@ -344,6 +407,11 @@ status_t lockImageFromBuffer(sp<GraphicBuffer> buffer, uint32_t inUsage,
int flexFormat = format;
if (isPossiblyYUV(format)) {
res = buffer->lockAsyncYCbCr(inUsage, rect, &ycbcr, fenceFd);
+
+ if (res != OK) {
+ ALOGW("lockAsyncYCbCr failed with error %d", res);
+ }
+
pData = ycbcr.y;
flexFormat = HAL_PIXEL_FORMAT_YCbCr_420_888;
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 5daf8b0f88f8..0845933e90e9 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -22,7 +22,6 @@
#include "android_runtime/AndroidRuntime.h"
#include <android-base/logging.h>
-#include <android/hardware/tv/tuner/1.0/ITuner.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -34,7 +33,6 @@ using ::android::hardware::Void;
using ::android::hardware::hidl_bitfield;
using ::android::hardware::hidl_vec;
using ::android::hardware::tv::tuner::V1_0::AudioExtraMetaData;
-using ::android::hardware::tv::tuner::V1_0::Constant;
using ::android::hardware::tv::tuner::V1_0::DataFormat;
using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
@@ -129,12 +127,13 @@ using ::android::hardware::tv::tuner::V1_0::FrontendStatus;
using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
using ::android::hardware::tv::tuner::V1_0::FrontendStatusType;
using ::android::hardware::tv::tuner::V1_0::FrontendType;
-using ::android::hardware::tv::tuner::V1_0::ITuner;
using ::android::hardware::tv::tuner::V1_0::LnbPosition;
using ::android::hardware::tv::tuner::V1_0::LnbTone;
using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
using ::android::hardware::tv::tuner::V1_0::PlaybackSettings;
using ::android::hardware::tv::tuner::V1_0::RecordSettings;
+using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::Constant64Bit;
struct fields_t {
jfieldID tunerContext;
@@ -310,9 +309,9 @@ C2DataIdInfo::C2DataIdInfo(uint32_t index, uint64_t value) : C2Param(kParamSize,
/////////////// MediaEvent ///////////////////////
-MediaEvent::MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle,
- uint64_t dataId, uint64_t dataLength, jobject obj) : mIFilter(iFilter),
- mDataId(dataId), mDataLength(dataLength), mBuffer(nullptr),
+MediaEvent::MediaEvent(sp<Filter> filter, hidl_handle avHandle,
+ uint64_t dataId, uint64_t dataSize, jobject obj) : mFilter(filter),
+ mDataId(dataId), mDataSize(dataSize), mBuffer(nullptr),
mDataIdRefCnt(0), mAvHandleRefCnt(0), mIonHandle(nullptr) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
mMediaEventObj = env->NewWeakGlobalRef(obj);
@@ -336,7 +335,8 @@ MediaEvent::~MediaEvent() {
void MediaEvent::finalize() {
if (mAvHandleRefCnt == 0) {
- mIFilter->releaseAvHandle(hidl_handle(mAvHandle), mDataIdRefCnt == 0 ? mDataId : 0);
+ mFilter->mFilterSp->releaseAvHandle(
+ hidl_handle(mAvHandle), mDataIdRefCnt == 0 ? mDataId : 0);
native_handle_close(mAvHandle);
}
}
@@ -349,7 +349,47 @@ jobject MediaEvent::getLinearBlock() {
if (mLinearBlockObj != NULL) {
return mLinearBlockObj;
}
- mIonHandle = new C2HandleIon(dup(mAvHandle->data[0]), mDataLength);
+
+ int fd;
+ int numInts = 0;
+ int memIndex;
+ int dataSize;
+ if (mAvHandle->numFds == 0) {
+ if (mFilter->mAvSharedHandle == NULL) {
+ ALOGE("Shared AV memory handle is not initialized.");
+ return NULL;
+ }
+ if (mFilter->mAvSharedHandle->numFds == 0) {
+ ALOGE("Shared AV memory handle is empty.");
+ return NULL;
+ }
+ fd = mFilter->mAvSharedHandle->data[0];
+ dataSize = mFilter->mAvSharedMemSize;
+ numInts = mFilter->mAvSharedHandle->numInts;
+ if (numInts > 0) {
+ // If the first int in the shared native handle has value, use it as the index
+ memIndex = mFilter->mAvSharedHandle->data[mFilter->mAvSharedHandle->numFds];
+ }
+ } else {
+ fd = mAvHandle->data[0];
+ dataSize = mDataSize;
+ numInts = mAvHandle->numInts;
+ if (numInts > 0) {
+ // Otherwise if the first int in the av native handle returned from the filter
+ // event has value, use it as the index
+ memIndex = mAvHandle->data[mAvHandle->numFds];
+ } else {
+ if (mFilter->mAvSharedHandle != NULL) {
+ numInts = mFilter->mAvSharedHandle->numInts;
+ if (numInts > 0) {
+ // If the first int in the shared native handle has value, use it as the index
+ memIndex = mFilter->mAvSharedHandle->data[mFilter->mAvSharedHandle->numFds];
+ }
+ }
+ }
+ }
+
+ mIonHandle = new C2HandleIon(dup(fd), dataSize);
std::shared_ptr<C2LinearBlock> block = _C2BlockFactory::CreateLinearBlock(mIonHandle);
if (block != nullptr) {
// CreateLinearBlock delete mIonHandle after it create block successfully.
@@ -358,13 +398,11 @@ jobject MediaEvent::getLinearBlock() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
context->mBlock = block;
- std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, mDataLength);
+ std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, dataSize);
context->mBuffer = pC2Buffer;
mC2Buffer = pC2Buffer;
- if (mAvHandle->numInts > 0) {
- // use first int in the native_handle as the index
- int index = mAvHandle->data[mAvHandle->numFds];
- std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(index, mDataId);
+ if (numInts > 0) {
+ std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(memIndex, mDataId);
std::shared_ptr<C2Info> info(std::static_pointer_cast<C2Info>(c2param));
pC2Buffer->setInfo(info);
}
@@ -471,7 +509,7 @@ jobjectArray FilterCallback::getMediaEvent(
if (mediaEvent.avMemory.getNativeHandle() != NULL || mediaEvent.avDataId != 0) {
sp<MediaEvent> mediaEventSp =
- new MediaEvent(mIFilter, mediaEvent.avMemory,
+ new MediaEvent(mFilter, mediaEvent.avMemory,
mediaEvent.avDataId, dataLength + offset, obj);
mediaEventSp->mAvHandleRefCnt++;
env->SetLongField(obj, eventContext, (jlong) mediaEventSp.get());
@@ -505,10 +543,11 @@ jobjectArray FilterCallback::getPesEvent(
}
jobjectArray FilterCallback::getTsRecordEvent(
- jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events) {
+ jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events,
+ const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJ)V");
for (int i = 0; i < events.size(); i++) {
auto event = events[i];
@@ -537,28 +576,39 @@ jobjectArray FilterCallback::getTsRecordEvent(
jlong byteNumber = static_cast<jlong>(tsRecordEvent.byteNumber);
+ jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].tsRecord().pts)
+ : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+
jobject obj =
- env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber);
+ env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts);
env->SetObjectArrayElement(arr, i, obj);
}
return arr;
}
jobjectArray FilterCallback::getMmtpRecordEvent(
- jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events) {
+ jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events,
+ const std::vector<DemuxFilterEventExt::Event>& eventsExt) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent");
- jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJ)V");
+ jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJ)V");
for (int i = 0; i < events.size(); i++) {
auto event = events[i];
+
DemuxFilterMmtpRecordEvent mmtpRecordEvent = event.mmtpRecord();
jint scHevcIndexMask = static_cast<jint>(mmtpRecordEvent.scHevcIndexMask);
jlong byteNumber = static_cast<jlong>(mmtpRecordEvent.byteNumber);
+ jint mpuSequenceNumber = (eventsExt.size() > i)
+ ? static_cast<jint>(eventsExt[i].mmtpRecord().mpuSequenceNumber)
+ : static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
+ jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].mmtpRecord().pts)
+ : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
jobject obj =
- env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber);
+ env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
+ mpuSequenceNumber, pts);
env->SetObjectArrayElement(arr, i, obj);
}
return arr;
@@ -627,12 +677,14 @@ jobjectArray FilterCallback::getTemiEvent(
return arr;
}
-Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent) {
- ALOGD("FilterCallback::onFilterEvent");
+Return<void> FilterCallback::onFilterEvent_1_1(const DemuxFilterEvent& filterEvent,
+ const DemuxFilterEventExt& filterEventExt) {
+ ALOGD("FilterCallback::onFilterEvent_1_1");
JNIEnv *env = AndroidRuntime::getJNIEnv();
std::vector<DemuxFilterEvent::Event> events = filterEvent.events;
+ std::vector<DemuxFilterEventExt::Event> eventsExt = filterEventExt.events;
jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/FilterEvent");
jobjectArray array = env->NewObjectArray(events.size(), eventClazz, NULL);
@@ -652,11 +704,11 @@ Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent)
break;
}
case DemuxFilterEvent::Event::hidl_discriminator::tsRecord: {
- array = getTsRecordEvent(array, events);
+ array = getTsRecordEvent(array, events, eventsExt);
break;
}
case DemuxFilterEvent::Event::hidl_discriminator::mmtpRecord: {
- array = getMmtpRecordEvent(array, events);
+ array = getMmtpRecordEvent(array, events, eventsExt);
break;
}
case DemuxFilterEvent::Event::hidl_discriminator::download: {
@@ -677,18 +729,26 @@ Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent)
}
}
env->CallVoidMethod(
- mFilter,
+ mFilter->mFilterObj,
gFields.onFilterEventID,
array);
return Void();
}
+Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& filterEvent) {
+ ALOGD("FilterCallback::onFilterEvent");
+ std::vector<DemuxFilterEventExt::Event> emptyEventsExt;
+ DemuxFilterEventExt emptyFilterEventExt {
+ .events = emptyEventsExt,
+ };
+ return onFilterEvent_1_1(filterEvent, emptyFilterEventExt);
+}
Return<void> FilterCallback::onFilterStatus(const DemuxFilterStatus status) {
ALOGD("FilterCallback::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallVoidMethod(
- mFilter,
+ mFilter->mFilterObj,
gFields.onFilterStatusID,
(jint)status);
return Void();
@@ -696,17 +756,11 @@ Return<void> FilterCallback::onFilterStatus(const DemuxFilterStatus status) {
void FilterCallback::setFilter(const sp<Filter> filter) {
ALOGD("FilterCallback::setFilter");
- mFilter = filter->mFilterObj;
- mIFilter = filter->mFilterSp;
+ // JNI Object
+ mFilter = filter;
}
-FilterCallback::~FilterCallback() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (mFilter != NULL) {
- env->DeleteWeakGlobalRef(mFilter);
- mFilter = NULL;
- }
-}
+FilterCallback::~FilterCallback() {}
/////////////// Filter ///////////////////////
@@ -919,6 +973,8 @@ Return<void> FrontendCallback::onScanMessage(FrontendScanMessageType type, const
/////////////// Tuner ///////////////////////
sp<ITuner> JTuner::mTuner;
+sp<::android::hardware::tv::tuner::V1_1::ITuner> JTuner::mTuner_1_1;
+int JTuner::mTunerVersion = 0;
JTuner::JTuner(JNIEnv *env, jobject thiz)
: mClass(NULL) {
@@ -950,13 +1006,28 @@ JTuner::~JTuner() {
sp<ITuner> JTuner::getTunerService() {
if (mTuner == nullptr) {
- mTuner = ITuner::getService();
+ mTunerVersion = 0;
+ mTuner_1_1 = ::android::hardware::tv::tuner::V1_1::ITuner::getService();
+
+ if (mTuner_1_1 == nullptr) {
+ ALOGW("Failed to get tuner 1.1 service.");
+ mTuner = ITuner::getService();
+ if (mTuner == nullptr) {
+ ALOGW("Failed to get tuner 1.0 service.");
+ } else {
+ mTunerVersion = 1 << 16;
+ }
+ } else {
+ mTuner = static_cast<sp<ITuner>>(mTuner_1_1);
+ mTunerVersion = ((1 << 16) | 1);
+ }
+ }
+ return mTuner;
+}
- if (mTuner == nullptr) {
- ALOGW("Failed to get tuner service.");
- }
- }
- return mTuner;
+jint JTuner::getTunerVersion() {
+ ALOGD("JTuner::getTunerVersion()");
+ return (jint) mTunerVersion;
}
jobject JTuner::getFrontendIds() {
@@ -996,6 +1067,7 @@ jobject JTuner::openFrontendById(int id) {
return NULL;
}
mFe = fe;
+ mFe_1_1 = ::android::hardware::tv::tuner::V1_1::IFrontend::castFrom(mFe);
mFeId = id;
if (mDemux != NULL) {
mDemux->setFrontendDataSource(mFeId);
@@ -1455,6 +1527,27 @@ int JTuner::connectCiCam(jint id) {
return (int) r;
}
+int JTuner::linkCiCam(int id) {
+ if (mFe_1_1 == NULL) {
+ ALOGE("frontend 1.1 is not initialized");
+ return (int)Constant::INVALID_LTS_ID;
+ }
+
+ Result res;
+ uint32_t ltsId;
+ mFe_1_1->linkCiCam(static_cast<uint32_t>(id),
+ [&](Result r, uint32_t id) {
+ res = r;
+ ltsId = id;
+ });
+
+ if (res != Result::SUCCESS) {
+ return (int)Constant::INVALID_LTS_ID;
+ }
+
+ return (int) ltsId;
+}
+
int JTuner::disconnectCiCam() {
if (mDemux == NULL) {
Result r = openDemux();
@@ -1466,6 +1559,18 @@ int JTuner::disconnectCiCam() {
return (int) r;
}
+
+int JTuner::unlinkCiCam(int id) {
+ if (mFe_1_1 == NULL) {
+ ALOGE("frontend 1.1 is not initialized");
+ return (int)Result::INVALID_STATE;
+ }
+
+ Result r = mFe_1_1->unlinkCiCam(static_cast<uint32_t>(id));
+
+ return (int) r;
+}
+
jobject JTuner::openDescrambler() {
ALOGD("JTuner::openDescrambler");
if (mTuner == nullptr || mDemux == nullptr) {
@@ -1504,6 +1609,7 @@ jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) {
}
sp<IFilter> iFilterSp;
+ sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
sp<FilterCallback> callback = new FilterCallback();
Result res;
mDemux->openFilter(type, bufferSize, callback,
@@ -1515,24 +1621,54 @@ jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) {
ALOGD("Failed to open filter, type = %d", type.mainType);
return NULL;
}
- int fId;
+ uint64_t fId;
iFilterSp->getId([&](Result, uint32_t filterId) {
fId = filterId;
});
+ iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+ if (iFilterSp_1_1 != NULL) {
+ iFilterSp_1_1->getId64Bit([&](Result, uint64_t filterId64Bit) {
+ fId = filterId64Bit;
+ });
+ }
JNIEnv *env = AndroidRuntime::getJNIEnv();
jobject filterObj =
env->NewObject(
env->FindClass("android/media/tv/tuner/filter/Filter"),
gFields.filterInitID,
- (jint) fId);
+ (jlong) fId);
sp<Filter> filterSp = new Filter(iFilterSp, filterObj);
filterSp->incStrong(filterObj);
env->SetLongField(filterObj, gFields.filterContext, (jlong)filterSp.get());
-
+ filterSp->mIsMediaFilter = false;
+ filterSp->mAvSharedHandle = NULL;
callback->setFilter(filterSp);
+ if (type.mainType == DemuxFilterMainType::MMTP) {
+ if (type.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO ||
+ type.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) {
+ filterSp->mIsMediaFilter = true;
+ }
+ }
+
+ if (type.mainType == DemuxFilterMainType::TS) {
+ if (type.subType.tsFilterType() == DemuxTsFilterType::AUDIO ||
+ type.subType.tsFilterType() == DemuxTsFilterType::VIDEO) {
+ filterSp->mIsMediaFilter = true;
+ }
+ }
+
+ if (iFilterSp_1_1 != NULL && filterSp->mIsMediaFilter) {
+ iFilterSp_1_1->getAvSharedHandle([&](Result r, hidl_handle avMemory, uint64_t avMemSize) {
+ if (r == Result::SUCCESS) {
+ filterSp->mAvSharedHandle = native_handle_clone(avMemory.getNativeHandle());
+ filterSp->mAvSharedMemSize = avMemSize;
+ }
+ });
+ }
+
return filterObj;
}
@@ -1902,6 +2038,10 @@ jint JTuner::closeFrontend() {
if (mFe != NULL) {
r = mFe->close();
}
+ if (r == Result::SUCCESS) {
+ mFe = NULL;
+ mFe_1_1 = NULL;
+ }
return (jint) r;
}
@@ -1910,6 +2050,9 @@ jint JTuner::closeDemux() {
if (mDemux != NULL) {
r = mDemux->close();
}
+ if (r == Result::SUCCESS) {
+ mDemux = NULL;
+ }
return (jint) r;
}
@@ -2460,7 +2603,7 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) {
jclass filterClazz = env->FindClass("android/media/tv/tuner/filter/Filter");
gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J");
gFields.filterInitID =
- env->GetMethodID(filterClazz, "<init>", "(I)V");
+ env->GetMethodID(filterClazz, "<init>", "(J)V");
gFields.onFilterStatusID =
env->GetMethodID(filterClazz, "onFilterStatus", "(I)V");
gFields.onFilterEventID =
@@ -2501,6 +2644,11 @@ static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) {
setTuner(env,thiz, tuner);
}
+static jint android_media_tv_Tuner_native_get_tuner_version(JNIEnv *env, jobject thiz) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->getTunerVersion();
+}
+
static jobject android_media_tv_Tuner_get_frontend_ids(JNIEnv *env, jobject thiz) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->getFrontendIds();
@@ -2579,11 +2727,21 @@ static int android_media_tv_Tuner_connect_cicam(JNIEnv *env, jobject thiz, jint
return tuner->connectCiCam(id);
}
+static int android_media_tv_Tuner_link_cicam(JNIEnv *env, jobject thiz, jint id) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->linkCiCam(id);
+}
+
static int android_media_tv_Tuner_disconnect_cicam(JNIEnv *env, jobject thiz) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->disconnectCiCam();
}
+static int android_media_tv_Tuner_unlink_cicam(JNIEnv *env, jobject thiz, jint id) {
+ sp<JTuner> tuner = getTuner(env, thiz);
+ return tuner->unlinkCiCam(id);
+}
+
static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv *env, jobject thiz, jint id) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->getFrontendInfo(id);
@@ -2991,6 +3149,29 @@ static DemuxFilterSettings getFilterConfiguration(
return filterSettings;
}
+static Result configureIpFilterContextId(
+ JNIEnv *env, sp<IFilter> iFilterSp, jobject ipFilterConfigObj) {
+ jclass clazz = env->FindClass(
+ "android/media/tv/tuner/filter/IpFilterConfiguration");
+ uint32_t cid = env->GetIntField(ipFilterConfigObj, env->GetFieldID(
+ clazz, "mIpFilterContextId", "I"));
+ Result res = Result::SUCCESS;
+ if (cid != static_cast<uint32_t>(Constant::INVALID_IP_FILTER_CONTEXT_ID)) {
+ sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+ iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+
+ if (iFilterSp_1_1 != NULL) {
+ res = iFilterSp_1_1->configureIpCid(cid);
+ if (res != Result::SUCCESS) {
+ return res;
+ }
+ } else {
+ ALOGW("configureIpCid is not supported with the current HAL implementation.");
+ }
+ }
+ return res;
+}
+
static jint copyData(JNIEnv *env, std::unique_ptr<MQ>& mq, EventFlag* flag, jbyteArray buffer,
jlong offset, jlong size) {
ALOGD("copyData, size=%ld, offset=%ld", (long) size, (long) offset);
@@ -3034,6 +3215,13 @@ static jint android_media_tv_Tuner_configure_filter(
return (jint) res;
}
+ if (static_cast<DemuxFilterMainType>(type) == DemuxFilterMainType::IP) {
+ res = configureIpFilterContextId(env, iFilterSp, settings);
+ if (res != Result::SUCCESS) {
+ return (jint) res;
+ }
+ }
+
MQDescriptorSync<uint8_t> filterMQDesc;
Result getQueueDescResult = Result::UNKNOWN_ERROR;
if (filterSp->mFilterMQ == NULL) {
@@ -3071,6 +3259,36 @@ static jint android_media_tv_Tuner_get_filter_id(JNIEnv* env, jobject filter) {
return (jint) id;
}
+static jlong android_media_tv_Tuner_get_filter_64bit_id(JNIEnv* env, jobject filter) {
+ sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
+ if (iFilterSp == NULL) {
+ ALOGD("Failed to get filter ID: filter not found");
+ return static_cast<jlong>(
+ ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+ }
+
+ sp<::android::hardware::tv::tuner::V1_1::IFilter> iFilterSp_1_1;
+ iFilterSp_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(iFilterSp);
+ Result res;
+ uint64_t id;
+
+ if (iFilterSp_1_1 != NULL) {
+ iFilterSp_1_1->getId64Bit(
+ [&](Result r, uint64_t filterId64Bit) {
+ res = r;
+ id = filterId64Bit;
+ });
+ } else {
+ ALOGW("getId64Bit is not supported with the current HAL implementation.");
+ return static_cast<jlong>(
+ ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+ }
+
+ return (res == Result::SUCCESS) ?
+ static_cast<jlong>(id) : static_cast<jlong>(
+ ::android::hardware::tv::tuner::V1_1::Constant64Bit::INVALID_FILTER_ID_64BIT);
+}
+
static jint android_media_tv_Tuner_set_filter_data_source(
JNIEnv* env, jobject filter, jobject srcFilter) {
sp<IFilter> iFilterSp = getFilter(env, filter)->getIFilter();
@@ -3684,6 +3902,7 @@ static void android_media_tv_Tuner_media_event_finalize(JNIEnv* env, jobject med
static const JNINativeMethod gTunerMethods[] = {
{ "nativeInit", "()V", (void *)android_media_tv_Tuner_native_init },
{ "nativeSetup", "()V", (void *)android_media_tv_Tuner_native_setup },
+ { "nativeGetTunerVersion", "()I", (void *)android_media_tv_Tuner_native_get_tuner_version },
{ "nativeGetFrontendIds", "()Ljava/util/List;",
(void *)android_media_tv_Tuner_get_frontend_ids },
{ "nativeOpenFrontendByHandle", "(I)Landroid/media/tv/tuner/Tuner$Frontend;",
@@ -3705,6 +3924,10 @@ static const JNINativeMethod gTunerMethods[] = {
{ "nativeGetAvSyncTime", "(I)Ljava/lang/Long;",
(void *)android_media_tv_Tuner_get_av_sync_time },
{ "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam },
+ { "nativeLinkCiCam", "(I)I",
+ (void *)android_media_tv_Tuner_link_cicam },
+ { "nativeUnlinkCiCam", "(I)I",
+ (void *)android_media_tv_Tuner_unlink_cicam },
{ "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam },
{ "nativeGetFrontendInfo", "(I)Landroid/media/tv/tuner/frontend/FrontendInfo;",
(void *)android_media_tv_Tuner_get_frontend_info },
@@ -3735,6 +3958,8 @@ static const JNINativeMethod gFilterMethods[] = {
{ "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
(void *)android_media_tv_Tuner_configure_filter },
{ "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
+ { "nativeGetId64Bit", "()J",
+ (void *)android_media_tv_Tuner_get_filter_64bit_id },
{ "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
(void *)android_media_tv_Tuner_set_filter_data_source },
{ "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index c4deeaf887bb..5a59a5881586 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -17,7 +17,12 @@
#ifndef _ANDROID_MEDIA_TV_TUNER_H_
#define _ANDROID_MEDIA_TV_TUNER_H_
-#include <android/hardware/tv/tuner/1.0/ITuner.h>
+#include <android/hardware/tv/tuner/1.1/IFilter.h>
+#include <android/hardware/tv/tuner/1.1/IFilterCallback.h>
+#include <android/hardware/tv/tuner/1.1/IFrontend.h>
+#include <android/hardware/tv/tuner/1.1/ITuner.h>
+#include <android/hardware/tv/tuner/1.1/types.h>
+
#include <C2BlockInternal.h>
#include <C2HandleIonInternal.h>
#include <C2ParamDef.h>
@@ -38,6 +43,7 @@ using ::android::hardware::hidl_handle;
using ::android::hardware::hidl_vec;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
+using ::android::hardware::tv::tuner::V1_1::DemuxFilterEventExt;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
using ::android::hardware::tv::tuner::V1_0::DemuxPid;
@@ -54,7 +60,7 @@ using ::android::hardware::tv::tuner::V1_0::IDescrambler;
using ::android::hardware::tv::tuner::V1_0::IDvr;
using ::android::hardware::tv::tuner::V1_0::IDvrCallback;
using ::android::hardware::tv::tuner::V1_0::IFilter;
-using ::android::hardware::tv::tuner::V1_0::IFilterCallback;
+using ::android::hardware::tv::tuner::V1_1::IFilterCallback;
using ::android::hardware::tv::tuner::V1_0::IFrontend;
using ::android::hardware::tv::tuner::V1_0::IFrontendCallback;
using ::android::hardware::tv::tuner::V1_0::ILnb;
@@ -112,18 +118,32 @@ struct Dvr : public RefBase {
int mFd;
};
+struct Filter : public RefBase {
+ Filter(sp<IFilter> sp, jobject obj);
+ ~Filter();
+ int close();
+ sp<IFilter> getIFilter();
+ sp<IFilter> mFilterSp;
+ std::unique_ptr<MQ> mFilterMQ;
+ EventFlag* mFilterMQEventFlag;
+ jweak mFilterObj;
+ native_handle_t* mAvSharedHandle;
+ uint64_t mAvSharedMemSize;
+ bool mIsMediaFilter;
+};
+
struct MediaEvent : public RefBase {
- MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle, uint64_t dataId,
- uint64_t dataLength, jobject obj);
+ MediaEvent(sp<Filter> filter, hidl_handle avHandle, uint64_t dataId,
+ uint64_t dataSize, jobject obj);
~MediaEvent();
jobject getLinearBlock();
uint64_t getAudioHandle();
void finalize();
- sp<IFilter> mIFilter;
+ sp<Filter> mFilter;
native_handle_t* mAvHandle;
uint64_t mDataId;
- uint64_t mDataLength;
+ uint64_t mDataSize;
uint8_t* mBuffer;
android::Mutex mLock;
int mDataIdRefCnt;
@@ -134,26 +154,16 @@ struct MediaEvent : public RefBase {
std::weak_ptr<C2Buffer> mC2Buffer;
};
-struct Filter : public RefBase {
- Filter(sp<IFilter> sp, jobject obj);
- ~Filter();
- int close();
- sp<IFilter> getIFilter();
- sp<IFilter> mFilterSp;
- std::unique_ptr<MQ> mFilterMQ;
- EventFlag* mFilterMQEventFlag;
- jweak mFilterObj;
-};
-
struct FilterCallback : public IFilterCallback {
~FilterCallback();
+ virtual Return<void> onFilterEvent_1_1(const DemuxFilterEvent& filterEvent,
+ const DemuxFilterEventExt& filterEventExt);
virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent);
virtual Return<void> onFilterStatus(const DemuxFilterStatus status);
void setFilter(const sp<Filter> filter);
private:
- jweak mFilter;
- sp<IFilter> mIFilter;
+ sp<Filter> mFilter;
jobjectArray getSectionEvent(
jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
jobjectArray getMediaEvent(
@@ -161,9 +171,11 @@ private:
jobjectArray getPesEvent(
jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
jobjectArray getTsRecordEvent(
- jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
+ jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>&events,
+ const std::vector<DemuxFilterEventExt::Event>& eventsExt);
jobjectArray getMmtpRecordEvent(
- jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
+ jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>&events,
+ const std::vector<DemuxFilterEventExt::Event>& eventsExt);
jobjectArray getDownloadEvent(
jobjectArray& arr, const std::vector<DemuxFilterEvent::Event>& events);
jobjectArray getIpPayloadEvent(
@@ -194,10 +206,13 @@ struct TimeFilter : public RefBase {
struct JTuner : public RefBase {
JTuner(JNIEnv *env, jobject thiz);
sp<ITuner> getTunerService();
+ int getTunerVersion();
jobject getAvSyncHwId(sp<Filter> filter);
jobject getAvSyncTime(jint id);
int connectCiCam(jint id);
+ int linkCiCam(jint id);
int disconnectCiCam();
+ int unlinkCiCam(jint id);
jobject getFrontendIds();
jobject openFrontendById(int id);
jint closeFrontendById(int id);
@@ -229,8 +244,13 @@ private:
jclass mClass;
jweak mObject;
static sp<ITuner> mTuner;
+ static sp<::android::hardware::tv::tuner::V1_1::ITuner> mTuner_1_1;
+ // An integer that carries the Tuner version. The high 16 bits are the major version number
+ // while the low 16 bits are the minor version. Default value is unknown version 0.
+ static int mTunerVersion;
hidl_vec<FrontendId> mFeIds;
sp<IFrontend> mFe;
+ sp<::android::hardware::tv::tuner::V1_1::IFrontend> mFe_1_1;
int mFeId;
hidl_vec<LnbId> mLnbIds;
sp<ILnb> mLnb;
diff --git a/media/jni/audioeffect/Android.bp b/media/jni/audioeffect/Android.bp
index 5ba5c0159275..40e4c54c2921 100644
--- a/media/jni/audioeffect/Android.bp
+++ b/media/jni/audioeffect/Android.bp
@@ -28,4 +28,9 @@ cc_library_shared {
"-Wunused",
"-Wunreachable-code",
],
+
+ // Workaround Clang LTO crash.
+ lto: {
+ never: true,
+ },
}
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 45c49e58f390..0d53ab152129 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -331,7 +331,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
}
if (deviceType != AUDIO_DEVICE_NONE) {
- device.mType = deviceType;
+ device.mType = (audio_devices_t)deviceType;
ScopedUtfChars address(env, deviceAddress);
device.setAddress(address.c_str());
}
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index ca3cc8552990..26725f87bfdc 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -200,7 +200,7 @@ android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
paa->content_type =
(audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
- paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
+ paa->flags = (audio_flags_mask_t) env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
ALOGV("android_media_SoundPool_native_setup");
auto *ap = new SoundPool(maxChannels, paa);
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index 5a0a50c2ae38..4d0c258843f9 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -11,7 +11,8 @@ android_test {
static_libs: [
"android-support-test",
"mockito-target-minus-junit4",
- "testng"
+ "testng",
+ "truth-prebuilt",
],
platform_apis: true,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java
new file mode 100644
index 000000000000..92e4c9554cb4
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouteInfoTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.mediaroutertest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.display.DisplayManagerGlobal;
+import android.media.MediaRouter;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRouteInfoTest {
+ private TestableRouteInfo mRouteInfo;
+ private static Display sWifiDisplay;
+ private static Display sExternalDisplay;
+ private static Display sInternalDisplay;
+ private static final String FAKE_MAC_ADDRESS = "fake MAC address";
+
+ @BeforeClass
+ public static void setUpOnce() {
+ final DisplayManagerGlobal global = DisplayManagerGlobal.getInstance();
+ final DisplayInfo wifiInfo = new DisplayInfo();
+ wifiInfo.flags = Display.FLAG_PRESENTATION;
+ wifiInfo.type = Display.TYPE_WIFI;
+ wifiInfo.address = DisplayAddress.fromMacAddress(FAKE_MAC_ADDRESS);
+ sWifiDisplay = new Display(global, 2, wifiInfo, new DisplayAdjustments());
+
+ final DisplayInfo externalInfo = new DisplayInfo();
+ externalInfo.flags = Display.FLAG_PRESENTATION;
+ externalInfo.type = Display.TYPE_EXTERNAL;
+ sExternalDisplay = new Display(global, 3, externalInfo, new DisplayAdjustments());
+
+ final DisplayInfo internalInfo = new DisplayInfo();
+ internalInfo.flags = Display.FLAG_PRESENTATION;
+ internalInfo.type = Display.TYPE_INTERNAL;
+ sInternalDisplay = new Display(global, 4, internalInfo, new DisplayAdjustments());
+ }
+
+ @Before
+ public void setUp() {
+ mRouteInfo = new TestableRouteInfo();
+ }
+
+ @Test
+ public void testGetPresentationDisplay_notLiveVideo() {
+ mRouteInfo.setPresentationDisplays(sWifiDisplay);
+ mRouteInfo.mSupportedType = MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+ }
+
+ @Test
+ public void testGetPresentationDisplay_includesLiveVideo() {
+ mRouteInfo.setPresentationDisplays(sWifiDisplay);
+ mRouteInfo.mSupportedType |= MediaRouter.ROUTE_TYPE_LIVE_AUDIO;
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_noPresentationDisplay() {
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+ }
+
+ @Test
+ public void testGetPresentationDisplay_wifiDisplayOnly() {
+ mRouteInfo.setPresentationDisplays(sWifiDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_externalDisplayOnly() {
+ mRouteInfo.setPresentationDisplays(sExternalDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sExternalDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_internalDisplayOnly() {
+ mRouteInfo.setPresentationDisplays(sInternalDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sInternalDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_addressNotMatch() {
+ mRouteInfo.setPresentationDisplays(sWifiDisplay);
+ mRouteInfo.mDeviceAddress = "Not match";
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isNull();
+ }
+
+ @Test
+ public void testGetPresentationDisplay_containsWifiAndExternalDisplays_returnWifiDisplay() {
+ mRouteInfo.setPresentationDisplays(sExternalDisplay, sWifiDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_containsExternalAndInternalDisplays_returnExternal() {
+ mRouteInfo.setPresentationDisplays(sExternalDisplay, sInternalDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sExternalDisplay);
+ }
+
+ @Test
+ public void testGetPresentationDisplay_containsAllDisplays_returnWifiDisplay() {
+ mRouteInfo.setPresentationDisplays(sExternalDisplay, sInternalDisplay, sWifiDisplay);
+
+ mRouteInfo.updatePresentationDisplay();
+
+ assertThat(mRouteInfo.getPresentationDisplay()).isEqualTo(sWifiDisplay);
+ }
+
+ private static class TestableRouteInfo extends MediaRouter.RouteInfo {
+ private Display[] mDisplays = new Display[0];
+ private int mSupportedType = MediaRouter.ROUTE_TYPE_LIVE_VIDEO;
+ private String mDeviceAddress = FAKE_MAC_ADDRESS;
+ private MediaRouter.RouteInfo mDefaultRouteInfo = null;
+
+ private TestableRouteInfo() {
+ super(null);
+ }
+
+ private void setPresentationDisplays(Display ...displays) {
+ mDisplays = new Display[displays.length];
+ System.arraycopy(displays, 0, mDisplays, 0, displays.length);
+ }
+
+ @Override
+ public Display[] getAllPresentationDisplays() {
+ return mDisplays;
+ }
+
+ @Override
+ public int getSupportedTypes() {
+ return mSupportedType;
+ }
+
+ @Override
+ public String getDeviceAddress() {
+ return mDeviceAddress;
+ }
+
+ @Override
+ public MediaRouter.RouteInfo getDefaultAudioVideo() {
+ return mDefaultRouteInfo;
+ }
+ }
+}
diff --git a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
index 33d6d64c7f37..21ed840c7313 100644
--- a/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
+++ b/media/tests/MediaTranscodingTest/src/com/android/mediatranscodingtest/MediaTranscodeManagerTest.java
@@ -203,6 +203,42 @@ public class MediaTranscodeManagerTest
}
/**
+ * Verify that setting invalid pid will throw exception.
+ */
+ @Test
+ public void testCreateTranscodingWithInvalidClientPid() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ TranscodingRequest request =
+ new TranscodingRequest.Builder()
+ .setSourceUri(mSourceHEVCVideoUri)
+ .setDestinationUri(mDestinationUri)
+ .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+ .setClientPid(-1)
+ .setVideoTrackFormat(createMediaFormat())
+ .build();
+ });
+ }
+
+ /**
+ * Verify that setting invalid uid will throw exception.
+ */
+ @Test
+ public void testCreateTranscodingWithInvalidClientUid() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> {
+ TranscodingRequest request =
+ new TranscodingRequest.Builder()
+ .setSourceUri(mSourceHEVCVideoUri)
+ .setDestinationUri(mDestinationUri)
+ .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+ .setClientUid(-1)
+ .setVideoTrackFormat(createMediaFormat())
+ .build();
+ });
+ }
+
+ /**
* Verify that setting null source uri will throw exception.
*/
@Test
@@ -425,16 +461,24 @@ public class MediaTranscodeManagerTest
MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
assertNotNull(videoTrackFormat);
+ int pid = android.os.Process.myPid();
+ int uid = android.os.Process.myUid();
+
TranscodingRequest request =
new TranscodingRequest.Builder()
.setSourceUri(mSourceHEVCVideoUri)
.setDestinationUri(destinationUri)
.setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+ .setClientPid(pid)
+ .setClientUid(uid)
.setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
.setVideoTrackFormat(videoTrackFormat)
.build();
Executor listenerExecutor = Executors.newSingleThreadExecutor();
+ assertEquals(pid, request.getClientPid());
+ assertEquals(uid, request.getClientUid());
+
Log.i(TAG, "transcoding to " + videoTrackFormat);
TranscodingJob job = mMediaTranscodeManager.enqueueRequest(request, listenerExecutor,
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 0af6cbf3cb40..0a466f424cec 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -317,10 +317,9 @@ void ASurfaceTransaction_reparent(ASurfaceTransaction* aSurfaceTransaction,
sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
sp<SurfaceControl> newParentSurfaceControl = ASurfaceControl_to_SurfaceControl(
newParentASurfaceControl);
- sp<IBinder> newParentHandle = (newParentSurfaceControl)? newParentSurfaceControl->getHandle() : nullptr;
Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- transaction->reparent(surfaceControl, newParentHandle);
+ transaction->reparent(surfaceControl, newParentSurfaceControl);
}
void ASurfaceTransaction_setVisibility(ASurfaceTransaction* aSurfaceTransaction,
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
index 56f390698eb2..ac4c16a4199b 100644
--- a/native/graphics/jni/imagedecoder.cpp
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -346,3 +346,25 @@ int AImageDecoder_decodeImage(AImageDecoder* decoder,
void AImageDecoder_delete(AImageDecoder* decoder) {
delete toDecoder(decoder);
}
+
+bool AImageDecoder_isAnimated(AImageDecoder* decoder) {
+ if (!decoder) return false;
+
+ ImageDecoder* imageDecoder = toDecoder(decoder);
+ return imageDecoder->mCodec->codec()->getFrameCount() > 1;
+}
+
+int32_t AImageDecoder_getRepeatCount(AImageDecoder* decoder) {
+ if (!decoder) return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+
+ ImageDecoder* imageDecoder = toDecoder(decoder);
+ const int count = imageDecoder->mCodec->codec()->getRepetitionCount();
+
+ // Skia should not report anything out of range, but defensively treat
+ // negative and too big as INFINITE.
+ if (count == SkCodec::kRepetitionCountInfinite || count < 0
+ || count > std::numeric_limits<int32_t>::max()) {
+ return ANDROID_IMAGE_DECODER_INFINITE;
+ }
+ return count;
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index 01c14770bebd..a184ab9d42e9 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -13,6 +13,8 @@ LIBJNIGRAPHICS {
AImageDecoder_setTargetSize; # introduced=30
AImageDecoder_computeSampledSize; # introduced=30
AImageDecoder_setCrop; # introduced=30
+ AImageDecoder_isAnimated; # introduced=31
+ AImageDecoder_getRepeatCount; # introduced=31
AImageDecoderHeaderInfo_getWidth; # introduced=30
AImageDecoderHeaderInfo_getHeight; # introduced=30
AImageDecoderHeaderInfo_getMimeType; # introduced=30
diff --git a/non-updatable-api/Android.bp b/non-updatable-api/Android.bp
new file mode 100644
index 000000000000..00b901992b90
--- /dev/null
+++ b/non-updatable-api/Android.bp
@@ -0,0 +1,53 @@
+// 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 {
+ default_visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "non-updatable-current.txt",
+ srcs: ["current.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-removed.txt",
+ srcs: ["removed.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-current.txt",
+ srcs: ["system-current.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-removed.txt",
+ srcs: ["system-removed.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-module-lib-current.txt",
+ srcs: ["module-lib-current.txt"],
+ visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-module-lib-removed.txt",
+ srcs: ["module-lib-removed.txt"],
+ visibility: ["//frameworks/base/api"],
+}
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index fce66164dae6..648ad5630723 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -21,6 +21,7 @@ package android {
field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+ field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
@@ -129,6 +130,7 @@ package android {
field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+ field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND";
field public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
@@ -644,10 +646,10 @@ package android {
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
- field public static final int fontProviderAuthority = 16844112; // 0x1010550
- field public static final int fontProviderCerts = 16844125; // 0x101055d
- field public static final int fontProviderPackage = 16844119; // 0x1010557
- field public static final int fontProviderQuery = 16844113; // 0x1010551
+ field @Deprecated public static final int fontProviderAuthority = 16844112; // 0x1010550
+ field @Deprecated public static final int fontProviderCerts = 16844125; // 0x101055d
+ field @Deprecated public static final int fontProviderPackage = 16844119; // 0x1010557
+ field @Deprecated public static final int fontProviderQuery = 16844113; // 0x1010551
field public static final int fontStyle = 16844095; // 0x101053f
field public static final int fontVariationSettings = 16844144; // 0x1010570
field public static final int fontWeight = 16844083; // 0x1010533
@@ -2887,6 +2889,7 @@ package android.accessibilityservice {
field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14
field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28
field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13
+ field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b
field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a
field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b
field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c
@@ -2895,11 +2898,13 @@ package android.accessibilityservice {
field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17
field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29
field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16
+ field public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44; // 0x2c
field public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30; // 0x1e
field public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31; // 0x1f
field public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32; // 0x20
field public static final int GESTURE_3_FINGER_SWIPE_UP = 29; // 0x1d
field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18
+ field public static final int GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD = 45; // 0x2d
field public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38; // 0x26
field public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42; // 0x2a
field public static final int GESTURE_4_FINGER_SINGLE_TAP = 37; // 0x25
@@ -4374,6 +4379,7 @@ package android.app {
method @Deprecated public void checkPackage(int, @NonNull String);
method @Deprecated public void finishOp(@NonNull String, int, @NonNull String);
method public void finishOp(@NonNull String, int, @NonNull String, @Nullable String);
+ method public void finishProxyOp(@NonNull String, int, @NonNull String, @Nullable String);
method public boolean isOpActive(@NonNull String, int, @NonNull String);
method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
@@ -4390,6 +4396,8 @@ package android.app {
method public int startOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
method public int startOpNoThrow(@NonNull String, int, @NonNull String, @NonNull String, @Nullable String);
+ method public int startProxyOp(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
+ method public int startProxyOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String, @Nullable String);
method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -6138,6 +6146,7 @@ package android.app {
field @NonNull public static final android.os.Parcelable.Creator<android.app.PendingIntent> CREATOR;
field public static final int FLAG_CANCEL_CURRENT = 268435456; // 0x10000000
field public static final int FLAG_IMMUTABLE = 67108864; // 0x4000000
+ field public static final int FLAG_MUTABLE = 33554432; // 0x2000000
field public static final int FLAG_NO_CREATE = 536870912; // 0x20000000
field public static final int FLAG_ONE_SHOT = 1073741824; // 0x40000000
field public static final int FLAG_UPDATE_CURRENT = 134217728; // 0x8000000
@@ -6512,6 +6521,7 @@ package android.app {
method public void dropShellPermissionIdentity();
method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
method public android.os.ParcelFileDescriptor executeShellCommand(String);
+ method @NonNull public android.os.ParcelFileDescriptor[] executeShellCommandRw(@NonNull String);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
@@ -8783,6 +8793,7 @@ package android.bluetooth {
method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
+ method public void onServiceChanged(@NonNull android.bluetooth.BluetoothGatt);
method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
}
@@ -9137,6 +9148,7 @@ package android.bluetooth.le {
method public boolean getIncludeTxPowerLevel();
method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
+ method @Nullable public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
method public java.util.List<android.os.ParcelUuid> getServiceUuids();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
@@ -9146,6 +9158,7 @@ package android.bluetooth.le {
ctor public AdvertiseData.Builder();
method public android.bluetooth.le.AdvertiseData.Builder addManufacturerData(int, byte[]);
method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
+ method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
method public android.bluetooth.le.AdvertiseData build();
method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
@@ -10654,12 +10667,15 @@ package android.content {
field public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
field public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED";
field public static final String ACTION_PACKAGE_FIRST_LAUNCH = "android.intent.action.PACKAGE_FIRST_LAUNCH";
+ field public static final String ACTION_PACKAGE_FULLY_LOADED = "android.intent.action.PACKAGE_FULLY_LOADED";
field public static final String ACTION_PACKAGE_FULLY_REMOVED = "android.intent.action.PACKAGE_FULLY_REMOVED";
field @Deprecated public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL";
field public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+ field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
+ field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
field public static final String ACTION_PASTE = "android.intent.action.PASTE";
field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -10800,6 +10816,7 @@ package android.content {
field public static final String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
field public static final String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
+ field public static final String EXTRA_REMOVED_BY_SYSTEM = "android.intent.extra.REMOVED_BY_SYSTEM";
field public static final String EXTRA_REPLACEMENT_EXTRAS = "android.intent.extra.REPLACEMENT_EXTRAS";
field public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
field public static final String EXTRA_RESTRICTIONS_BUNDLE = "android.intent.extra.restrictions_bundle";
@@ -10823,6 +10840,7 @@ package android.content {
field public static final String EXTRA_TIMEZONE = "time-zone";
field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final String EXTRA_UID = "android.intent.extra.UID";
+ field public static final String EXTRA_UNSTARTABLE_REASON = "android.intent.extra.UNSTARTABLE_REASON";
field public static final String EXTRA_USER = "android.intent.extra.USER";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
@@ -11359,6 +11377,7 @@ package android.content.pm {
field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
field public static final int CONFIG_DENSITY = 4096; // 0x1000
field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
+ field public static final int CONFIG_FORCE_BOLD_TEXT = 268435456; // 0x10000000
field public static final int CONFIG_KEYBOARD = 16; // 0x10
field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20
field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000
@@ -11449,9 +11468,10 @@ package android.content.pm {
public final class ApkChecksum implements android.os.Parcelable {
method public int describeContents();
- method public int getKind();
- method @Nullable public java.security.cert.Certificate getSourceCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public java.security.cert.Certificate getInstallerCertificate() throws java.security.cert.CertificateException;
+ method @Nullable public String getInstallerPackageName();
method @Nullable public String getSplitName();
+ method public int getType();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ApkChecksum> CREATOR;
@@ -11559,18 +11579,19 @@ package android.content.pm {
}
public final class Checksum implements android.os.Parcelable {
+ ctor public Checksum(int, @NonNull byte[]);
method public int describeContents();
- method public int getKind();
+ method public int getType();
method @NonNull public byte[] getValue();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.Checksum> CREATOR;
- field public static final int PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
- field public static final int PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
- field public static final int WHOLE_MD5 = 2; // 0x2
- field public static final int WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
- field public static final int WHOLE_SHA1 = 4; // 0x4
- field public static final int WHOLE_SHA256 = 8; // 0x8
- field public static final int WHOLE_SHA512 = 16; // 0x10
+ field public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 = 32; // 0x20
+ field public static final int TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512 = 64; // 0x40
+ field @Deprecated public static final int TYPE_WHOLE_MD5 = 2; // 0x2
+ field public static final int TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 = 1; // 0x1
+ field @Deprecated public static final int TYPE_WHOLE_SHA1 = 4; // 0x4
+ field @Deprecated public static final int TYPE_WHOLE_SHA256 = 8; // 0x8
+ field @Deprecated public static final int TYPE_WHOLE_SHA512 = 16; // 0x10
}
public class ComponentInfo extends android.content.pm.PackageItemInfo {
@@ -11864,7 +11885,7 @@ package android.content.pm {
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
- method public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
+ method @Deprecated public void addChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>) throws java.io.IOException;
method public void addChildSessionId(int);
method public void close();
method public void commit(@NonNull android.content.IntentSender);
@@ -12020,7 +12041,6 @@ package android.content.pm {
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull public CharSequence getBackgroundPermissionOptionLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
- method public void getChecksums(@NonNull String, boolean, int, @Nullable java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
@@ -12093,6 +12113,7 @@ package android.content.pm {
method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
method public abstract void removePermission(@NonNull String);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
+ method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
@@ -12230,6 +12251,7 @@ package android.content.pm {
field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+ field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8
field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2
field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1
field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4
@@ -12278,6 +12300,9 @@ package android.content.pm {
field public static final int SYNCHRONOUS = 2; // 0x2
field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
+ field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
+ field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
+ field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
field public static final int VERIFICATION_ALLOW = 1; // 0x1
field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -12339,6 +12364,7 @@ package android.content.pm {
field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4
field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10
field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000
+ field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20
field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8
field public static final int PROTECTION_DANGEROUS = 1; // 0x1
field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40
@@ -12680,6 +12706,9 @@ package android.content.res {
field public static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
+ field public static final int FORCE_BOLD_TEXT_NO = 1; // 0x1
+ field public static final int FORCE_BOLD_TEXT_UNDEFINED = 0; // 0x0
+ field public static final int FORCE_BOLD_TEXT_YES = 2; // 0x2
field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2
@@ -12746,6 +12775,7 @@ package android.content.res {
field public int colorMode;
field public int densityDpi;
field public float fontScale;
+ field public int forceBoldText;
field public int hardKeyboardHidden;
field public int keyboard;
field public int keyboardHidden;
@@ -14285,6 +14315,7 @@ package android.graphics {
public final class BlurShader extends android.graphics.Shader {
ctor public BlurShader(float, float, @Nullable android.graphics.Shader);
+ ctor public BlurShader(float, float, @Nullable android.graphics.Shader, @NonNull android.graphics.Shader.TileMode);
}
public class Camera {
@@ -14342,6 +14373,7 @@ package android.graphics {
method public void drawColor(@ColorLong long, @NonNull android.graphics.BlendMode);
method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint);
method public void drawDoubleRoundRect(@NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.RectF, @NonNull float[], @NonNull android.graphics.Paint);
+ method public void drawGlyphs(@NonNull int[], @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.graphics.fonts.Font, @NonNull android.graphics.Paint);
method public void drawLine(float, float, float, float, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], int, int, @NonNull android.graphics.Paint);
method public void drawLines(@NonNull @Size(multiple=4) float[], @NonNull android.graphics.Paint);
@@ -15170,6 +15202,20 @@ package android.graphics {
ctor public PaintFlagsDrawFilter(int, int);
}
+ public final class ParcelableColorSpace extends android.graphics.ColorSpace implements android.os.Parcelable {
+ ctor public ParcelableColorSpace(@NonNull android.graphics.ColorSpace);
+ method public int describeContents();
+ method @NonNull public float[] fromXyz(@NonNull float[]);
+ method @NonNull public android.graphics.ColorSpace getColorSpace();
+ method public float getMaxValue(int);
+ method public float getMinValue(int);
+ method public static boolean isParcelable(@NonNull android.graphics.ColorSpace);
+ method public boolean isWideGamut();
+ method @NonNull public float[] toXyz(@NonNull float[]);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR;
+ }
+
public class Path {
ctor public Path();
ctor public Path(@Nullable android.graphics.Path);
@@ -15605,6 +15651,7 @@ package android.graphics {
public enum Shader.TileMode {
enum_constant public static final android.graphics.Shader.TileMode CLAMP;
+ enum_constant public static final android.graphics.Shader.TileMode DECAL;
enum_constant public static final android.graphics.Shader.TileMode MIRROR;
enum_constant public static final android.graphics.Shader.TileMode REPEAT;
}
@@ -16348,7 +16395,9 @@ package android.graphics.fonts {
method @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
method @NonNull public java.nio.ByteBuffer getBuffer();
method @Nullable public java.io.File getFile();
+ method public float getGlyphBounds(@IntRange(from=0) int, @NonNull android.graphics.Paint, @Nullable android.graphics.RectF);
method @NonNull public android.os.LocaleList getLocaleList();
+ method public void getMetrics(@NonNull android.graphics.Paint, @Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.graphics.fonts.FontStyle getStyle();
method @IntRange(from=0) public int getTtcIndex();
}
@@ -16360,6 +16409,7 @@ package android.graphics.fonts {
ctor public Font.Builder(@NonNull android.os.ParcelFileDescriptor, @IntRange(from=0) long, @IntRange(from=0xffffffff) long);
ctor public Font.Builder(@NonNull android.content.res.AssetManager, @NonNull String);
ctor public Font.Builder(@NonNull android.content.res.Resources, int);
+ ctor public Font.Builder(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.Font build() throws java.io.IOException;
method @NonNull public android.graphics.fonts.Font.Builder setFontVariationSettings(@Nullable String);
method @NonNull public android.graphics.fonts.Font.Builder setFontVariationSettings(@Nullable android.graphics.fonts.FontVariationAxis[]);
@@ -16464,6 +16514,23 @@ package android.graphics.pdf {
package android.graphics.text {
+ public class GlyphStyle {
+ ctor public GlyphStyle(@ColorInt int, @FloatRange(from=0) float, @FloatRange(from=0) float, @FloatRange(from=0) float, int);
+ ctor public GlyphStyle(@NonNull android.graphics.Paint);
+ method public void applyToPaint(@NonNull android.graphics.Paint);
+ method @ColorInt public int getColor();
+ method public int getFlags();
+ method @FloatRange(from=0) public float getFontSize();
+ method @FloatRange(from=0) public float getScaleX();
+ method @FloatRange(from=0) public float getSkewX();
+ method public void setColor(@ColorInt int);
+ method public void setFlags(int);
+ method public void setFontSize(@FloatRange(from=0) float);
+ method public void setFromPaint(@NonNull android.graphics.Paint);
+ method public void setScaleX(@FloatRange(from=0) float);
+ method public void setSkewX(@FloatRange(from=0) float);
+ }
+
public class LineBreaker {
method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int);
field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -16524,6 +16591,25 @@ package android.graphics.text {
method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
}
+ public final class PositionedGlyphs {
+ method public float getAscent();
+ method public float getDescent();
+ method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
+ method public float getOriginX();
+ method public float getOriginY();
+ method public float getPositionX(@IntRange(from=0) int);
+ method public float getPositionY(@IntRange(from=0) int);
+ method @NonNull public android.graphics.text.GlyphStyle getStyle();
+ method public float getTotalAdvance();
+ method @IntRange(from=0) public int glyphCount();
+ }
+
+ public class TextShaper {
+ method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull char[], int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
+ method @NonNull public static android.graphics.text.PositionedGlyphs shapeTextRun(@NonNull CharSequence, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
+ }
+
}
package android.hardware {
@@ -24888,6 +24974,10 @@ package android.media {
method public double getAttributeDouble(@NonNull String, double);
method public int getAttributeInt(@NonNull String, int);
method @Nullable public long[] getAttributeRange(@NonNull String);
+ method public long getDateTime();
+ method public long getDateTimeDigitized();
+ method public long getDateTimeOriginal();
+ method public long getGpsDateTime();
method public boolean getLatLong(float[]);
method public byte[] getThumbnail();
method public android.graphics.Bitmap getThumbnailBitmap();
@@ -25358,6 +25448,7 @@ package android.media {
public static final class MediaCodec.CryptoInfo {
ctor public MediaCodec.CryptoInfo();
+ method @NonNull public android.media.MediaCodec.CryptoInfo.Pattern getPattern();
method public void set(int, @NonNull int[], @NonNull int[], @NonNull byte[], @NonNull byte[], int);
method public void setPattern(android.media.MediaCodec.CryptoInfo.Pattern);
field public byte[] iv;
@@ -26234,6 +26325,7 @@ package android.media {
field public static final String KEY_ROTATION = "rotation-degrees";
field public static final String KEY_SAMPLE_RATE = "sample-rate";
field public static final String KEY_SLICE_HEIGHT = "slice-height";
+ field public static final String KEY_SLOW_MOTION_MARKERS = "slow-motion-markers";
field public static final String KEY_STRIDE = "stride";
field public static final String KEY_TEMPORAL_LAYERING = "ts-schema";
field public static final String KEY_TILE_HEIGHT = "tile-height";
@@ -26285,6 +26377,7 @@ package android.media {
method public boolean containsKey(String);
method public int describeContents();
method public android.graphics.Bitmap getBitmap(String);
+ method @IntRange(from=1) public int getBitmapDimensionLimit();
method @NonNull public android.media.MediaDescription getDescription();
method public long getLong(String);
method public android.media.Rating getRating(String);
@@ -26334,6 +26427,7 @@ package android.media {
method public android.media.MediaMetadata.Builder putRating(String, android.media.Rating);
method public android.media.MediaMetadata.Builder putString(String, String);
method public android.media.MediaMetadata.Builder putText(String, CharSequence);
+ method @NonNull public android.media.MediaMetadata.Builder setBitmapDimensionLimit(@IntRange(from=1) int);
}
@Deprecated public abstract class MediaMetadataEditor {
@@ -35135,7 +35229,7 @@ package android.os {
method public int dataCapacity();
method public int dataPosition();
method public int dataSize();
- method public void enforceInterface(String);
+ method public void enforceInterface(@NonNull String);
method public boolean hasFileDescriptors();
method public byte[] marshall();
method @NonNull public static android.os.Parcel obtain();
@@ -35206,7 +35300,7 @@ package android.os {
method public void writeFloatArray(@Nullable float[]);
method public void writeInt(int);
method public void writeIntArray(@Nullable int[]);
- method public void writeInterfaceToken(String);
+ method public void writeInterfaceToken(@NonNull String);
method public void writeList(@Nullable java.util.List);
method public void writeLong(long);
method public void writeLongArray(@Nullable long[]);
@@ -38650,62 +38744,62 @@ package android.provider {
method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
}
- public final class FontRequest {
- ctor public FontRequest(@NonNull String, @NonNull String, @NonNull String);
- ctor public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
- method public java.util.List<java.util.List<byte[]>> getCertificates();
- method public String getProviderAuthority();
- method public String getProviderPackage();
- method public String getQuery();
- }
-
- public class FontsContract {
- method public static android.graphics.Typeface buildTypeface(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontInfo[]);
- method @NonNull public static android.provider.FontsContract.FontFamilyResult fetchFonts(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
- method public static void requestFonts(@NonNull android.content.Context, @NonNull android.provider.FontRequest, @NonNull android.os.Handler, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontRequestCallback);
- }
-
- public static final class FontsContract.Columns implements android.provider.BaseColumns {
- field public static final String FILE_ID = "file_id";
- field public static final String ITALIC = "font_italic";
- field public static final String RESULT_CODE = "result_code";
- field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
- field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
- field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
- field public static final int RESULT_CODE_OK = 0; // 0x0
- field public static final String TTC_INDEX = "font_ttc_index";
- field public static final String VARIATION_SETTINGS = "font_variation_settings";
- field public static final String WEIGHT = "font_weight";
- }
-
- public static class FontsContract.FontFamilyResult {
- method @NonNull public android.provider.FontsContract.FontInfo[] getFonts();
- method public int getStatusCode();
- field public static final int STATUS_OK = 0; // 0x0
- field public static final int STATUS_REJECTED = 3; // 0x3
- field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
- field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
- }
-
- public static class FontsContract.FontInfo {
- method @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
- method public int getResultCode();
- method @IntRange(from=0) public int getTtcIndex();
- method @NonNull public android.net.Uri getUri();
- method @IntRange(from=1, to=1000) public int getWeight();
- method public boolean isItalic();
- }
-
- public static class FontsContract.FontRequestCallback {
- ctor public FontsContract.FontRequestCallback();
- method public void onTypefaceRequestFailed(int);
- method public void onTypefaceRetrieved(android.graphics.Typeface);
- field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
- field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
- field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
- field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
- field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
- field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+ @Deprecated public final class FontRequest {
+ ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String);
+ ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
+ method @Deprecated public java.util.List<java.util.List<byte[]>> getCertificates();
+ method @Deprecated public String getProviderAuthority();
+ method @Deprecated public String getProviderPackage();
+ method @Deprecated public String getQuery();
+ }
+
+ @Deprecated public class FontsContract {
+ method @Deprecated public static android.graphics.Typeface buildTypeface(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontInfo[]);
+ method @Deprecated @NonNull public static android.provider.FontsContract.FontFamilyResult fetchFonts(@NonNull android.content.Context, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @Deprecated public static void requestFonts(@NonNull android.content.Context, @NonNull android.provider.FontRequest, @NonNull android.os.Handler, @Nullable android.os.CancellationSignal, @NonNull android.provider.FontsContract.FontRequestCallback);
+ }
+
+ @Deprecated public static final class FontsContract.Columns implements android.provider.BaseColumns {
+ field @Deprecated public static final String FILE_ID = "file_id";
+ field @Deprecated public static final String ITALIC = "font_italic";
+ field @Deprecated public static final String RESULT_CODE = "result_code";
+ field @Deprecated public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
+ field @Deprecated public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
+ field @Deprecated public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
+ field @Deprecated public static final int RESULT_CODE_OK = 0; // 0x0
+ field @Deprecated public static final String TTC_INDEX = "font_ttc_index";
+ field @Deprecated public static final String VARIATION_SETTINGS = "font_variation_settings";
+ field @Deprecated public static final String WEIGHT = "font_weight";
+ }
+
+ @Deprecated public static class FontsContract.FontFamilyResult {
+ method @Deprecated @NonNull public android.provider.FontsContract.FontInfo[] getFonts();
+ method @Deprecated public int getStatusCode();
+ field @Deprecated public static final int STATUS_OK = 0; // 0x0
+ field @Deprecated public static final int STATUS_REJECTED = 3; // 0x3
+ field @Deprecated public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+ field @Deprecated public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+ }
+
+ @Deprecated public static class FontsContract.FontInfo {
+ method @Deprecated @Nullable public android.graphics.fonts.FontVariationAxis[] getAxes();
+ method @Deprecated public int getResultCode();
+ method @Deprecated @IntRange(from=0) public int getTtcIndex();
+ method @Deprecated @NonNull public android.net.Uri getUri();
+ method @Deprecated @IntRange(from=1, to=1000) public int getWeight();
+ method @Deprecated public boolean isItalic();
+ }
+
+ @Deprecated public static class FontsContract.FontRequestCallback {
+ ctor @Deprecated public FontsContract.FontRequestCallback();
+ method @Deprecated public void onTypefaceRequestFailed(int);
+ method @Deprecated public void onTypefaceRetrieved(android.graphics.Typeface);
+ field @Deprecated public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+ field @Deprecated public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+ field @Deprecated public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+ field @Deprecated public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+ field @Deprecated public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+ field @Deprecated public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
}
@Deprecated public final class LiveFolders implements android.provider.BaseColumns {
@@ -42151,11 +42245,13 @@ package android.service.notification {
method public long getLastAudiblyAlertedMillis();
method public String getOverrideGroupKey();
method public int getRank();
+ method @Nullable public android.content.pm.ShortcutInfo getShortcutInfo();
method @NonNull public java.util.List<android.app.Notification.Action> getSmartActions();
method @NonNull public java.util.List<java.lang.CharSequence> getSmartReplies();
method public int getSuppressedVisualEffects();
method public int getUserSentiment();
method public boolean isAmbient();
+ method public boolean isConversation();
method public boolean isSuspended();
method public boolean matchesInterruptionFilter();
field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
@@ -42430,54 +42526,8 @@ package android.service.textservice {
package android.service.voice {
- public class AlwaysOnHotwordDetector {
- method public android.content.Intent createEnrollIntent();
- method public android.content.Intent createReEnrollIntent();
- method public android.content.Intent createUnEnrollIntent();
- method public int getParameter(int);
- method public int getSupportedAudioCapabilities();
- method public int getSupportedRecognitionModes();
- method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
- method public int setParameter(int, int);
- method public boolean startRecognition(int);
- method public boolean stopRecognition();
- field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
- field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
- field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
- field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
- field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
- field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
- field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
- field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
- field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
- field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
- field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
- field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
- field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
- }
-
- public abstract static class AlwaysOnHotwordDetector.Callback {
- ctor public AlwaysOnHotwordDetector.Callback();
- method public abstract void onAvailabilityChanged(int);
- method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
- method public abstract void onError();
- method public abstract void onRecognitionPaused();
- method public abstract void onRecognitionResumed();
- }
-
- public static class AlwaysOnHotwordDetector.EventPayload {
- method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
- method @Nullable public byte[] getTriggerAudio();
- }
-
- public static final class AlwaysOnHotwordDetector.ModelParamRange {
- method public int getEnd();
- method public int getStart();
- }
-
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
- method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method public int getDisabledShowContext();
method public static boolean isActiveService(android.content.Context, android.content.ComponentName);
method public android.os.IBinder onBind(android.content.Intent);
@@ -44248,6 +44298,7 @@ package android.telecom {
field public static final int MISSED = 5; // 0x5
field public static final int OTHER = 9; // 0x9
field public static final String REASON_EMERGENCY_CALL_PLACED = "REASON_EMERGENCY_CALL_PLACED";
+ field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
field public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
field public static final String REASON_WIFI_ON_BUT_WFC_OFF = "REASON_WIFI_ON_BUT_WFC_OFF";
field public static final int REJECTED = 6; // 0x6
@@ -44947,6 +44998,7 @@ package android.telephony {
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
+ field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
@@ -45141,6 +45193,10 @@ package android.telephony {
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
field public static final int SERVICE_CLASS_NONE = 0; // 0x0
field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
+ field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
+ field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0
+ field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3
+ field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1
}
public static final class CarrierConfigManager.Apn {
@@ -45315,9 +45371,9 @@ package android.telephony {
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoWcdma> CREATOR;
}
- public abstract class CellLocation {
- ctor public CellLocation();
- method public static android.telephony.CellLocation getEmpty();
+ @Deprecated public abstract class CellLocation {
+ ctor @Deprecated public CellLocation();
+ method @Deprecated public static android.telephony.CellLocation getEmpty();
method @Deprecated public static void requestLocationUpdate();
}
@@ -46188,6 +46244,7 @@ package android.telephony {
method @NonNull public android.os.Bundle getCarrierConfigValues();
method @Deprecated public static android.telephony.SmsManager getDefault();
method public static int getDefaultSmsSubscriptionId();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getSmsCapacityOnIcc();
method @Deprecated public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.SMS_FINANCIAL_TRANSACTIONS) public void getSmsMessagesForFinancialApp(android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.SmsManager.FinancialSmsCallback);
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSmscAddress();
@@ -46530,6 +46587,7 @@ package android.telephony {
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String[] getForbiddenPlmns();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getGroupIdLevel1();
method public String getIccAuthentication(int, int, String);
@@ -46554,7 +46612,7 @@ package android.telephony {
method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
- method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
method @Nullable public android.telephony.SignalStrength getSignalStrength();
method public int getSimCarrierId();
method @Nullable public CharSequence getSimCarrierIdName();
@@ -46577,7 +46635,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
- method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
+ method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
method public boolean hasCarrierPrivileges();
method public boolean hasIccCard();
method @Deprecated public boolean iccCloseLogicalChannel(int);
@@ -46835,19 +46893,19 @@ package android.telephony {
package android.telephony.cdma {
- public class CdmaCellLocation extends android.telephony.CellLocation {
- ctor public CdmaCellLocation();
- ctor public CdmaCellLocation(android.os.Bundle);
- method public static double convertQuartSecToDecDegrees(int);
- method public void fillInNotifierBundle(android.os.Bundle);
- method public int getBaseStationId();
- method public int getBaseStationLatitude();
- method public int getBaseStationLongitude();
- method public int getNetworkId();
- method public int getSystemId();
- method public void setCellLocationData(int, int, int);
- method public void setCellLocationData(int, int, int, int, int);
- method public void setStateInvalid();
+ @Deprecated public class CdmaCellLocation extends android.telephony.CellLocation {
+ ctor @Deprecated public CdmaCellLocation();
+ ctor @Deprecated public CdmaCellLocation(android.os.Bundle);
+ method @Deprecated public static double convertQuartSecToDecDegrees(int);
+ method @Deprecated public void fillInNotifierBundle(android.os.Bundle);
+ method @Deprecated public int getBaseStationId();
+ method @Deprecated public int getBaseStationLatitude();
+ method @Deprecated public int getBaseStationLongitude();
+ method @Deprecated public int getNetworkId();
+ method @Deprecated public int getSystemId();
+ method @Deprecated public void setCellLocationData(int, int, int);
+ method @Deprecated public void setCellLocationData(int, int, int, int, int);
+ method @Deprecated public void setStateInvalid();
}
}
@@ -47047,15 +47105,15 @@ package android.telephony.euicc {
package android.telephony.gsm {
- public class GsmCellLocation extends android.telephony.CellLocation {
- ctor public GsmCellLocation();
- ctor public GsmCellLocation(android.os.Bundle);
- method public void fillInNotifierBundle(android.os.Bundle);
- method public int getCid();
- method public int getLac();
- method public int getPsc();
- method public void setLacAndCid(int, int);
- method public void setStateInvalid();
+ @Deprecated public class GsmCellLocation extends android.telephony.CellLocation {
+ ctor @Deprecated public GsmCellLocation();
+ ctor @Deprecated public GsmCellLocation(android.os.Bundle);
+ method @Deprecated public void fillInNotifierBundle(android.os.Bundle);
+ method @Deprecated public int getCid();
+ method @Deprecated public int getLac();
+ method @Deprecated public int getPsc();
+ method @Deprecated public void setLacAndCid(int, int);
+ method @Deprecated public void setStateInvalid();
}
@Deprecated public final class SmsManager {
@@ -48142,6 +48200,10 @@ package android.text {
method @NonNull public android.text.StaticLayout.Builder setUseLineSpacingFromFallbacks(boolean);
}
+ public class StyledTextShaper {
+ method @NonNull public static java.util.List<android.graphics.text.PositionedGlyphs> shapeText(@NonNull CharSequence, int, int, @NonNull android.text.TextDirectionHeuristic, @NonNull android.text.TextPaint);
+ }
+
public interface TextDirectionHeuristic {
method public boolean isRtl(char[], int, int);
method public boolean isRtl(CharSequence, int, int);
@@ -48969,6 +49031,7 @@ package android.text.style {
field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionSpan> CREATOR;
field public static final int FLAG_AUTO_CORRECTION = 4; // 0x4
field public static final int FLAG_EASY_CORRECT = 1; // 0x1
+ field public static final int FLAG_GRAMMAR_ERROR = 8; // 0x8
field public static final int FLAG_MISSPELLED = 2; // 0x2
field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5
field @Deprecated public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
@@ -51787,6 +51850,33 @@ package android.view {
field public int toolType;
}
+ public interface OnReceiveContentCallback<T extends android.view.View> {
+ method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull T);
+ method public boolean onReceiveContent(@NonNull T, @NonNull android.view.OnReceiveContentCallback.Payload);
+ }
+
+ public static final class OnReceiveContentCallback.Payload {
+ method @NonNull public android.content.ClipData getClip();
+ method @Nullable public android.os.Bundle getExtras();
+ method public int getFlags();
+ method @Nullable public android.net.Uri getLinkUri();
+ method public int getSource();
+ field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
+ field public static final int SOURCE_AUTOFILL = 3; // 0x3
+ field public static final int SOURCE_CLIPBOARD = 0; // 0x0
+ field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2
+ field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
+ field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4
+ }
+
+ public static final class OnReceiveContentCallback.Payload.Builder {
+ ctor public OnReceiveContentCallback.Payload.Builder(@NonNull android.content.ClipData, int);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload build();
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setFlags(int);
+ method @NonNull public android.view.OnReceiveContentCallback.Payload.Builder setLinkUri(@Nullable android.net.Uri);
+ }
+
public abstract class OrientationEventListener {
ctor public OrientationEventListener(android.content.Context);
ctor public OrientationEventListener(android.content.Context, int);
@@ -52338,6 +52428,7 @@ package android.view {
method @IdRes public int getNextFocusRightId();
method @IdRes public int getNextFocusUpId();
method public android.view.View.OnFocusChangeListener getOnFocusChangeListener();
+ method @Nullable public android.view.OnReceiveContentCallback<? extends android.view.View> getOnReceiveContentCallback();
method @ColorInt public int getOutlineAmbientShadowColor();
method public android.view.ViewOutlineProvider getOutlineProvider();
method @ColorInt public int getOutlineSpotShadowColor();
@@ -52689,6 +52780,7 @@ package android.view {
method public void setOnHoverListener(android.view.View.OnHoverListener);
method public void setOnKeyListener(android.view.View.OnKeyListener);
method public void setOnLongClickListener(@Nullable android.view.View.OnLongClickListener);
+ method public void setOnReceiveContentCallback(@Nullable android.view.OnReceiveContentCallback<? extends android.view.View>);
method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener);
method @Deprecated public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener);
method public void setOnTouchListener(android.view.View.OnTouchListener);
@@ -56307,6 +56399,7 @@ package android.view.textservice {
field @NonNull public static final android.os.Parcelable.Creator<android.view.textservice.SuggestionsInfo> CREATOR;
field public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 4; // 0x4
field public static final int RESULT_ATTR_IN_THE_DICTIONARY = 1; // 0x1
+ field public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 8; // 0x8
field public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 2; // 0x2
}
@@ -58951,17 +59044,6 @@ package android.widget {
method public android.view.View newGroupView(android.content.Context, android.database.Cursor, boolean, android.view.ViewGroup);
}
- public interface RichContentReceiver<T extends android.view.View> {
- method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes();
- method public boolean onReceive(@NonNull T, @NonNull android.content.ClipData, int, int);
- field public static final int FLAG_CONVERT_TO_PLAIN_TEXT = 1; // 0x1
- field public static final int SOURCE_AUTOFILL = 3; // 0x3
- field public static final int SOURCE_CLIPBOARD = 0; // 0x0
- field public static final int SOURCE_DRAG_AND_DROP = 2; // 0x2
- field public static final int SOURCE_INPUT_METHOD = 1; // 0x1
- field public static final int SOURCE_PROCESS_TEXT = 4; // 0x4
- }
-
public class ScrollView extends android.widget.FrameLayout {
ctor public ScrollView(android.content.Context);
ctor public ScrollView(android.content.Context, android.util.AttributeSet);
@@ -59516,10 +59598,10 @@ package android.widget {
method public int getMinWidth();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
+ method @Nullable public android.view.OnReceiveContentCallback<android.widget.TextView> getOnReceiveContentCallback();
method public android.text.TextPaint getPaint();
method public int getPaintFlags();
method public String getPrivateImeOptions();
- method @NonNull public android.widget.RichContentReceiver<android.widget.TextView> getRichContentReceiver();
method public int getSelectionEnd();
method public int getSelectionStart();
method @ColorInt public int getShadowColor();
@@ -59647,7 +59729,6 @@ package android.widget {
method public void setPaintFlags(int);
method public void setPrivateImeOptions(String);
method public void setRawInputType(int);
- method public void setRichContentReceiver(@NonNull android.widget.RichContentReceiver<android.widget.TextView>);
method public void setScroller(android.widget.Scroller);
method public void setSelectAllOnFocus(boolean);
method public void setShadowLayer(float, float, float, int);
@@ -59688,7 +59769,6 @@ package android.widget {
method public void setWidth(int);
field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
- field @NonNull public static final android.widget.RichContentReceiver<android.widget.TextView> DEFAULT_RICH_CONTENT_RECEIVER;
}
public enum TextView.BufferType {
@@ -59705,6 +59785,12 @@ package android.widget {
field @NonNull public static final android.os.Parcelable.Creator<android.widget.TextView.SavedState> CREATOR;
}
+ public class TextViewOnReceiveContentCallback implements android.view.OnReceiveContentCallback<android.widget.TextView> {
+ ctor public TextViewOnReceiveContentCallback();
+ method @NonNull public java.util.Set<java.lang.String> getSupportedMimeTypes(@NonNull android.widget.TextView);
+ method public boolean onReceiveContent(@NonNull android.widget.TextView, @NonNull android.view.OnReceiveContentCallback.Payload);
+ }
+
public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter {
method @Nullable public android.content.res.Resources.Theme getDropDownViewTheme();
method public void setDropDownViewTheme(@Nullable android.content.res.Resources.Theme);
diff --git a/non-updatable-api/module-lib-current.txt b/non-updatable-api/module-lib-current.txt
index ae576d4e01ad..e135a3b734cf 100644
--- a/non-updatable-api/module-lib-current.txt
+++ b/non-updatable-api/module-lib-current.txt
@@ -1,17 +1,35 @@
// Signature format: 2.0
package android.app {
+ public class ActivityManager {
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
+ method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener);
+ }
+
public class AppOpsManager {
field public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage";
}
+ public abstract class HomeVisibilityListener {
+ ctor public HomeVisibilityListener();
+ method public abstract void onHomeVisibilityChanged(boolean);
+ }
+
public class NotificationManager {
method public boolean hasEnabledNotificationListener(@NonNull String, @NonNull android.os.UserHandle);
field public static final String ACTION_NOTIFICATION_LISTENER_ENABLED_CHANGED = "android.app.action.NOTIFICATION_LISTENER_ENABLED_CHANGED";
}
public class StatusBarManager {
- method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
+ }
+
+}
+
+package android.app.role {
+
+ public final class RoleManager {
+ method @Nullable public String getSmsRoleHolder(int);
}
}
@@ -45,8 +63,8 @@ package android.media {
field public static final int FLAG_FROM_KEY = 4096; // 0x1000
}
- public static final class MediaMetadata.Builder {
- ctor public MediaMetadata.Builder(@NonNull android.media.MediaMetadata, @IntRange(from=1) int);
+ public class MediaMetadataRetriever implements java.lang.AutoCloseable {
+ field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
}
}
@@ -66,10 +84,11 @@ package android.media.session {
}
public final class MediaSessionManager {
+ method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, int, @Nullable android.os.Handler);
method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent);
- method public boolean dispatchMediaKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+ method public boolean dispatchMediaKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.view.KeyEvent, int);
- method public void dispatchVolumeKeyEventAsSystemService(@NonNull android.media.session.MediaSession.Token, @NonNull android.view.KeyEvent);
+ method public void dispatchVolumeKeyEventToSessionAsSystemService(@NonNull android.view.KeyEvent, @NonNull android.media.session.MediaSession.Token);
field public static final int RESULT_MEDIA_KEY_HANDLED = 1; // 0x1
field public static final int RESULT_MEDIA_KEY_NOT_HANDLED = 0; // 0x0
}
@@ -108,6 +127,7 @@ package android.os {
package android.provider {
public final class DeviceConfig {
+ field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt
index 310b55fa97f9..8730ba9a43b5 100644
--- a/non-updatable-api/system-current.txt
+++ b/non-updatable-api/system-current.txt
@@ -38,6 +38,7 @@ package android {
field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
field public static final String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
+ field public static final String BIND_MUSIC_RECOGNITION_SERVICE = "android.permission.BIND_MUSIC_RECOGNITION_SERVICE";
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
@@ -95,6 +96,7 @@ package android {
field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM";
field public static final String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS";
+ field public static final String INSTALL_LOCATION_TIME_ZONE_PROVIDER = "android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER";
field public static final String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
field public static final String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
field public static final String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
@@ -119,12 +121,15 @@ package android {
field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION";
field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+ field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION";
+ field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
field public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
+ field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION";
field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
@@ -305,6 +310,7 @@ package android {
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemGallery = 17039399; // 0x1040027
+ field public static final int config_systemVideoCall = 17039401; // 0x1040029
}
public static final class R.style {
@@ -669,8 +675,10 @@ package android.app {
public class NotificationManager {
method @NonNull public java.util.List<java.lang.String> getAllowedAssistantAdjustments();
method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public java.util.List<android.content.ComponentName> getEnabledNotificationListeners();
method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean);
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL = "android.app.action.CLOSE_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_OPEN_NOTIFICATION_HANDLER_PANEL = "android.app.action.OPEN_NOTIFICATION_HANDLER_PANEL";
field @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE) public static final String ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL = "android.app.action.TOGGLE_NOTIFICATION_HANDLER_PANEL";
@@ -1329,6 +1337,57 @@ package android.app.role {
}
+package android.app.time {
+
+ public final class TimeManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void addTimeZoneDetectorListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public android.app.time.TimeZoneCapabilitiesAndConfig getTimeZoneCapabilitiesAndConfig();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public void removeTimeZoneDetectorListener(@NonNull android.app.time.TimeManager.TimeZoneDetectorListener);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION) public boolean updateTimeZoneConfiguration(@NonNull android.app.time.TimeZoneConfiguration);
+ }
+
+ @java.lang.FunctionalInterface public static interface TimeManager.TimeZoneDetectorListener {
+ method public void onChange();
+ }
+
+ public final class TimeZoneCapabilities implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConfigureAutoDetectionEnabledCapability();
+ method public int getConfigureGeoDetectionEnabledCapability();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPABILITY_NOT_ALLOWED = 20; // 0x14
+ field public static final int CAPABILITY_NOT_APPLICABLE = 30; // 0x1e
+ field public static final int CAPABILITY_NOT_SUPPORTED = 10; // 0xa
+ field public static final int CAPABILITY_POSSESSED = 40; // 0x28
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilities> CREATOR;
+ }
+
+ public final class TimeZoneCapabilitiesAndConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.app.time.TimeZoneCapabilities getCapabilities();
+ method @NonNull public android.app.time.TimeZoneConfiguration getConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneCapabilitiesAndConfig> CREATOR;
+ }
+
+ public final class TimeZoneConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isAutoDetectionEnabled();
+ method public boolean isGeoDetectionEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.time.TimeZoneConfiguration> CREATOR;
+ }
+
+ public static final class TimeZoneConfiguration.Builder {
+ ctor public TimeZoneConfiguration.Builder();
+ ctor public TimeZoneConfiguration.Builder(@NonNull android.app.time.TimeZoneConfiguration);
+ method @NonNull public android.app.time.TimeZoneConfiguration build();
+ method @NonNull public android.app.time.TimeZoneConfiguration.Builder setAutoDetectionEnabled(boolean);
+ method @NonNull public android.app.time.TimeZoneConfiguration.Builder setGeoDetectionEnabled(boolean);
+ }
+
+}
+
package android.app.usage {
public final class CacheQuotaHint implements android.os.Parcelable {
@@ -2097,6 +2156,7 @@ package android.content.pm {
field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4
field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800
+ field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000
field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000
field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000
field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
@@ -4056,7 +4116,7 @@ package android.location {
method @Deprecated @NonNull public String getProvider();
method public int getQuality();
method @Deprecated public float getSmallestDisplacement();
- method @Nullable public android.os.WorkSource getWorkSource();
+ method @NonNull public android.os.WorkSource getWorkSource();
method public boolean isHiddenFromAppOps();
method public boolean isLocationSettingsIgnored();
method public boolean isLowPower();
@@ -4339,6 +4399,8 @@ package android.media {
}
public static final class MediaTranscodeManager.TranscodingRequest {
+ method public int getClientPid();
+ method public int getClientUid();
method @NonNull public android.net.Uri getDestinationUri();
method public int getPriority();
method @NonNull public android.net.Uri getSourceUri();
@@ -4349,6 +4411,8 @@ package android.media {
public static final class MediaTranscodeManager.TranscodingRequest.Builder {
ctor public MediaTranscodeManager.TranscodingRequest.Builder();
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest build();
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientPid(int);
+ method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setClientUid(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setDestinationUri(@NonNull android.net.Uri);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setPriority(int);
method @NonNull public android.media.MediaTranscodeManager.TranscodingRequest.Builder setSourceUri(@NonNull android.net.Uri);
@@ -4498,6 +4562,58 @@ package android.media.audiopolicy {
}
+package android.media.musicrecognition {
+
+ public class MusicRecognitionManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_MUSIC_RECOGNITION) public void beginStreamingSearch(@NonNull android.media.musicrecognition.RecognitionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.media.musicrecognition.MusicRecognitionManager.RecognitionCallback);
+ field public static final int RECOGNITION_FAILED_AUDIO_UNAVAILABLE = 7; // 0x7
+ field public static final int RECOGNITION_FAILED_NOT_FOUND = 1; // 0x1
+ field public static final int RECOGNITION_FAILED_NO_CONNECTIVITY = 2; // 0x2
+ field public static final int RECOGNITION_FAILED_SERVICE_KILLED = 5; // 0x5
+ field public static final int RECOGNITION_FAILED_SERVICE_UNAVAILABLE = 3; // 0x3
+ field public static final int RECOGNITION_FAILED_TIMEOUT = 6; // 0x6
+ field public static final int RECOGNITION_FAILED_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public static interface MusicRecognitionManager.RecognitionCallback {
+ method public void onAudioStreamClosed();
+ method public void onRecognitionFailed(@NonNull android.media.musicrecognition.RecognitionRequest, int);
+ method public void onRecognitionSucceeded(@NonNull android.media.musicrecognition.RecognitionRequest, @NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public abstract class MusicRecognitionService extends android.app.Service {
+ ctor public MusicRecognitionService();
+ method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);
+ }
+
+ public static interface MusicRecognitionService.Callback {
+ method public void onRecognitionFailed(int);
+ method public void onRecognitionSucceeded(@NonNull android.media.MediaMetadata, @Nullable android.os.Bundle);
+ }
+
+ public final class RecognitionRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.media.AudioAttributes getAudioAttributes();
+ method @NonNull public android.media.AudioFormat getAudioFormat();
+ method public int getCaptureSession();
+ method public int getIgnoreBeginningFrames();
+ method public int getMaxAudioLengthSeconds();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.musicrecognition.RecognitionRequest> CREATOR;
+ }
+
+ public static final class RecognitionRequest.Builder {
+ ctor public RecognitionRequest.Builder();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest build();
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioAttributes(@NonNull android.media.AudioAttributes);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setCaptureSession(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setIgnoreBeginningFrames(int);
+ method @NonNull public android.media.musicrecognition.RecognitionRequest.Builder setMaxAudioLengthSeconds(int);
+ }
+
+}
+
package android.media.session {
public final class MediaSessionManager {
@@ -4863,6 +4979,7 @@ package android.media.tv.tuner {
method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities();
method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
+ method public int linkFrontendToCiCam(int);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
method @Nullable public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
method @Nullable public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
@@ -4876,9 +4993,13 @@ package android.media.tv.tuner {
method public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
method public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
method public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
+ method public int unlinkFrontendToCiCam(int);
method public void updateResourcePriority(int, int);
field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
+ field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+ field public static final int INVALID_LTS_ID = -1; // 0xffffffff
+ field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
field public static final int INVALID_STREAM_ID = 65535; // 0xffff
field public static final long INVALID_TIMESTAMP = -1L; // 0xffffffffffffffffL
field public static final int INVALID_TS_PID = 65535; // 0xffff
@@ -4898,6 +5019,13 @@ package android.media.tv.tuner {
method public void onResourceLost(@NonNull android.media.tv.tuner.Tuner);
}
+ public final class TunerVersionChecker {
+ method public static int getTunerVersion();
+ field public static final int TUNER_VERSION_1_0 = 65536; // 0x10000
+ field public static final int TUNER_VERSION_1_1 = 65537; // 0x10001
+ field public static final int TUNER_VERSION_UNKNOWN = 0; // 0x0
+ }
+
}
package android.media.tv.tuner.dvr {
@@ -5031,6 +5159,7 @@ package android.media.tv.tuner.filter {
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
method public int flush();
method public int getId();
+ method public long getId64Bit();
method public int read(@NonNull byte[], long, long);
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int start();
@@ -5082,16 +5211,19 @@ package android.media.tv.tuner.filter {
method @NonNull public static android.media.tv.tuner.filter.IpFilterConfiguration.Builder builder();
method @NonNull @Size(min=4, max=16) public byte[] getDstIpAddress();
method public int getDstPort();
+ method public int getIpFilterContextId();
method @NonNull @Size(min=4, max=16) public byte[] getSrcIpAddress();
method public int getSrcPort();
method public int getType();
method public boolean isPassthrough();
+ field public static final int INVALID_IP_FILTER_CONTEXT_ID = -1; // 0xffffffff
}
public static final class IpFilterConfiguration.Builder {
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration build();
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstIpAddress(@NonNull byte[]);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setDstPort(int);
+ method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setIpFilterContextId(int);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setPassthrough(boolean);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSettings(@Nullable android.media.tv.tuner.filter.Settings);
method @NonNull public android.media.tv.tuner.filter.IpFilterConfiguration.Builder setSrcIpAddress(@NonNull byte[]);
@@ -5131,6 +5263,8 @@ package android.media.tv.tuner.filter {
public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
+ method public int getMpuSequenceNumber();
+ method public long getPts();
method public int getScHevcIndexMask();
}
@@ -5293,6 +5427,7 @@ package android.media.tv.tuner.filter {
public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
method public long getDataLength();
method public int getPacketId();
+ method public long getPts();
method public int getScIndexMask();
method public int getTsIndexMask();
}
@@ -9135,7 +9270,53 @@ package android.service.trust {
package android.service.voice {
+ public class AlwaysOnHotwordDetector {
+ method @Nullable public android.content.Intent createEnrollIntent();
+ method @Nullable public android.content.Intent createReEnrollIntent();
+ method @Nullable public android.content.Intent createUnEnrollIntent();
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int getParameter(int);
+ method public int getSupportedAudioCapabilities();
+ method public int getSupportedRecognitionModes();
+ method @Nullable @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public int setParameter(int, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition();
+ field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+ field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
+ field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
+ field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
+ field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
+ field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
+ field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2
+ field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1
+ field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff
+ }
+
+ public abstract static class AlwaysOnHotwordDetector.Callback {
+ ctor public AlwaysOnHotwordDetector.Callback();
+ method public abstract void onAvailabilityChanged(int);
+ method public abstract void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
+ method public abstract void onError();
+ method public abstract void onRecognitionPaused();
+ method public abstract void onRecognitionResumed();
+ }
+
+ public static class AlwaysOnHotwordDetector.EventPayload {
+ method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
+ method @Nullable public byte[] getTriggerAudio();
+ }
+
+ public static final class AlwaysOnHotwordDetector.ModelParamRange {
+ method public int getEnd();
+ method public int getStart();
+ }
+
public class VoiceInteractionService extends android.app.Service {
+ method @NonNull public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
}
@@ -9253,10 +9434,6 @@ package android.telecom {
method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference);
}
- public final class DisconnectCause implements android.os.Parcelable {
- field public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
- }
-
public abstract class InCallService extends android.app.Service {
method @Deprecated public android.telecom.Phone getPhone();
method @Deprecated public void onPhoneCreated(android.telecom.Phone);
@@ -9526,6 +9703,22 @@ package android.telephony {
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
+ public final class CallForwardingInfo implements android.os.Parcelable {
+ ctor public CallForwardingInfo(boolean, int, @Nullable String, int);
+ method public int describeContents();
+ method @Nullable public String getNumber();
+ method public int getReason();
+ method public int getTimeoutSeconds();
+ method public boolean isEnabled();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
+ field public static final int REASON_ALL = 4; // 0x4
+ field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
+ field public static final int REASON_BUSY = 1; // 0x1
+ field public static final int REASON_NOT_REACHABLE = 3; // 0x3
+ field public static final int REASON_NO_REPLY = 2; // 0x2
+ field public static final int REASON_UNCONDITIONAL = 0; // 0x0
+ }
+
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
@@ -9715,6 +9908,25 @@ package android.telephony {
field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
}
+ public final class ModemActivityInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+ method public long getIdleTimeMillis();
+ method public static int getNumTxPowerLevels();
+ method public long getReceiveTimeMillis();
+ method public long getSleepTimeMillis();
+ method public long getTimestampMillis();
+ method public long getTransmitDurationMillisAtPowerLevel(int);
+ method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+ field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+ field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+ field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+ field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+ field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+ }
+
public final class NetworkRegistrationInfo implements android.os.Parcelable {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getRegistrationState();
@@ -10094,7 +10306,6 @@ package android.telephony {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
@@ -10187,6 +10398,8 @@ package android.telephony {
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -10240,6 +10453,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -10263,11 +10477,14 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
@@ -10300,6 +10517,10 @@ package android.telephony {
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
+ field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
+ field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
+ field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
+ field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -10314,6 +10535,8 @@ package android.telephony {
field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
field public static final int KEY_TYPE_EPDG = 1; // 0x1
field public static final int KEY_TYPE_WLAN = 2; // 0x2
+ field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
+ field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
@@ -10354,6 +10577,15 @@ package android.telephony {
field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0
}
+ public static interface TelephonyManager.CallForwardingInfoCallback {
+ method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
+ method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
@@ -10431,11 +10663,11 @@ package android.telephony.data {
method public int getSuggestedRetryTime();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
- field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
- field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
- field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+ field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+ field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
field public static final int LINK_STATUS_DORMANT = 1; // 0x1
field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
@@ -11719,11 +11951,12 @@ package android.webkit {
}
public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor createInstance();
method @Nullable public String findProxyForUrl(@NonNull String);
method @NonNull public static android.webkit.PacProcessor getInstance();
- method @NonNull public static android.webkit.PacProcessor getInstanceForNetwork(@Nullable android.net.Network);
method @Nullable public default android.net.Network getNetwork();
method public default void releasePacProcessor();
+ method public default void setNetwork(@Nullable android.net.Network);
method public boolean setProxyScript(@NonNull String);
}
@@ -11859,11 +12092,11 @@ package android.webkit {
}
public interface WebViewFactoryProvider {
+ method @NonNull public default android.webkit.PacProcessor createPacProcessor();
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
method @NonNull public default android.webkit.PacProcessor getPacProcessor();
- method @NonNull public default android.webkit.PacProcessor getPacProcessorForNetwork(@Nullable android.net.Network);
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml b/packages/CarSystemUI/res/drawable/car_ic_settings_icon.xml
new file mode 100644
index 000000000000..147f5397361e
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_settings_icon.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="@*android:dimen/status_bar_system_icon_size"
+ android:height="@*android:dimen/status_bar_system_icon_size"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@color/system_bar_icon_color"
+ android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4C9.75,2 9.54,2.18 9.51,2.42L9.13,5.07C8.52,5.32 7.96,5.66 7.44,6.05l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46C2.21,8.95 2.27,9.22 2.46,9.37l2.11,1.65C4.53,11.34 4.5,11.67 4.5,12s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65C9.54,21.82 9.75,22 10,22h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64L19.43,12.98zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5S13.93,15.5 12,15.5z"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml b/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml
index 1195d05da228..270d932714f3 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_user_icon.xml
@@ -21,5 +21,5 @@
android:viewportHeight="24">
<path
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"
- android:fillColor="@color/system_bar_user_icon_color"/>
+ android:fillColor="@color/system_bar_icon_color"/>
</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
new file mode 100644
index 000000000000..51d2b9d3e1e0
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/system_bar_background_pill.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <item>
+ <aapt:attr name="android:drawable">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/system_bar_background_pill_color"/>
+ <corners android:radius="30dp"/>
+ </shape>
+ </aapt:attr>
+ </item>
+ <item>
+ <aapt:attr name="android:drawable">
+ <ripple android:color="?android:attr/colorControlHighlight"/>
+ </aapt:attr>
+ </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index b07dde582e5f..8314ba5600a9 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -30,22 +30,13 @@
android:layout_height="wrap_content"
android:layoutDirection="ltr">
- <com.android.systemui.car.navigationbar.CarNavigationButton
+ <com.android.systemui.car.hvac.AdjustableTemperatureView
+ android:id="@+id/driver_hvac"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_alignParentStart="true"
- android:background="@null"
- systemui:broadcast="true"
- systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end">
-
- <com.android.systemui.car.hvac.AdjustableTemperatureView
- android:id="@+id/driver_hvac"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- systemui:hvacAreaId="49"
- systemui:hvacTempFormat="%.0f\u00B0" />
- </com.android.systemui.car.navigationbar.CarNavigationButton>
+ android:gravity="center_vertical"
+ systemui:hvacAreaId="49"
+ systemui:hvacTempFormat="%.0f\u00B0" />
<LinearLayout
android:layout_width="wrap_content"
@@ -66,69 +57,55 @@
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_home"
- systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
- systemui:selectedIcon="@drawable/car_ic_home_selected"/>
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"/>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/phone_nav"
style="@style/NavigationBarButton"
+ systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_phone"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
- systemui:packages="com.android.car.dialer"
- systemui:selectedIcon="@drawable/car_ic_phone_selected"/>
+ systemui:packages="com.android.car.dialer"/>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/grid_nav"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_apps"
- systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
- systemui:selectedIcon="@drawable/car_ic_apps_selected"/>
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"/>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/hvac"
style="@style/NavigationBarButton"
+ systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_hvac"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
- systemui:selectedIcon="@drawable/car_ic_hvac_selected"
systemui:broadcast="true"/>
<com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/notifications"
style="@style/NavigationBarButton"
+ systemui:highlightWhenSelected="true"
systemui:icon="@drawable/car_ic_notification"
systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/>
- <com.android.systemui.car.navigationbar.AssitantButton
- android:id="@+id/assist"
- style="@style/NavigationBarButton"
- systemui:icon="@drawable/ic_mic_white"
- systemui:useDefaultAppIconForRole="true"/>
-
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
- <com.android.systemui.car.navigationbar.CarNavigationButton
+ <com.android.systemui.car.hvac.AdjustableTemperatureView
+ android:id="@+id/passenger_hvac"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
- android:background="@null"
- systemui:broadcast="true"
- systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end">
-
- <com.android.systemui.car.hvac.AdjustableTemperatureView
- android:id="@+id/passenger_hvac"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:gravity="center_vertical"
- systemui:hvacAreaId="68"
- systemui:hvacTempFormat="%.0f\u00B0" />
- </com.android.systemui.car.navigationbar.CarNavigationButton>
+ android:gravity="center_vertical"
+ systemui:hvacAreaId="68"
+ systemui:hvacTempFormat="%.0f\u00B0" />
</RelativeLayout>
<LinearLayout
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index a8f115742023..9f79023b0f31 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -36,7 +36,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:tintMode="src_in"
- android:tint="@color/car_nav_icon_fill_color"
+ android:tint="@color/car_nav_icon_fill_color_selected"
android:clickable="false"
/>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index af8482a8c6a5..7994b19242cd 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -31,43 +31,32 @@
android:layoutDirection="ltr">
<FrameLayout
- android:id="@+id/user_name_container"
+ android:id="@+id/system_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
- android:layout_toStartOf="@+id/clock_container"
+ android:layout_marginTop="@dimen/car_padding_2"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
>
-
<com.android.systemui.car.navigationbar.CarNavigationButton
- android:id="@+id/user_name"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- systemui:icon="@null"
- systemui:intent="intent:#Intent;component=com.android.car.settings/.users.UserSwitcherActivity;launchFlags=0x24000000;end"
- >
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
+ android:background="@drawable/system_bar_background_pill"
+ android:layout_weight="1"
+ android:gravity="center_vertical"
+ systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivities$QuickSettingActivity;launchFlags=0x24000000;end">
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_marginEnd="@dimen/car_padding_2"
android:gravity="center_vertical"
- >
- <ImageView
- android:id="@+id/user_avatar"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:src="@drawable/car_ic_user_icon"
- android:paddingLeft="@dimen/system_bar_user_icon_padding"
- android:paddingRight="@dimen/system_bar_user_icon_padding"
- />
- <TextView
- android:id="@+id/user_name_text"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:textAppearance="@style/TextAppearance.SystemBar.Username"
- android:maxLines="1"
- />
- </LinearLayout>
+ />
</com.android.systemui.car.navigationbar.CarNavigationButton>
</FrameLayout>
@@ -96,25 +85,51 @@
/>
</FrameLayout>
- <LinearLayout
- android:id="@+id/system_icon_area"
+ <FrameLayout
+ android:id="@+id/user_name_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
- android:paddingEnd="@*android:dimen/car_padding_1"
- android:gravity="center_vertical"
- android:orientation="horizontal"
+ android:layout_marginTop="@dimen/car_padding_2"
>
-
- <include
- layout="@layout/system_icons"
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/user_name"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/car_padding_2"
+ android:background="@drawable/system_bar_background_pill"
android:gravity="center_vertical"
- />
- </LinearLayout>
+ systemui:intent="intent:#Intent;component=com.android.car.settings/.users.UserSwitcherActivity;launchFlags=0x24000000;end"
+ >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:layout_marginEnd="@dimen/car_padding_2"
+ android:gravity="center_vertical"
+ >
+ <ImageView
+ android:id="@+id/user_avatar"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/car_ic_user_icon"
+ android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+ />
+ <TextView
+ android:id="@+id/user_name_text"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.SystemBar.Username"
+ android:maxLines="1"
+ android:maxLength="10"
+ android:layout_marginEnd="@dimen/system_bar_user_icon_padding"
+ />
+ </LinearLayout>
+ </com.android.systemui.car.navigationbar.CarNavigationButton>
+ </FrameLayout>
</RelativeLayout>
</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml
index d23579294ce8..f6ffcc8c16f0 100644
--- a/packages/CarSystemUI/res/layout/system_icons.xml
+++ b/packages/CarSystemUI/res/layout/system_icons.xml
@@ -20,15 +20,24 @@
android:id="@+id/system_icons"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
<com.android.systemui.statusbar.phone.StatusIconContainer
android:id="@+id/statusIcons"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
- android:paddingEnd="4dp"
+ android:scaleType="fitCenter"
android:gravity="center_vertical"
android:orientation="horizontal"
/>
+
+ <ImageView
+ android:id="@+id/settingsIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/car_padding_2"
+ android:src="@drawable/car_ic_settings_icon"
+ />
</LinearLayout> \ No newline at end of file
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index c390cc8d70a4..0181b9a39aea 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -32,8 +32,9 @@
<color name="system_bar_background_opaque">#ff172026</color>
<!-- colors for status bar -->
- <color name="system_bar_user_icon_color">#ffffff</color>
- <color name="system_bar_text_color">#ffffff</color>
+ <color name="system_bar_background_pill_color">#282A2D</color>
+ <color name="system_bar_icon_color">#FFFFFF</color>
+ <color name="system_bar_text_color">#FFFFFF</color>
<color name="status_bar_background_color">#33000000</color>
<drawable name="system_bar_background">@color/status_bar_background_color</drawable>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index 28b8eadf9750..86bfa751d085 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -46,6 +46,10 @@
in frameworks/base/core package and thus will have no effect if
set here. See car_product overlay for car specific defaults-->
+ <!-- Overrides the space between each status icon in the system bar -->
+ <dimen name="status_bar_system_icon_spacing">16dp</dimen>
+ <!-- Overrides the size of the network signal icon -->
+ <dimen name="signal_icon_size">32dp</dimen>
<dimen name="system_bar_user_icon_padding">16dp</dimen>
<dimen name="system_bar_user_icon_drawing_size">36dp</dimen>
<!-- Padding on either side of the group of all system bar buttons -->
@@ -207,6 +211,12 @@
<dimen name="car_navigation_bar_width">760dp</dimen>
<dimen name="car_left_navigation_bar_width">96dp</dimen>
<dimen name="car_right_navigation_bar_width">96dp</dimen>
+ <!-- In order to change the height of the bottom nav bar, overlay navigation_bar_height in
+ frameworks/base/core/res/res instead. -->
+ <dimen name="car_bottom_navigation_bar_height">@*android:dimen/navigation_bar_height</dimen>
+ <!-- In order to change the height of the top nav bar, overlay status_bar_height in
+ frameworks/base/core/res/res instead. -->
+ <dimen name="car_top_navigation_bar_height">@*android:dimen/status_bar_height</dimen>
<dimen name="car_user_switcher_container_height">420dp</dimen>
<!-- This must be the negative of car_user_switcher_container_height for the animation. -->
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
index fbdb5167fade..06ae7cfd6d1b 100644
--- a/packages/CarSystemUI/res/values/strings.xml
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -22,6 +22,8 @@
<string name="hvac_min_text">Min</string>
<!-- String to represent largest setting of an HVAC system [CHAR LIMIT=10]-->
<string name="hvac_max_text">Max</string>
+ <!-- String to display when no HVAC temperature is available -->
+ <string name="hvac_null_temp_text" translatable="false">--</string>
<!-- Text for voice recognition toast. [CHAR LIMIT=60] -->
<string name="voice_recognition_toast">Voice recognition now handled by connected Bluetooth device</string>
<!-- Name of Guest Profile. [CHAR LIMIT=35] -->
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
index f242db0aec09..f5de2fde7b1a 100644
--- a/packages/CarSystemUI/res/values/styles.xml
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -49,5 +49,6 @@
<item name="android:padding">@dimen/system_bar_button_padding</item>
<item name="android:gravity">center</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="unselectedAlpha">0.56</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/CarSystemUI/samples/sample2/rro/Android.bp
index 0e7e10152f81..bf68e414ca1c 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/CarSystemUI/samples/sample2/rro/Android.bp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2013 The Android Open Source Project
+// 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.
@@ -14,30 +14,14 @@
// limitations under the License.
//
-cc_library_shared {
- name: "libjni_pacprocessor",
-
- srcs: [
- "jni_init.cpp",
- "com_android_pacprocessor_PacNative.cpp",
- ],
-
- shared_libs: [
- "libandroidfw",
- "libandroid_runtime",
- "liblog",
- "libutils",
- "libnativehelper",
- "libpac",
- ],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wunused",
- "-Wunreachable-code",
- ],
- sanitize: {
- cfi: true,
- },
-}
+android_app {
+ name: "CarSystemUISampleTwoRRO",
+ resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ aaptflags: [
+ "--no-resource-deduping",
+ "--no-resource-removal",
+ ]
+} \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml b/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml
new file mode 100644
index 000000000000..5c25056f7915
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.rro">
+ <overlay
+ android:targetPackage="com.android.systemui"
+ android:isStatic="false"
+ android:resourcesMap="@xml/car_sysui_overlays"
+ />
+</manifest> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_apps.xml
new file mode 100644
index 000000000000..a8d8a2f241f6
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp">
+<path
+ android:pathData="M7.33333333 14.6666667L14.6666667 14.6666667L14.6666667 7.33333333L7.33333333 7.33333333L7.33333333 14.6666667ZM18.3333333 36.6666667L25.6666667 36.6666667L25.6666667 29.3333333L18.3333333 29.3333333L18.3333333 36.6666667ZM7.33333333 36.6666667L14.6666667 36.6666667L14.6666667 29.3333333L7.33333333 29.3333333L7.33333333 36.6666667ZM7.33333333 25.6666667L14.6666667 25.6666667L14.6666667 18.3333333L7.33333333 18.3333333L7.33333333 25.6666667ZM18.3333333 25.6666667L25.6666667 25.6666667L25.6666667 18.3333333L18.3333333 18.3333333L18.3333333 25.6666667ZM29.3333333 7.33333333L29.3333333 14.6666667L36.6666667 14.6666667L36.6666667 7.33333333L29.3333333 7.33333333ZM18.3333333 14.6666667L25.6666667 14.6666667L25.6666667 7.33333333L18.3333333 7.33333333L18.3333333 14.6666667ZM29.3333333 25.6666667L36.6666667 25.6666667L36.6666667 18.3333333L29.3333333 18.3333333L29.3333333 25.6666667ZM29.3333333 36.6666667L36.6666667 36.6666667L36.6666667 29.3333333L29.3333333 29.3333333L29.3333333 36.6666667Z"
+ android:fillColor="@color/car_nav_icon_fill_color" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.xml
new file mode 100644
index 000000000000..6339ebb3ea8d
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_music.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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M22 5.5L22 24.8416667C20.9183333 24.2183333 19.6716667 23.8333333 18.3333333 23.8333333C14.2816667 23.8333333 11 27.115 11 31.1666667C11 35.2183333 14.2816667 38.5 18.3333333 38.5C22.385 38.5 25.6666667 35.2183333 25.6666667 31.1666667L25.6666667 12.8333333L33 12.8333333L33 5.5L22 5.5Z"
+ android:fillColor="@color/car_nav_icon_fill_color" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.xml
new file mode 100644
index 000000000000..e1fabe07cdeb
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_navigation.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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M39.8016667 20.6983333L23.3016667 4.19833333C22.5866667 3.48333333 21.4316667 3.48333333 20.7166667 4.19833333L4.21666667 20.6983333C3.50166667 21.4133333 3.50166667 22.5683333 4.21666667 23.2833333L20.7166667 39.7833333C21.4316667 40.4983333 22.5866667 40.4983333 23.3016667 39.7833333L39.8016667 23.2833333C40.5166667 22.5866667 40.5166667 21.4316667 39.8016667 20.6983333ZM25.6666667 26.5833333L25.6666667 22L18.3333333 22L18.3333333 27.5L14.6666667 27.5L14.6666667 20.1666667C14.6666667 19.1583333 15.4916667 18.3333333 16.5 18.3333333L25.6666667 18.3333333L25.6666667 13.75L32.0833333 20.1666667L25.6666667 26.5833333Z"
+ android:fillColor="@color/car_nav_icon_fill_color" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.xml
new file mode 100644
index 000000000000..3c3fefc9782a
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_notification.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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="44dp"
+ android:height="44dp"
+ android:viewportWidth="44"
+ android:viewportHeight="44">
+ <path
+ android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
+ android:fillColor="@color/car_nav_icon_fill_color" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml
new file mode 100644
index 000000000000..f185eb9afb75
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_overview.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M36.92857 22.39286A14.53571 14.53571 0 0 1 7.857143 22.39286A14.53571 14.53571 0 0 1 36.92857 22.39286Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="4" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.xml
new file mode 100644
index 000000000000..50e36b5a6e3c
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/car_ic_phone.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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="44"
+ android:viewportHeight="44"
+ android:width="44dp"
+ android:height="44dp">
+ <path
+ android:pathData="M12.1366667 19.7816667C14.7766667 24.97 19.03 29.205 24.2183333 31.8633333L28.2516667 27.83C28.7466667 27.335 29.48 27.17 30.1216667 27.39C32.175 28.0683333 34.3933333 28.435 36.6666667 28.435C37.675 28.435 38.5 29.26 38.5 30.2683333L38.5 36.6666667C38.5 37.675 37.675 38.5 36.6666667 38.5C19.4516667 38.5 5.5 24.5483333 5.5 7.33333333C5.5 6.325 6.325 5.5 7.33333333 5.5L13.75 5.5C14.7583333 5.5 15.5833333 6.325 15.5833333 7.33333333C15.5833333 9.625 15.95 11.825 16.6283333 13.8783333C16.83 14.52 16.6833333 15.235 16.17 15.7483333L12.1366667 19.7816667Z"
+ android:fillColor="@color/car_nav_icon_fill_color" />
+</vector> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml b/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml
new file mode 100644
index 000000000000..6161ad9b041c
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/drawable/system_bar_background.xml
@@ -0,0 +1,33 @@
+<?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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="10dp"
+ android:bottomLeftRadius="0dp"
+ android:bottomRightRadius="0dp"
+ />
+ <solid
+ android:color="#404040"
+ />
+ <padding
+ android:left="0dp"
+ android:top="0dp"
+ android:right="0dp"
+ android:bottom="0dp"
+ />
+</shape> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml
new file mode 100644
index 000000000000..bd6065c3de9a
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/layout/car_left_navigation_bar.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.systemui.car.navigationbar.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:background="@drawable/system_bar_background">
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/home"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/maps_nav"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MAPS"
+ systemui:icon="@drawable/car_ic_navigation"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/music_nav"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MUSIC"
+ systemui:icon="@drawable/car_ic_music"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
+ systemui:packages="com.android.car.media"
+ systemui:highlightWhenSelected="true"
+ />
+
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/grid_nav"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/phone_nav"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_phone"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+ systemui:packages="com.android.car.dialer"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.car.navigationbar.CarNavigationButton
+ android:id="@+id/notifications"
+ style="@style/NavigationBarButton"
+ systemui:highlightWhenSelected="true"
+ systemui:icon="@drawable/car_ic_notification"
+ systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:singleLine="true"
+ android:gravity="center_horizontal"
+ android:paddingBottom="20dp"
+ />
+
+ <Space
+ android:layout_height="10dp"
+ android:layout_width="match_parent"/>
+
+ </LinearLayout>
+
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.xml
new file mode 100644
index 000000000000..7ba333468422
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/values/attrs.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.
+ -->
+
+<resources>
+ <attr name="broadcast" format="boolean"/>
+ <attr name="icon" format="reference"/>
+ <attr name="selectedIcon" format="reference"/>
+ <attr name="intent" format="string"/>
+ <attr name="longIntent" format="string"/>
+ <attr name="componentNames" format="string" />
+ <attr name="highlightWhenSelected" format="boolean" />
+ <attr name="categories" format="string"/>
+ <attr name="packages" format="string" />
+</resources>
diff --git a/packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml
index 9b48a70d9439..c32d638681a2 100644
--- a/packages/SystemUI/res/drawable/tv_rect_dark_left_rounded.xml
+++ b/packages/CarSystemUI/samples/sample2/rro/res/values/colors.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
+ ~ 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.
@@ -14,13 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
-
- <corners
- android:bottomLeftRadius="8dp"
- android:topLeftRadius="8dp" />
- <solid android:color="@color/tv_audio_recording_indicator_background" />
-
-</shape> \ No newline at end of file
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="car_nav_icon_fill_color">#8F8F8F</color>
+ <color name="car_nav_icon_fill_color_selected">#FFFFFF</color>
+</resources>
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml
new file mode 100644
index 000000000000..89c7bd4df2bc
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/values/config.xml
@@ -0,0 +1,57 @@
+<?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.
+ -->
+
+<resources>
+ <!-- Configure which system bars should be displayed. -->
+ <bool name="config_enableTopNavigationBar">false</bool>
+ <bool name="config_enableLeftNavigationBar">true</bool>
+ <bool name="config_enableRightNavigationBar">false</bool>
+ <bool name="config_enableBottomNavigationBar">false</bool>
+
+ <!-- Configure the type of each system bar. Each system bar must have a unique type. -->
+ <!-- STATUS_BAR = 0-->
+ <!-- NAVIGATION_BAR = 1-->
+ <!-- STATUS_BAR_EXTRA = 2-->
+ <!-- NAVIGATION_BAR_EXTRA = 3-->
+ <integer name="config_topSystemBarType">0</integer>
+ <integer name="config_leftSystemBarType">1</integer>
+ <integer name="config_rightSystemBarType">2</integer>
+ <integer name="config_bottomSystemBarType">3</integer>
+
+ <!-- Configure the relative z-order among the system bars. When two system bars overlap (e.g.
+ if both top bar and left bar are enabled, it creates an overlapping space in the upper left
+ corner), the system bar with the higher z-order takes the overlapping space and padding is
+ applied to the other bar.-->
+ <!-- NOTE: If two overlapping system bars have the same z-order, SystemBarConfigs will throw a
+ RuntimeException, since their placing order cannot be determined. Bars that do not overlap
+ are allowed to have the same z-order. -->
+ <!-- NOTE: If the z-order of a bar is 10 or above, it will also appear on top of HUN's. -->
+ <integer name="config_topSystemBarZOrder">0</integer>
+ <integer name="config_leftSystemBarZOrder">10</integer>
+ <integer name="config_rightSystemBarZOrder">10</integer>
+ <integer name="config_bottomSystemBarZOrder">10</integer>
+
+ <!-- Whether heads-up notifications should be shown on the bottom. If false, heads-up
+ notifications will be shown pushed to the top of their parent container. If true, they will
+ be shown pushed to the bottom of their parent container. If true, then should override
+ config_headsUpNotificationAnimationHelper to use a different AnimationHelper, such as
+ com.android.car.notification.headsup.animationhelper.
+ CarHeadsUpNotificationBottomAnimationHelper. -->
+ <bool name="config_showHeadsUpNotificationOnBottom">true</bool>
+
+ <string name="config_notificationPanelViewMediator" translatable="false">com.android.systemui.car.notification.NotificationPanelViewMediator</string>
+</resources> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml b/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml
new file mode 100644
index 000000000000..136dc3b6df18
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/values/styles.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="TextAppearance.StatusBar.Clock"
+ parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">40sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ <item name="android:textColor">#FFFFFF</item>
+ </style>
+
+ <style name="NavigationBarButton">
+ <item name="android:layout_height">96dp</item>
+ <item name="android:layout_width">96dp</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="TextAppearance.CarStatus" parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:textSize">30sp</item>
+ <item name="android:textColor">#FFFFFF</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml b/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml
new file mode 100644
index 000000000000..58a535b4e000
--- /dev/null
+++ b/packages/CarSystemUI/samples/sample2/rro/res/xml/car_sysui_overlays.xml
@@ -0,0 +1,66 @@
+
+<!--
+ ~ 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.
+ -->
+
+<overlay>
+ <item target="layout/car_left_navigation_bar" value="@layout/car_left_navigation_bar"/>
+
+ <item target="attr/icon" value="@attr/icon"/>
+ <item target="attr/selectedIcon" value="@attr/selectedIcon"/>
+ <item target="attr/intent" value="@attr/intent"/>
+ <item target="attr/longIntent" value="@attr/longIntent"/>
+ <item target="attr/componentNames" value="@attr/componentNames"/>
+ <item target="attr/highlightWhenSelected" value="@attr/highlightWhenSelected"/>
+ <item target="attr/categories" value="@attr/categories"/>
+ <item target="attr/packages" value="@attr/packages"/>
+ <!-- start the intent as a broad cast instead of an activity if true-->
+ <item target="attr/broadcast" value="@attr/broadcast"/>
+
+ <item target="drawable/car_ic_overview" value="@drawable/car_ic_overview" />
+ <item target="drawable/car_ic_apps" value="@drawable/car_ic_apps" />
+ <item target="drawable/car_ic_music" value="@drawable/car_ic_music" />
+ <item target="drawable/car_ic_phone" value="@drawable/car_ic_phone" />
+ <item target="drawable/car_ic_navigation" value="@drawable/car_ic_navigation" />
+
+ <item target="style/NavigationBarButton" value="@style/NavigationBarButton"/>
+
+ <item target="color/car_nav_icon_fill_color" value="@color/car_nav_icon_fill_color" />
+
+ <item target="bool/config_enableTopNavigationBar" value="@bool/config_enableTopNavigationBar"/>
+ <item target="bool/config_enableLeftNavigationBar" value="@bool/config_enableLeftNavigationBar"/>
+ <item target="bool/config_enableRightNavigationBar" value="@bool/config_enableRightNavigationBar"/>
+ <item target="bool/config_enableBottomNavigationBar" value="@bool/config_enableBottomNavigationBar"/>
+ <item target="bool/config_showHeadsUpNotificationOnBottom" value="@bool/config_showHeadsUpNotificationOnBottom"/>
+
+ <item target="integer/config_topSystemBarType" value="@integer/config_topSystemBarType"/>
+ <item target="integer/config_leftSystemBarType" value="@integer/config_leftSystemBarType"/>
+ <item target="integer/config_rightSystemBarType" value="@integer/config_rightSystemBarType"/>
+ <item target="integer/config_bottomSystemBarType" value="@integer/config_bottomSystemBarType"/>
+
+ <item target="integer/config_topSystemBarZOrder" value="@integer/config_topSystemBarZOrder"/>
+ <item target="integer/config_leftSystemBarZOrder" value="@integer/config_leftSystemBarZOrder"/>
+ <item target="integer/config_rightSystemBarZOrder" value="@integer/config_rightSystemBarZOrder"/>
+ <item target="integer/config_bottomSystemBarZOrder" value="@integer/config_bottomSystemBarZOrder"/>
+
+ <item target="string/config_notificationPanelViewMediator" value="@string/config_notificationPanelViewMediator"/>
+
+ <item target="id/home" value="@id/home"/>
+ <item target="id/maps_nav" value="@id/maps_nav"/>
+ <item target="id/music_nav" value="@id/music_nav"/>
+ <item target="id/grid_nav" value="@id/grid_nav"/>
+ <item target="id/phone_nav" value="@id/phone_nav"/>
+ <item target="id/notifications" value="@id/notifications"/>
+</overlay> \ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java
index 85d4ceb81eeb..af2a1d36bbd7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AdjustableTemperatureView.java
@@ -40,6 +40,9 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu
private float mMinTempC;
private float mMaxTempC;
private String mTempFormat;
+ private String mNullTempText;
+ private String mMinTempText;
+ private String mMaxTempText;
private boolean mDisplayInFahrenheit = false;
private HvacController mHvacController;
@@ -59,6 +62,9 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu
mTempFormat = getResources().getString(R.string.hvac_temperature_format);
mMinTempC = getResources().getFloat(R.dimen.hvac_min_value_celsius);
mMaxTempC = getResources().getFloat(R.dimen.hvac_max_value_celsius);
+ mNullTempText = getResources().getString(R.string.hvac_null_temp_text);
+ mMinTempText = getResources().getString(R.string.hvac_min_text);
+ mMaxTempText = getResources().getString(R.string.hvac_max_text);
initializeButtons();
}
@@ -69,12 +75,23 @@ public class AdjustableTemperatureView extends LinearLayout implements Temperatu
@Override
public void setTemp(float tempC) {
- if (tempC > mMaxTempC || tempC < mMinTempC) {
- return;
- }
if (mTempTextView == null) {
mTempTextView = findViewById(R.id.hvac_temperature_text);
}
+ if (Float.isNaN(tempC)) {
+ mTempTextView.setText(mNullTempText);
+ return;
+ }
+ if (tempC <= mMinTempC) {
+ mTempTextView.setText(mMinTempText);
+ mCurrentTempC = mMinTempC;
+ return;
+ }
+ if (tempC >= mMaxTempC) {
+ mTempTextView.setText(mMaxTempText);
+ mCurrentTempC = mMaxTempC;
+ return;
+ }
mTempTextView.setText(String.format(mTempFormat,
mDisplayInFahrenheit ? convertToFahrenheit(tempC) : tempC));
mCurrentTempC = tempC;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
index e7e33a5439f9..d2b931b3624b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
@@ -96,11 +96,11 @@ public class CarNavigationButton extends LinearLayout {
public void setSelected(boolean selected) {
super.setSelected(selected);
mSelected = selected;
+
if (mHighlightWhenSelected) {
- // Always apply selected alpha if the button does not toggle alpha based on selection
- // state.
- setAlpha(!mHighlightWhenSelected || mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
}
+
if (mShowMoreWhenSelected && mMoreIcon != null) {
mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
}
@@ -299,10 +299,10 @@ public class CarNavigationButton extends LinearLayout {
mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
R.styleable.CarNavigationButton_useDefaultAppIconForRole, false);
mIcon = findViewById(R.id.car_nav_button_icon_image);
- // Always apply selected alpha if the button does not toggle alpha based on selection state.
- mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
+ // Always apply un-selected alpha regardless of if the button toggles alpha based on
+ // selection state.
+ setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
- mMoreIcon.setAlpha(mSelectedAlpha);
mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
updateImage();
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
index 078196e18b88..92cf60006917 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java
@@ -97,10 +97,12 @@ public class SystemBarConfigs {
populateMaps();
readConfigs();
+
checkEnabledBarsHaveUniqueBarTypes();
checkAllOverlappingBarsHaveDifferentZOrders();
checkSystemBarEnabledForNotificationPanel();
checkHideBottomBarForKeyboardConfigSync();
+
setInsetPaddingsForOverlappingCorners();
sortSystemBarSidesByZOrder();
}
@@ -199,10 +201,10 @@ public class SystemBarConfigs {
BAR_TITLE_MAP.put(LEFT, "LeftCarSystemBar");
BAR_TITLE_MAP.put(RIGHT, "RightCarSystemBar");
- BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_GESTURES);
- BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_GESTURES);
- BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_GESTURES);
- BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_GESTURES);
+ BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_MANDATORY_GESTURES);
+ BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES);
+ BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_MANDATORY_GESTURES);
+ BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_MANDATORY_GESTURES);
}
private void readConfigs() {
@@ -216,7 +218,7 @@ public class SystemBarConfigs {
new SystemBarConfigBuilder()
.setSide(TOP)
.setGirth(mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height))
+ R.dimen.car_top_navigation_bar_height))
.setBarType(mResources.getInteger(R.integer.config_topSystemBarType))
.setZOrder(mResources.getInteger(R.integer.config_topSystemBarZOrder))
.setHideForKeyboard(mResources.getBoolean(
@@ -230,7 +232,7 @@ public class SystemBarConfigs {
new SystemBarConfigBuilder()
.setSide(BOTTOM)
.setGirth(mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_height))
+ R.dimen.car_bottom_navigation_bar_height))
.setBarType(mResources.getInteger(R.integer.config_bottomSystemBarType))
.setZOrder(
mResources.getInteger(R.integer.config_bottomSystemBarZOrder))
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index c7155f4b21d2..4b660692cd8f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -16,8 +16,6 @@
package com.android.systemui.car.notification;
-import static android.view.WindowInsets.Type.navigationBars;
-
import android.app.ActivityManager;
import android.car.Car;
import android.car.drivingstate.CarUxRestrictionsManager;
@@ -25,6 +23,8 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.GestureDetector;
@@ -32,6 +32,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@@ -96,6 +97,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
private boolean mNotificationListAtEndAtTimeOfTouch;
private boolean mIsSwipingVerticallyToClose;
private boolean mIsNotificationCardSwiping;
+ private boolean mImeVisible = false;
private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
@@ -139,6 +141,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
mNotificationVisibilityLogger = notificationVisibilityLogger;
mCommandQueue.addCallback(this);
+
// Notification background setup.
mInitialBackgroundAlpha = (float) mResources.getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -179,6 +182,15 @@ public class NotificationPanelViewController extends OverlayPanelViewController
}
}
+ @Override
+ public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
+ boolean showImeSwitcher) {
+ if (mContext.getDisplayId() != displayId) {
+ return;
+ }
+ mImeVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
+ }
+
// OverlayViewController
@Override
@@ -204,7 +216,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController
@Override
protected int getInsetTypesToFit() {
- return navigationBars();
+ return WindowInsets.Type.navigationBars();
}
@Override
@@ -212,6 +224,12 @@ public class NotificationPanelViewController extends OverlayPanelViewController
return mEnableHeadsUpNotificationWhenNotificationShadeOpen;
}
+ @Override
+ protected boolean shouldUseStableInsets() {
+ // When IME is visible, then the inset from the nav bar should not be applied.
+ return !mImeVisible;
+ }
+
/** Reinflates the view. */
public void reinflate() {
ViewGroup container = (ViewGroup) getLayout();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 023b5b4f5f30..6d63e31d79b5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -567,6 +567,9 @@ public class UserGridRecyclerView extends RecyclerView {
Log.e(TAG, "Failed to switch to new user: " + user.id);
}
}
+ if (mAddUserView != null) {
+ mAddUserView.setEnabled(true);
+ }
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
index 8adc1adcc41c..6c3a6327d90c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java
@@ -168,6 +168,19 @@ public class OverlayViewController {
}
/**
+ * Returns {@code true} if the window should use stable insets. Using stable insets means that
+ * even when system bars are temporarily not visible, inset from the system bars will still be
+ * applied.
+ *
+ * NOTE: When system bars are hidden in transient mode, insets from them will not be applied
+ * even when the system bars become visible. Setting the return value to {@true} here can
+ * prevent the OverlayView from overlapping with the system bars when that happens.
+ */
+ protected boolean shouldUseStableInsets() {
+ return false;
+ }
+
+ /**
* Returns the insets types to fit to the sysui overlay window when this
* {@link OverlayViewController} is in the foreground.
*/
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
index c13e486f1c0e..0da23605a2cf 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java
@@ -108,6 +108,7 @@ public class OverlayViewGlobalStateController {
if (mZOrderVisibleSortedMap.isEmpty()) {
setWindowVisible(true);
}
+
if (!(viewController instanceof OverlayPanelViewController)) {
inflateView(viewController);
}
@@ -117,6 +118,7 @@ public class OverlayViewGlobalStateController {
}
updateInternalsWhenShowingView(viewController);
+ refreshUseStableInsets();
refreshInsetTypesToFit();
refreshWindowFocus();
refreshNavigationBarVisibility();
@@ -190,6 +192,7 @@ public class OverlayViewGlobalStateController {
mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController));
refreshHighestZOrderWhenHidingView(viewController);
+ refreshUseStableInsets();
refreshInsetTypesToFit();
refreshWindowFocus();
refreshNavigationBarVisibility();
@@ -247,6 +250,11 @@ public class OverlayViewGlobalStateController {
setWindowFocusable(mHighestZOrder == null ? false : mHighestZOrder.shouldFocusWindow());
}
+ private void refreshUseStableInsets() {
+ mSystemUIOverlayWindowController.setUsingStableInsets(
+ mHighestZOrder == null ? false : mHighestZOrder.shouldUseStableInsets());
+ }
+
private void refreshInsetTypesToFit() {
if (mZOrderVisibleSortedMap.isEmpty()) {
setFitInsetsTypes(statusBars());
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
index 887329359b05..b22de84eb038 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java
@@ -53,6 +53,7 @@ public class SystemUIOverlayWindowController implements
private boolean mIsAttached = false;
private boolean mVisible = false;
private boolean mFocusable = false;
+ private boolean mUsingStableInsets = false;
@Inject
public SystemUIOverlayWindowController(
@@ -115,6 +116,7 @@ public class SystemUIOverlayWindowController implements
/** Sets the types of insets to fit. Note: This should be rarely used. */
public void setFitInsetsTypes(@WindowInsets.Type.InsetsType int types) {
mLpChanged.setFitInsetsTypes(types);
+ mLpChanged.setFitInsetsIgnoringVisibility(mUsingStableInsets);
updateWindow();
}
@@ -159,6 +161,10 @@ public class SystemUIOverlayWindowController implements
return mFocusable;
}
+ protected void setUsingStableInsets(boolean useStableInsets) {
+ mUsingStableInsets = useStableInsets;
+ }
+
private void updateWindow() {
if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
if (isAttached()) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
index b113d29f00e6..f2ca4956be07 100644
--- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -50,6 +50,7 @@ public class DisplaySystemBarsController extends DisplayImeController {
private final Context mContext;
private final DisplayController mDisplayController;
+ private final Handler mHandler;
private SparseArray<PerDisplay> mPerDisplaySparseArray;
public DisplaySystemBarsController(
@@ -58,9 +59,10 @@ public class DisplaySystemBarsController extends DisplayImeController {
DisplayController displayController,
@Main Handler mainHandler,
TransactionPool transactionPool) {
- super(wmService, displayController, mainHandler, transactionPool);
+ super(wmService, displayController, (r) -> mainHandler.post(r), transactionPool);
mContext = context;
mDisplayController = displayController;
+ mHandler = mainHandler;
}
@Override
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java
index a3a55aae5f18..fe071d54fb10 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/AdjustableTemperatureViewTest.java
@@ -98,6 +98,48 @@ public class AdjustableTemperatureViewTest extends SysuiTestCase {
}
@Test
+ public void setTemp_tempNaN_setsTextToNaNText() {
+ when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET),
+ anyInt())).thenReturn(true);
+ when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn(
+ Float.NaN);
+
+ mHvacController.addTemperatureViewToController(mAdjustableTemperatureView);
+
+ TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text);
+ assertEquals(tempText.getText(),
+ getContext().getResources().getString(R.string.hvac_null_temp_text));
+ }
+
+ @Test
+ public void setTemp_tempBelowMin_setsTextToMinTempText() {
+ when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET),
+ anyInt())).thenReturn(true);
+ when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn(
+ getContext().getResources().getFloat(R.dimen.hvac_min_value_celsius));
+
+ mHvacController.addTemperatureViewToController(mAdjustableTemperatureView);
+
+ TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text);
+ assertEquals(tempText.getText(),
+ getContext().getResources().getString(R.string.hvac_min_text));
+ }
+
+ @Test
+ public void setTemp_tempAboveMax_setsTextToMaxTempText() {
+ when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET),
+ anyInt())).thenReturn(true);
+ when(mCarPropertyManager.getFloatProperty(eq(HVAC_TEMPERATURE_SET), anyInt())).thenReturn(
+ getContext().getResources().getFloat(R.dimen.hvac_max_value_celsius));
+
+ mHvacController.addTemperatureViewToController(mAdjustableTemperatureView);
+
+ TextView tempText = mAdjustableTemperatureView.findViewById(R.id.hvac_temperature_text);
+ assertEquals(tempText.getText(),
+ getContext().getResources().getString(R.string.hvac_max_text));
+ }
+
+ @Test
public void setTemperatureToFahrenheit_callsViewSetDisplayInFahrenheit() {
when(mCarPropertyManager.isPropertyAvailable(eq(HVAC_TEMPERATURE_SET),
anyInt())).thenReturn(true);
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9ff868467531..f7f3cbb7d332 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -321,6 +321,9 @@ public class DynamicSystemInstallationService extends Service
if (!isDynamicSystemInstalled() && (getStatus() != STATUS_READY)) {
Log.e(TAG, "Trying to discard AOT while there is no complete installation");
+ // Stop foreground state and dismiss stale notification.
+ stopForeground(STOP_FOREGROUND_REMOVE);
+ resetTaskAndStop();
return;
}
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index e05bd3c22ae8..d3aa977f85b1 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -21,8 +21,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER;
import static androidx.test.ext.truth.location.LocationSubject.assertThat;
-import static com.google.common.truth.Truth.assertThat;
-
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
diff --git a/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
new file mode 100644
index 000000000000..5b96da027be7
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_turkish_f.kcm
@@ -0,0 +1,366 @@
+# 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.
+
+#
+# Turkish F keyboard layout.
+#
+
+type OVERLAY
+
+map key 12 SLASH
+map key 13 MINUS
+map key 43 COMMA
+map key 51 EQUALS
+map key 52 BACKSLASH
+map key 53 PERIOD
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '+'
+ base: '+'
+ shift: '*'
+ ralt: '\u00ac'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+ ralt: '\u00b9'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '"'
+ ralt: '\u00b2'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '^'
+ ralt: '#'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+ ralt: '\u00bc'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+ ralt: '\u00bd'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '&'
+ ralt: '\u00be'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '\''
+ ralt: '{'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '('
+ ralt: '['
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: ')'
+ ralt: ']'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: '='
+ ralt: '}'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+ ralt: '\\'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+ ralt: '|'
+}
+
+### ROW 2
+
+key Q {
+ label: 'F'
+ base: 'f'
+ shift, capslock: 'F'
+ ralt: '@'
+}
+
+key W {
+ label: 'G'
+ base: 'g'
+ shift, capslock: 'G'
+}
+
+key E {
+ label: '\u011f'
+ base: '\u011f'
+ shift, capslock: '\u011e'
+}
+
+key R {
+ label: '\u0131'
+ base: '\u0131'
+ shift, capslock: 'I'
+ ralt: '\u00b6'
+ ralt+shift, ralt+capslock: '\u00ae'
+}
+
+key T {
+ label: 'O'
+ base: 'o'
+ shift, capslock: 'O'
+}
+
+key Y {
+ label: 'D'
+ base: 'd'
+ shift, capslock: 'D'
+ ralt: '\u00a5'
+}
+
+key U {
+ label: 'R'
+ base: 'r'
+ shift, capslock: 'R'
+}
+
+key I {
+ label: 'N'
+ base: 'n'
+ shift, capslock: 'N'
+}
+
+key O {
+ label: 'H'
+ base: 'h'
+ shift, capslock: 'H'
+ ralt: '\u00f8'
+ ralt+shift, ralt+capslock: '\u00d8'
+}
+
+key P {
+ label: 'P'
+ base: 'p'
+ shift, capslock: 'P'
+ ralt: '\u00a3'
+}
+
+key LEFT_BRACKET {
+ label: 'Q'
+ base: 'q'
+ shift, capslock: 'Q'
+ ralt: '"'
+}
+
+key RIGHT_BRACKET {
+ label: 'W'
+ base: 'w'
+ shift, capslock: 'W'
+ ralt: '~'
+}
+
+### ROW 3
+
+key A {
+ label: '\u0075'
+ base: '\u0075'
+ shift, capslock: '\u0055'
+ ralt: '\u00e6'
+ ralt+shift, ralt+capslock: '\u00c6'
+}
+
+key S {
+ label: 'i'
+ base: 'i'
+ shift, capslock: '\u0130'
+ ralt: '\u00df'
+ ralt+shift, ralt+capslock: '\u00a7'
+}
+
+key D {
+ label: 'E'
+ base: 'e'
+ shift, capslock: 'E'
+ ralt: '\u20ac'
+}
+
+key F {
+ label: 'A'
+ base: 'a'
+ shift, capslock: 'A'
+ ralt: '\u00aa'
+}
+
+key G {
+ label: '\u00fc'
+ base: '\u00fc'
+ shift, capslock: '\u00dc'
+}
+
+key H {
+ label: 'T'
+ base: 't'
+ shift, capslock: 'T'
+ ralt: '\u20ba'
+}
+
+key J {
+ label: 'K'
+ base: 'k'
+ shift, capslock: 'K'
+}
+
+key K {
+ label: 'M'
+ base: 'm'
+ shift, capslock: 'M'
+}
+
+key L {
+ label: 'L'
+ base: 'l'
+ shift, capslock: 'L'
+}
+
+key SEMICOLON {
+ label: 'Y'
+ base: 'y'
+ shift, capslock: 'Y'
+ ralt: '\u00b4'
+}
+
+key APOSTROPHE {
+ label: '\u015f'
+ base: '\u015f'
+ shift, capslock: '\u015e'
+}
+
+key COMMA {
+ label: 'X'
+ base: 'x'
+ shift: 'X'
+ ralt: '\u0060'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '<'
+ base: '<'
+ shift: '>'
+ ralt: '|'
+ ralt+shift, ralt+capslock: '\u00a6'
+}
+
+key Z {
+ label: 'J'
+ base: 'j'
+ shift, capslock: 'J'
+ ralt: '\u00ab'
+ ralt+shift, ralt+capslock: '<'
+}
+
+key X {
+ label: '\u00f6'
+ base: '\u00f6'
+ shift, capslock: '\u00d6'
+ ralt: '\u00bb'
+ ralt+shift, ralt+capslock: '>'
+}
+
+key C {
+ label: 'V'
+ base: 'v'
+ shift, capslock: 'V'
+ ralt: '\u00a2'
+ ralt+shift, ralt+capslock: '\u00a9'
+}
+
+key V {
+ label: 'C'
+ base: 'c'
+ shift, capslock: 'C'
+}
+
+key B {
+ label: '\u00e7'
+ base: '\u00e7'
+ shift, capslock: '\u00c7'
+}
+
+key N {
+ label: 'Z'
+ base: 'z'
+ shift, capslock: 'Z'
+}
+
+key M {
+ label: 'S'
+ base: 's'
+ shift, capslock: 'S'
+ ralt: '\u00b5'
+ ralt+shift, ralt+capslock: '\u00ba'
+}
+
+key EQUALS {
+ label: 'B'
+ base: 'b'
+ shift, capslock: 'B'
+ ralt: '\u00d7'
+}
+
+key BACKSLASH {
+ label: '.'
+ base: '.'
+ shift, capslock: ':'
+ ralt: '\u00f7'
+}
+
+key PERIOD {
+ label: ','
+ base: ','
+ shift: ';'
+}
diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml
index 4bc7bf6e5d1f..462a6a9bb7ac 100644
--- a/packages/InputDevices/res/values-af/strings.xml
+++ b/packages/InputDevices/res/values-af/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Switserse Duits"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgies"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaars"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgaars, foneties"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiaans"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Deens"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noors"</string>
diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml
index 7a4ffeff433a..1559fa890bcd 100644
--- a/packages/InputDevices/res/values-am/strings.xml
+++ b/packages/InputDevices/res/values-am/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"የስዊዝ ጀርመን"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ቤልጂየም"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ቡልጋሪያ"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ቡልጋሪያኛ፣ ፎነቲክ"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ጣሊያንኛ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ዴኒሽ"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ኖርዌጂያ"</string>
diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml
index 3b00576afd89..bf508b2ad7d1 100644
--- a/packages/InputDevices/res/values-ar/strings.xml
+++ b/packages/InputDevices/res/values-ar/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"الألمانية السويسرية"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"البلجيكية"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"البلغارية"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"بلغارية صوتية"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"الإيطالية"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"الدانماركية"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"النرويجية"</string>
diff --git a/packages/InputDevices/res/values-as/strings.xml b/packages/InputDevices/res/values-as/strings.xml
index a63821e76633..49fbef92c114 100644
--- a/packages/InputDevices/res/values-as/strings.xml
+++ b/packages/InputDevices/res/values-as/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ছুইছ জাৰ্মান"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"বেলজিয়ান"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"বুলগেৰিয়ান"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"বুলগেৰিয়ান ফ’নেটিক"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ইটালিয়ান"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ডেনিশ্ব"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ন\'ৰৱেয়ান"</string>
diff --git a/packages/InputDevices/res/values-az/strings.xml b/packages/InputDevices/res/values-az/strings.xml
index 16badc456e73..c5a1e1ee2e2e 100644
--- a/packages/InputDevices/res/values-az/strings.xml
+++ b/packages/InputDevices/res/values-az/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"İsveçrə Almanı"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belçikalı"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bolqar"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bolqar dili, Fonetika"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"İtalyan"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danimarkalı"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norveçli"</string>
diff --git a/packages/InputDevices/res/values-b+sr+Latn/strings.xml b/packages/InputDevices/res/values-b+sr+Latn/strings.xml
index 95ef45916031..16f1cb2fc591 100644
--- a/packages/InputDevices/res/values-b+sr+Latn/strings.xml
+++ b/packages/InputDevices/res/values-b+sr+Latn/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švajcarsko nemačka"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarska"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bugarska fonetska"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italijanska"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
diff --git a/packages/InputDevices/res/values-be/strings.xml b/packages/InputDevices/res/values-be/strings.xml
index 63b899af1c98..6b0523f2bdf8 100644
--- a/packages/InputDevices/res/values-be/strings.xml
+++ b/packages/InputDevices/res/values-be/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Нямецкая (Швейцарыя)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельгійская"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Балгарская"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Балгарская фанетычная"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Італьянская"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дацкая"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Нарвежская"</string>
diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml
index 90b7f6c5e9d3..a7088c930395 100644
--- a/packages/InputDevices/res/values-bg/strings.xml
+++ b/packages/InputDevices/res/values-bg/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"швейцарски немски"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"белгийски"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"български"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Българска фонетична клавиатура"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"италиански"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"датски"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвежки"</string>
diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml
index 19292780875e..f387414f50c8 100644
--- a/packages/InputDevices/res/values-bn/strings.xml
+++ b/packages/InputDevices/res/values-bn/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"সুইস জার্মান"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"বেলজিয়ান"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"বুলগেরীয়"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"বুলগেরিয়ান, ফনেটিক"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ইতালীয়"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ডেনিশ"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"নরওয়েজীয়"</string>
diff --git a/packages/InputDevices/res/values-bs/strings.xml b/packages/InputDevices/res/values-bs/strings.xml
index f6a229c34eb4..b92ac8cad498 100644
--- a/packages/InputDevices/res/values-bs/strings.xml
+++ b/packages/InputDevices/res/values-bs/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarski njemački"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijski"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarski"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bugarski, fonetski"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italijanski"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danski"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveški"</string>
diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml
index ec3b247ac6d3..a5b5e10129d4 100644
--- a/packages/InputDevices/res/values-ca/strings.xml
+++ b/packages/InputDevices/res/values-ca/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemany suís"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgar"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgar, fonètic"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italià"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danès"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruec"</string>
diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml
index 99aed5e6ea0e..6b4f7ebf7a4a 100644
--- a/packages/InputDevices/res/values-cs/strings.xml
+++ b/packages/InputDevices/res/values-cs/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švýcarské (němčina)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgické"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulharské"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulharská fonetická klávesnice"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italské"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"dánské"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norské"</string>
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
index dbe685cb2329..cf2aecf1783a 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Schweizertysk"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisk"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarsk"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarsk, fonetisk"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiensk"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dansk"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norsk"</string>
diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml
index fd7fca014548..1e786855ac25 100644
--- a/packages/InputDevices/res/values-de/strings.xml
+++ b/packages/InputDevices/res/values-de/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Schweizerdeutsch"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisch"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarisch"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarisch – phonetisch"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienisch"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dänisch"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegisch"</string>
diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml
index 8bdd6f8bc967..eb2cc9b09a09 100644
--- a/packages/InputDevices/res/values-el/strings.xml
+++ b/packages/InputDevices/res/values-el/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Γερμανικά Ελβετίας"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Βελγικά"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Βουλγαρικά"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Βουλγαρικά (Φωνητικό)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Ιταλικά"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Δανικά"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Νορβηγικά"</string>
diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml
index 5319f968afc3..e8d6597f0e57 100644
--- a/packages/InputDevices/res/values-es-rUS/strings.xml
+++ b/packages/InputDevices/res/values-es-rUS/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemán de Suiza"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro fonético"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danés"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruego"</string>
diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml
index 95b3b1c80cdf..9396e460eba1 100644
--- a/packages/InputDevices/res/values-es/strings.xml
+++ b/packages/InputDevices/res/values-es/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemán suizo"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro (fonético)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danés"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruego"</string>
diff --git a/packages/InputDevices/res/values-et/strings.xml b/packages/InputDevices/res/values-et/strings.xml
index a5fb8ac08502..cf28e9f66dae 100644
--- a/packages/InputDevices/res/values-et/strings.xml
+++ b/packages/InputDevices/res/values-et/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Šveitsisaksa"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgia"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaaria"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgaaria, foneetiline"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Itaalia"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Taani"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norra"</string>
diff --git a/packages/InputDevices/res/values-eu/strings.xml b/packages/InputDevices/res/values-eu/strings.xml
index 1c75a7c0f67f..1e080fc83141 100644
--- a/packages/InputDevices/res/values-eu/strings.xml
+++ b/packages/InputDevices/res/values-eu/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemana (Suitza)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgikarra"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgariarra"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgariarra, fonetikoa"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiarra"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Daniarra"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegiarra"</string>
diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml
index ca7d43a1400f..5cb237e35053 100644
--- a/packages/InputDevices/res/values-fa/strings.xml
+++ b/packages/InputDevices/res/values-fa/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"آلمانی سوئیسی"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"بلژیکی"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"بلغاری"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"بلغاری، آوایی"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ایتالیایی"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"دانمارکی"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"نروژی"</string>
diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml
index 2878c78318f7..da7210657381 100644
--- a/packages/InputDevices/res/values-fi/strings.xml
+++ b/packages/InputDevices/res/values-fi/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"sveitsinsaksa"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgialainen"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulgaria"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bulgaria, foneettinen"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italia"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"tanska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norja"</string>
diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml
index 697fff6f1339..45aca35eb012 100644
--- a/packages/InputDevices/res/values-fr-rCA/strings.xml
+++ b/packages/InputDevices/res/values-fr-rCA/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Phonétique bulgare"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string>
diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml
index 2e14019340fc..b55a3c99f410 100644
--- a/packages/InputDevices/res/values-fr/strings.xml
+++ b/packages/InputDevices/res/values-fr/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Allemand (Suisse)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belge"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgare"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Phonétique bulgare"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italien"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danois"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvégien"</string>
diff --git a/packages/InputDevices/res/values-gl/strings.xml b/packages/InputDevices/res/values-gl/strings.xml
index e1ca7cfefa1c..40ede04c8e05 100644
--- a/packages/InputDevices/res/values-gl/strings.xml
+++ b/packages/InputDevices/res/values-gl/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemán suízo"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro, fonético"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarqués"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noruegués"</string>
diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml
index bc2ee8303290..631fc1497ce4 100644
--- a/packages/InputDevices/res/values-gu/strings.xml
+++ b/packages/InputDevices/res/values-gu/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"સ્વિસ જર્મન"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"બેલ્જિયન"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"બલ્ગેરિયન"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"બલ્ગેરિયન ફોનેટિક"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ઇટાલિયન"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ડેનિશ"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"નોર્વેજીયન"</string>
diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml
index 2ffebdd2c1ff..7d1e2f8106e7 100644
--- a/packages/InputDevices/res/values-hi/strings.xml
+++ b/packages/InputDevices/res/values-hi/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस जर्मन"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियाई"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बुल्‍गारियाई"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"बुल्गेरियन, फ़ोनेटिक"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"इटैलियन"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"डैनिश"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नार्वेजियाई"</string>
diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml
index 0430f8662014..aff2a37c2b7f 100644
--- a/packages/InputDevices/res/values-hr/strings.xml
+++ b/packages/InputDevices/res/values-hr/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarsko-njemačka"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bugarska"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bugarska (fonetska)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"talijanska"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml
index 76d10f544280..50d667b40b62 100644
--- a/packages/InputDevices/res/values-hu/strings.xml
+++ b/packages/InputDevices/res/values-hu/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"svájci német"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bolgár"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bolgár fonetikus"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"olasz"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"dán"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norvég"</string>
diff --git a/packages/InputDevices/res/values-hy/strings.xml b/packages/InputDevices/res/values-hy/strings.xml
index fa4e24550639..4a6fe2b3be4f 100644
--- a/packages/InputDevices/res/values-hy/strings.xml
+++ b/packages/InputDevices/res/values-hy/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Շվեյցարական գերմաներեն"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Բելգիական"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Բուլղարերեն"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"բուլղարերեն (հնչյունային)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Իտալերեն"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Դանիերեն"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Նորվեգերեն"</string>
diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml
index f5d173a338a0..90ba97d16f6a 100644
--- a/packages/InputDevices/res/values-in/strings.xml
+++ b/packages/InputDevices/res/values-in/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Jerman Swiss"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgia"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaria"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgaria, Fonetik"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italia"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Denmark"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegia"</string>
diff --git a/packages/InputDevices/res/values-is/strings.xml b/packages/InputDevices/res/values-is/strings.xml
index 09eedd300177..0889b21f3a64 100644
--- a/packages/InputDevices/res/values-is/strings.xml
+++ b/packages/InputDevices/res/values-is/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Svissneskt-þýskt"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgískt"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgarskt"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgarskt hljóðritunarlyklaborð"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Ítalskt"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danskt"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norskt"</string>
diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml
index e15c01ff6a62..77f78c6f49d0 100644
--- a/packages/InputDevices/res/values-it/strings.xml
+++ b/packages/InputDevices/res/values-it/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tedesco svizzero"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgara, fonetica"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danese"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegese"</string>
diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml
index 4abdf87ec4d4..52641b2ba1ff 100644
--- a/packages/InputDevices/res/values-iw/strings.xml
+++ b/packages/InputDevices/res/values-iw/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"גרמנית שוויצרית"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"בלגית"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"בולגרית"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"בולגרית פונטית"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"איטלקית"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"דנית"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"נורווגית"</string>
diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml
index 606ab3cd2239..2961548599a6 100644
--- a/packages/InputDevices/res/values-ja/strings.xml
+++ b/packages/InputDevices/res/values-ja/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ドイツ語(スイス)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ベルギー語"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ブルガリア語"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ブルガリア語(表音)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"イタリア語"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"デンマーク語"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ノルウェー語"</string>
diff --git a/packages/InputDevices/res/values-ka/strings.xml b/packages/InputDevices/res/values-ka/strings.xml
index b4b1a2df4d30..2ccfeb2f635f 100644
--- a/packages/InputDevices/res/values-ka/strings.xml
+++ b/packages/InputDevices/res/values-ka/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"შვეიცარიული გერმანული"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ბელგიური"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ბულგარული"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ბულგარული ფონეტიკური"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"იტალიური"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"დანიური"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ნორვეგიული"</string>
diff --git a/packages/InputDevices/res/values-kk/strings.xml b/packages/InputDevices/res/values-kk/strings.xml
index cfdc3f85de4a..dfe8c56802c7 100644
--- a/packages/InputDevices/res/values-kk/strings.xml
+++ b/packages/InputDevices/res/values-kk/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Швейцариялық неміс"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельгиялық"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгар"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгар (фонетикалық)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Италиян"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дат"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml
index 2aaf816c7deb..3bd7f202f287 100644
--- a/packages/InputDevices/res/values-km/strings.xml
+++ b/packages/InputDevices/res/values-km/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"អាល្លឺម៉ង់ ស្វីស"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"បែលហ្ស៊ិក"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ប៊ុលហ្ការី"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ប៊ុលហ្គារី សូរសព្ទ"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"អ៊ីតាលី"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ដាណឺម៉ាក"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ន័រវែស"</string>
diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml
index 8f62eb36a1f6..1e3c6932c3d4 100644
--- a/packages/InputDevices/res/values-kn/strings.xml
+++ b/packages/InputDevices/res/values-kn/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ಸ್ವಿಸ್ ಜರ್ಮನ್"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ಬೆಲ್ಜಿಯನ್"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ಬಲ್ಗೇರಿಯನ್‌"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ಬಲ್ಗೇರಿಯನ್ ಫೋನೆಟಿಕ್‌‌"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ಇಟಾಲಿಯನ್"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ಡ್ಯಾನಿಶ್"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ನಾರ್ವೇಜಿಯನ್"</string>
diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml
index b1f658202c46..147050462eab 100644
--- a/packages/InputDevices/res/values-ko/strings.xml
+++ b/packages/InputDevices/res/values-ko/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"독일어(스위스)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"벨기에어"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"불가리아어"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"불가리아어, 표음"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"이탈리아어"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"덴마크어"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"노르웨이어"</string>
diff --git a/packages/InputDevices/res/values-ky/strings.xml b/packages/InputDevices/res/values-ky/strings.xml
index bc521a2cd6cb..cb9dbb2f0abb 100644
--- a/packages/InputDevices/res/values-ky/strings.xml
+++ b/packages/InputDevices/res/values-ky/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Немис (Швейцария)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Белгия"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгар"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгарча, фонетикалык"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Италия"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дания"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвег"</string>
diff --git a/packages/InputDevices/res/values-lo/strings.xml b/packages/InputDevices/res/values-lo/strings.xml
index edb59f3964ed..4ae4b7d9c58b 100644
--- a/packages/InputDevices/res/values-lo/strings.xml
+++ b/packages/InputDevices/res/values-lo/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ສະວິສ ເຢຍລະມັນ"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ເບວຢ້ຽນ"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ຮັງກາຣຽນ"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ບັງກາຣຽນ, ການອອກສຽງ"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ອິຕາລຽນ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ເດັນນິຊ"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ນໍເວກຽນ"</string>
diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml
index f33eb42e6a4f..d2aef7f057db 100644
--- a/packages/InputDevices/res/values-lt/strings.xml
+++ b/packages/InputDevices/res/values-lt/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Šveicarijos vokiečių k."</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgų k."</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarų k."</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Fonetinė bulgarų"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italų k."</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danų k."</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegų k."</string>
diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml
index 4f47a3ba65db..8f3ff0ab2264 100644
--- a/packages/InputDevices/res/values-lv/strings.xml
+++ b/packages/InputDevices/res/values-lv/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Vācu (Šveice)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Beļģu"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgāru"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgāru, fonētiskā"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Itāļu"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dāņu"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvēģu"</string>
diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml
index c036409c9c62..9584e156ad9c 100644
--- a/packages/InputDevices/res/values-mk/strings.xml
+++ b/packages/InputDevices/res/values-mk/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Германски (Швајцарија)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Белгиски"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Бугарски"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Бугарска, фонетска"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Италијански"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дански"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвешки"</string>
diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml
index 65fbf22d5fe7..9e5344367eae 100644
--- a/packages/InputDevices/res/values-ml/strings.xml
+++ b/packages/InputDevices/res/values-ml/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"സ്വിസ് ജര്‍മന്‍"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ബെൽജിയൻ"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ബൾഗേറിയൻ"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ബൾഗേറിയൻ, ഉച്ചാരണശബ്‌ദം"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ഇറ്റാലിയൻ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ഡാനിഷ്"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"നോർവീജിയൻ"</string>
diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml
index a8fc6615f4c1..18c2faf7e464 100644
--- a/packages/InputDevices/res/values-mn/strings.xml
+++ b/packages/InputDevices/res/values-mn/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Швейцарийн Герман"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельги"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгар"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгар хэл, Авиа зүй"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Итали"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Дани"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвеги"</string>
diff --git a/packages/InputDevices/res/values-mr/strings.xml b/packages/InputDevices/res/values-mr/strings.xml
index da6caab3842e..d8788c97b90c 100644
--- a/packages/InputDevices/res/values-mr/strings.xml
+++ b/packages/InputDevices/res/values-mr/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस जर्मन"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियन"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बल्गेरियन"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"बल्गेरियन, फोनेटिक"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"इटालियन"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"डॅनिश"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नॉर्वेजियन"</string>
diff --git a/packages/InputDevices/res/values-ms/strings.xml b/packages/InputDevices/res/values-ms/strings.xml
index 975024ba1cdf..8bc9b2a73cb2 100644
--- a/packages/InputDevices/res/values-ms/strings.xml
+++ b/packages/InputDevices/res/values-ms/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Jerman Switzerland"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Bahasa Belgium"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bahasa Bulgaria"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bahasa Bulgaria, Fonetik"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Bahasa Itali"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Bahasa Denmark"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Bahasa Norway"</string>
diff --git a/packages/InputDevices/res/values-my/strings.xml b/packages/InputDevices/res/values-my/strings.xml
index 5484d9d2a2e8..26720576ee91 100644
--- a/packages/InputDevices/res/values-my/strings.xml
+++ b/packages/InputDevices/res/values-my/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ဆွစ် ဂျာမန်"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ဘယ်လ်ဂျီယန်"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ဘူဂေးရီယန်း"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ဘူလ်ဂေးရီးယား အသံထွက်"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"အီတာလီယန်"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ဒိန်းမတ်"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"နောဝေဂျီယန်"</string>
diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml
index 54840338b966..83b87e5dd45e 100644
--- a/packages/InputDevices/res/values-nb/strings.xml
+++ b/packages/InputDevices/res/values-nb/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Sveitsisk standardtysk"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisk"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarsk"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarsk, fonetisk"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiensk"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dansk"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norsk"</string>
diff --git a/packages/InputDevices/res/values-ne/strings.xml b/packages/InputDevices/res/values-ne/strings.xml
index 24816c1126e3..4801f752fa6c 100644
--- a/packages/InputDevices/res/values-ne/strings.xml
+++ b/packages/InputDevices/res/values-ne/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"स्विस-जर्मन"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"बेल्जियन"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"बुल्गेरियन"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"बुल्गेरियन फोनेटिक किबोर्ड"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"इटालियन"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"डेनिश"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"नर्वेजियन"</string>
diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml
index e000a30f9cf9..6e5849013fe7 100644
--- a/packages/InputDevices/res/values-nl/strings.xml
+++ b/packages/InputDevices/res/values-nl/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Zwitsers Duits"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgisch"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgaars"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgaars, fonetisch"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiaans"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Deens"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Noors"</string>
diff --git a/packages/InputDevices/res/values-or/strings.xml b/packages/InputDevices/res/values-or/strings.xml
index 6185ff77695d..aa16151752e6 100644
--- a/packages/InputDevices/res/values-or/strings.xml
+++ b/packages/InputDevices/res/values-or/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ସୁଇସ୍ ଜର୍ମାନ୍‍"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ବେଲ୍‍ଜିଆନ୍‍"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ବୁଲଗାରିଆନ୍‍"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ବୁଲଗେରିଆନ୍, ଫୋନେଟିକ୍"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ଇଟାଲିୟାନ୍‌"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ଡାନିଶ୍‍"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ନରୱେଜିଆନ୍"</string>
diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml
index 97cf28bbc66c..7e5a03cde986 100644
--- a/packages/InputDevices/res/values-pa/strings.xml
+++ b/packages/InputDevices/res/values-pa/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ਸਵਿਸ ਜਰਮਨ"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"ਬੈਲਜੀਅਨ"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"ਬਲਗੇਰੀਅਨ"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ਬਲਗੇਰੀਅਨ, ਧੁਨੀਆਤਮਿਕ"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ਇਤਾਲਵੀ"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ਡੈਨਿਸ਼"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"ਨਾਰਵੇਜੀਅਨ"</string>
diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml
index 61819b624112..51755e2b2b52 100644
--- a/packages/InputDevices/res/values-pl/strings.xml
+++ b/packages/InputDevices/res/values-pl/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Niemiecki (Szwajcaria)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgijski"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bułgarski"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bułgarski (znaki fonetyczne)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Włoski"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Duński"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norweski"</string>
diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml
index 665a1c7632f1..128868856f5a 100644
--- a/packages/InputDevices/res/values-pt-rBR/strings.xml
+++ b/packages/InputDevices/res/values-pt-rBR/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemão suíço"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro, fonético"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarquês"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norueguês"</string>
diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml
index 1ccc64409452..89bb3e376678 100644
--- a/packages/InputDevices/res/values-pt-rPT/strings.xml
+++ b/packages/InputDevices/res/values-pt-rPT/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemão (Suíça)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro, fonético"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarquês"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norueguês"</string>
diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml
index 665a1c7632f1..128868856f5a 100644
--- a/packages/InputDevices/res/values-pt/strings.xml
+++ b/packages/InputDevices/res/values-pt/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Alemão suíço"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belga"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Búlgaro"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Búlgaro, fonético"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Dinamarquês"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norueguês"</string>
diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml
index e0b488585c78..f7ff2504f31f 100644
--- a/packages/InputDevices/res/values-ro/strings.xml
+++ b/packages/InputDevices/res/values-ro/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Germană (Elveția)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiană"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgară"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgară fonetică"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italiană"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Daneză"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegiană"</string>
diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml
index 41ccf1a4f689..70668133ae8b 100644
--- a/packages/InputDevices/res/values-ru/strings.xml
+++ b/packages/InputDevices/res/values-ru/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"немецкий (Швейцария)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"нидерландский (Бельгия)"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"болгарский"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"болгарский (фонетическая)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"итальянский"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"датский"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвежский"</string>
@@ -28,7 +27,7 @@
<string name="keyboard_layout_finnish" msgid="5585659438924315466">"финский"</string>
<string name="keyboard_layout_croatian" msgid="4172229471079281138">"хорватский"</string>
<string name="keyboard_layout_czech" msgid="1349256901452975343">"чешский"</string>
- <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Чешский (QWERTY)"</string>
+ <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"чешский (QWERTY)"</string>
<string name="keyboard_layout_estonian" msgid="8775830985185665274">"эстонский"</string>
<string name="keyboard_layout_hungarian" msgid="4154963661406035109">"венгерский"</string>
<string name="keyboard_layout_icelandic" msgid="5836645650912489642">"исландский"</string>
@@ -45,9 +44,9 @@
<string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"испанский (Латинская Америка)"</string>
<string name="keyboard_layout_latvian" msgid="4405417142306250595">"латышский"</string>
<string name="keyboard_layout_persian" msgid="3920643161015888527">"Персидский"</string>
- <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Азербайджанский"</string>
+ <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"азербайджанский"</string>
<string name="keyboard_layout_polish" msgid="1121588624094925325">"польский"</string>
- <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Белорусский"</string>
- <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгольский"</string>
- <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинский"</string>
+ <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"белорусский"</string>
+ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монгольский"</string>
+ <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузинский"</string>
</resources>
diff --git a/packages/InputDevices/res/values-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml
index 4d355d73e985..eb3c44678db2 100644
--- a/packages/InputDevices/res/values-si/strings.xml
+++ b/packages/InputDevices/res/values-si/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ස්විස් ජර්මන්"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"බෙල්ගියන්"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"බල්ගේරියානු"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"බල්ගේරියානු, ශබ්දිම"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ඉතාලි"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ඩෙන්මාර්ක"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"නෝර්වීජියානු"</string>
diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml
index c7ff2fd5d994..e7e15b0cbd00 100644
--- a/packages/InputDevices/res/values-sk/strings.xml
+++ b/packages/InputDevices/res/values-sk/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švajčiarske (nemčina)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgické"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bulharské"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulharská fonetická klávesnica"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"talianske"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"dánske"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"nórske"</string>
diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml
index 68741b42e880..f4d1e5732fc5 100644
--- a/packages/InputDevices/res/values-sl/strings.xml
+++ b/packages/InputDevices/res/values-sl/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"švicarska nemška"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belgijska"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bolgarska"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"bolgarščina (fonetična)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italijanska"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danska"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norveška"</string>
diff --git a/packages/InputDevices/res/values-sq/strings.xml b/packages/InputDevices/res/values-sq/strings.xml
index b3677cd699b4..9b4fe9ee7580 100644
--- a/packages/InputDevices/res/values-sq/strings.xml
+++ b/packages/InputDevices/res/values-sq/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"gjermanishte zvicerane"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"belge"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"bullgarisht"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Tastierë bullgare, fonetike"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"italisht"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"danisht"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"norvegjisht"</string>
diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml
index 2f68903c4e2d..e3a20438f34a 100644
--- a/packages/InputDevices/res/values-sr/strings.xml
+++ b/packages/InputDevices/res/values-sr/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"швајцарско немачка"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"белгијска"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"бугарска"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"бугарска фонетска"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"италијанска"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"данска"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвешка"</string>
diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml
index b465fa64ce82..097ada40be63 100644
--- a/packages/InputDevices/res/values-sv/strings.xml
+++ b/packages/InputDevices/res/values-sv/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tyskt (Schweiz)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiskt"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgariskt"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgariska (fonetiskt)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italienskt"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danskt"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norskt"</string>
diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml
index 794d907f006d..325796232d76 100644
--- a/packages/InputDevices/res/values-sw/strings.xml
+++ b/packages/InputDevices/res/values-sw/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Kijerumani cha Uswisi"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Kibelgiji"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Kibulgaria"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Kibulgaria, Fonetiki"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Kiitaliano"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Kidenmarki"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Kinorwei"</string>
diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml
index b75b57d5f358..d3c6000fd9d4 100644
--- a/packages/InputDevices/res/values-ta/strings.xml
+++ b/packages/InputDevices/res/values-ta/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"ஸ்விஸ் ஜெர்மன்"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"பெல்ஜியன்"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"பல்கேரியன்"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"பல்கேரியன், ஒலிப்புமுறை"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"இத்தாலியன்"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"டேனிஷ்"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"நார்வேஜியன்"</string>
diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml
index d40c3e0d5d9f..c0253e528684 100644
--- a/packages/InputDevices/res/values-te/strings.xml
+++ b/packages/InputDevices/res/values-te/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"స్విస్ జర్మన్"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"బెల్జియన్"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"బల్గేరియన్"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"బల్గేరియన్, ఫోనెటిక్"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"ఇటాలియన్"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"డేనిష్"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"నార్వేజియన్"</string>
diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml
index 59136506abe0..c8e0e62f6e18 100644
--- a/packages/InputDevices/res/values-th/strings.xml
+++ b/packages/InputDevices/res/values-th/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"เยอรมันสวิส"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"เบลเยียม"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"บัลแกเรีย"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"ภาษาบัลแกเรีย ตามการออกเสียง"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"อิตาลี"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"เดนมาร์ก"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"นอร์เวย์"</string>
diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml
index 21ea5de903bd..b9aee76d400c 100644
--- a/packages/InputDevices/res/values-tl/strings.xml
+++ b/packages/InputDevices/res/values-tl/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml
index a89cea59b30f..f093abb9a708 100644
--- a/packages/InputDevices/res/values-tr/strings.xml
+++ b/packages/InputDevices/res/values-tr/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"İsviçre Almancası"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belçika dili"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarca"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarca, Fonetik"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"İtalyanca"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danca"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norveççe"</string>
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 4b37ca954d37..d9b58d21c14f 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"німецька (Швейцарія)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"бельгійська"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"болгарська"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгарська (фонетична)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"італійська"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"данська"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвезька"</string>
diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml
index ca42086b8811..2bff7ed2f234 100644
--- a/packages/InputDevices/res/values-ur/strings.xml
+++ b/packages/InputDevices/res/values-ur/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"سوئس جرمن"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"بیلجیئن"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"بلغاریائی"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"بلغاریائی، فونیٹک"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"اطالوی"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"ڈینش"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"نارویجین"</string>
diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml
index 77a06b5a4d79..9245aebd22ba 100644
--- a/packages/InputDevices/res/values-uz/strings.xml
+++ b/packages/InputDevices/res/values-uz/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Nemis (Shveytsariya)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgiyancha"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bolgarcha"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bolgar, fonetik"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italyancha"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Datcha"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norvegcha"</string>
diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml
index fd570efe8693..76fd0bff0e2a 100644
--- a/packages/InputDevices/res/values-vi/strings.xml
+++ b/packages/InputDevices/res/values-vi/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Tiếng Đức Thụy Sĩ"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Tiếng Bỉ"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Tiếng Bungary"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Tiếng Bulgaria, Phiên âm"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Tiếng Ý"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Tiếng Đan Mạch"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Tiếng Na Uy"</string>
diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml
index afc373acd61f..aa75605b5abd 100644
--- a/packages/InputDevices/res/values-zh-rCN/strings.xml
+++ b/packages/InputDevices/res/values-zh-rCN/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"瑞士德语"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利时语"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亚语"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"保加利亚语,注音"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"意大利语"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麦语"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威语"</string>
diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml
index 775fa2acfcec..dc824b97369d 100644
--- a/packages/InputDevices/res/values-zh-rHK/strings.xml
+++ b/packages/InputDevices/res/values-zh-rHK/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"德文(瑞士)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利時文"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亞文"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"保加利亞文 (拼音)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"意大利文"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麥文"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威文"</string>
diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml
index b4a059cbeb1f..c2714da0c3d9 100644
--- a/packages/InputDevices/res/values-zh-rTW/strings.xml
+++ b/packages/InputDevices/res/values-zh-rTW/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"德文 (瑞士)"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"比利時式"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"保加利亞文"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"保加利亞文 (拼音)"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"義大利文"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"丹麥文"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"挪威文"</string>
diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml
index 0a2499ad8967..3af1da1ac694 100644
--- a/packages/InputDevices/res/values-zu/strings.xml
+++ b/packages/InputDevices/res/values-zu/strings.xml
@@ -19,8 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Isi-Swiss German"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Isi-Belgian"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Isi-Bulgarian"</string>
- <!-- no translation found for keyboard_layout_bulgarian_phonetic (7568914730360106653) -->
- <skip />
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Isi-Bulgarian, Ifonetiki"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Isi-Italian"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Isi-Danish"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Isi-Norwegian"</string>
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 9d068fe73d16..1e1394096926 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -105,6 +105,9 @@
<!-- Turkish keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_turkish">Turkish</string>
+ <!-- Turkish keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_turkish_f">Turkish F</string>
+
<!-- Ukrainian keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_ukrainian">Ukrainian</string>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 6b78b687b7e3..976a279ea2e4 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -132,6 +132,10 @@
android:label="@string/keyboard_layout_turkish"
android:keyboardLayout="@raw/keyboard_layout_turkish" />
+ <keyboard-layout android:name="keyboard_layout_turkish_f"
+ android:label="@string/keyboard_layout_turkish_f"
+ android:keyboardLayout="@raw/keyboard_layout_turkish_f" />
+
<keyboard-layout android:name="keyboard_layout_ukrainian"
android:label="@string/keyboard_layout_ukrainian"
android:keyboardLayout="@raw/keyboard_layout_ukrainian" />
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
new file mode 100644
index 000000000000..f66ff007fb90
--- /dev/null
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -0,0 +1,51 @@
+<!--
+ Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:orientation="horizontal"
+ android:padding="16dp">
+
+ <ImageView
+ android:id="@+id/user_photo"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_gravity="bottom"
+ android:contentDescription="@string/user_image_photo_selector"
+ android:background="@*android:drawable/spinner_background_holo_dark"
+ android:scaleType="fitCenter"/>
+
+ <EditText
+ android:id="@+id/user_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:layout_weight="1"
+ android:minWidth="200dp"
+ android:layout_marginStart="6dp"
+ android:minHeight="@dimen/min_tap_target_size"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAlignment="viewStart"
+ android:inputType="text|textCapWords"
+ android:selectAllOnFocus="true"
+ android:hint="@string/user_nickname"
+ android:maxLength="100"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml b/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml
new file mode 100644
index 000000000000..923d022aea68
--- /dev/null
+++ b/packages/SettingsLib/res/layout/restricted_popup_menu_item.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingEnd="16dp"
+ android:paddingStart="16dp">
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:textColor="?android:attr/textColorAlertDialogListItem" />
+
+ <ImageView
+ android:id="@+id/restricted_icon"
+ android:layout_width="@*android:dimen/config_restrictedIconSize"
+ android:layout_height="@*android:dimen/config_restrictedIconSize"
+ android:layout_alignParentRight="true"
+ android:scaleType="centerInside"
+ android:src="@*android:drawable/ic_info"
+ android:tint="?android:attr/colorAccent"
+ android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/packages/SettingsLib/res/layout/user_creation_progress_dialog.xml b/packages/SettingsLib/res/layout/user_creation_progress_dialog.xml
new file mode 100644
index 000000000000..fe09aaca0b1f
--- /dev/null
+++ b/packages/SettingsLib/res/layout/user_creation_progress_dialog.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.
+ -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/message"
+ style="?android:attr/textAppearanceListItem"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:paddingTop="24dp"
+ android:paddingBottom="24dp" />
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 6751fa4583c5..2f97f272c8b8 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Voordat jy \'n beperkte profiel kan skep, moet jy \'n skermslot opstel om jou programme en persoonlike data te beskerm."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Stel slot op"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Skakel oor na <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Voeg gas by"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Verwyder gas"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gas"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Toestelverstek"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Gedeaktiveer"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Geaktiveer"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 470e780fc049..f0d8a40b54ef 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"የተገደበ መገለጫ መፍጠር ከመቻልዎ በፊት መተግበሪያዎችዎን እና የግል ውሂብዎን ለመጠበቅ ቁልፍ ማያ ገጽ ማዋቀር አለብዎት።"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ቁልፍ አዘጋጅ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"ወደ <xliff:g id="USER_NAME">%s</xliff:g> ቀይር"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"እንግዳን አክል"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"እንግዳን አስወግድ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"እንግዳ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"የመሣሪያ ነባሪ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ተሰናክሏል"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ነቅቷል"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 0b4538097e41..323ba44ba420 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -549,9 +549,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"قبل أن تتمكن من إنشاء ملف شخصي مقيد، يلزمك إعداد تأمين للشاشة لحماية تطبيقاتك وبياناتك الشخصية."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"تعيين التأمين"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"التبديل إلى <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"إضافة ضيف"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"إزالة جلسة الضيف"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ضيف"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"الإعداد التلقائي للجهاز"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غير مفعّل"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index f993dba39476..7679c649c350 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"আপুনি সীমিত প্ৰ\'ফাইল এটা সৃষ্টি কৰাৰ আগেয়ে, আপোনাৰ ব্যক্তিগত ডেটা আৰু এপবিলাকক সুৰক্ষিত কৰিবলৈ স্ক্ৰীণ লক এটা নিৰ্ধাৰণ কৰিব লাগিব।"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"লক ছেট কৰক"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>লৈ সলনি কৰক"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ডিভাইচ ডিফ’ল্ট"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"অক্ষম কৰা আছে"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"সক্ষম কৰা আছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 6a506614f970..d0bf0edca17a 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Məhdudlaşdırılmış profil yaratmadan öncə, Siz tətbiqlərinizi və şəxsi datanızı qorumaq üçün ekran kilidi quraşdırmalısınız."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Kilid ayarlayın"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> adlı istifadəçiyə keçin"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Qonaq əlavə edin"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Qonağı silin"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Qonaq"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Cihaz defoltu"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index cf988ab3589f..3beb9b84e383 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Da biste mogli da napravite ograničeni profil, treba da podesite zaključavanje ekrana da biste zaštitili aplikacije i lične podatke."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Podesi zaključavanje"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Pređi na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Podrazumevano za uređaj"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogućeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 7f6237da9ee0..af6da4e64a36 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Перш чым вы зможаце стварыць профіль з абмежаваннямi, вам трэба наладзіць блакiроўку экрана для абароны сваiх дадаткаў і асабістай інфармацыі."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Усталёўка блакiроўкi"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Пераключыцца на карыстальніка <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Дадаць госця"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Выдаліць госця"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Госць"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Стандартная прылада"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Выключана"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Уключана"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 747cb266b6de..0097c9ced9ad 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Преди да можете да създадете потребителски профил с ограничена функционалност, трябва да настроите заключения екран, за да защитите приложенията и личните си данни."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Задаване на заключване"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Превключване към <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Добавяне на гост"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Премахване на госта"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Стандартна настройка за у-вото"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Деактивирано"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Активирано"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 1ab88ed8e00e..c3515420efed 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"আপনি একটি সীমাবদ্ধযুক্ত প্রোফাইল তৈরি করার আগে, আপনাকে আপনার অ্যাপ্লিকেশন এবং ব্যক্তিগত ডেটা সুরক্ষিত করার জন্য একটি স্ক্রিন লক সেট-আপ করতে হবে।"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"লক সেট করুন"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-এ পাল্টান"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ করুন"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি সরান"</string>
<string name="guest_nickname" msgid="6332276931583337261">"অতিথি"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ডিভাইসের ডিফল্ট"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"বন্ধ করা আছে"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"চালু করা আছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index e329c993eedb..3d3587302e67 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Prije nego vam se omogući kreiranje ograničenog profila, morate postaviti zaključavanje ekrana da biste zaštitili svoje aplikacije i lične podatke."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Postaviti zaključavanje"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Prebaci na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Zadana postavka uređaja"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogućeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 5ffdacd19c22..b1edd7877650 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Per crear un perfil restringit, has de configurar una pantalla de bloqueig per protegir les aplicacions i les dades personals."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Defineix un bloqueig"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Canvia a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Afegeix un convidat"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Suprimeix el convidat"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidat"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Opció predeter. del dispositiu"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 0aef99feb04f..bdce444ce81b 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Před vytvořením omezeného profilu je nutné nejprve nastavit zámek obrazovky k ochraně aplikací a dat."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Nastavit zámek"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Přepnout na uživatele <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Přidat hosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Odstranit hosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Host"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Výchozí nastavení zařízení"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuto"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuto"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 98068cb172fa..1dc0e4ed077f 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Før du kan oprette en begrænset profil, skal du oprette en skærmlås for at beskytte dine apps og personlige data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurer låseskærmen"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Skift til <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Tilføj gæsten"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gæsten"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gæst"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Enhedens standardindstilling"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiveret"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiveret"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 0837ad33ab06..a9cdf1ff81bf 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Vor dem Erstellen eines eingeschränkten Profils musst du eine Displaysperre einrichten, um deine Apps und personenbezogenen Daten zu schützen."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Sperre einrichten"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Zu <xliff:g id="USER_NAME">%s</xliff:g> wechseln"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Gast hinzufügen"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Gast entfernen"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Gerätestandard"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiviert"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiviert"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 1f9d97764016..00b843495a9b 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Προκειμένου να μπορέσετε να δημιουργήσετε ένα περιορισμένο προφίλ, θα πρέπει να δημιουργήσετε ένα κλείδωμα οθόνης για την προστασία των εφαρμογών και των προσωπικών δεδομένων σας."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Ορισμός κλειδώματος"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Εναλλαγή σε <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Προσθήκη επισκέπτη"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Κατάργηση επισκέπτη"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Επισκέπτης"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Προεπιλογή συσκευής"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ανενεργή"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ενεργή"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index abe61a97096c..4bb2d8a592f1 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Device default"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 959ad46bcf61..05c12d680c89 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Device default"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index abe61a97096c..4bb2d8a592f1 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Device default"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index abe61a97096c..4bb2d8a592f1 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Add guest"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remove guest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Guest"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Device default"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 738dd2a2ba31..699ad426834a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -545,9 +545,14 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data.‎‏‎‎‏‎"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎Set lock‎‏‎‎‏‎"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎Switch to ‎‏‎‎‏‏‎<xliff:g id="USER_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎Creating new user…‎‏‎‎‏‎"</string>
+ <string name="user_nickname" msgid="262624187455825083">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎Nickname‎‏‎‎‏‎"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‎Add guest‎‏‎‎‏‎"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‎‎‎Remove guest‎‏‎‎‏‎"</string>
<string name="guest_nickname" msgid="6332276931583337261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎Guest‎‏‎‎‏‎"</string>
+ <string name="user_image_take_photo" msgid="467512954561638530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎Take a photo‎‏‎‎‏‎"</string>
+ <string name="user_image_choose_photo" msgid="1363820919146782908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎Choose an image‎‏‎‎‏‎"</string>
+ <string name="user_image_photo_selector" msgid="433658323306627093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎Select photo‎‏‎‎‏‎"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‏‎‎Device default‎‏‎‎‏‎"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎Disabled‎‏‎‎‏‎"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎Enabled‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index d1e4fb5607a8..1dc5f5b332fc 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restringido, debes configurar un bloqueo de pantalla que proteja tus aplicaciones y datos personales."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Configurar bloqueo"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Agregar invitado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Predeterminado del dispositivo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 9ad71e2766a3..977b4694d6c6 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restringido, debes configurar una pantalla de bloqueo que proteja tus aplicaciones y datos personales."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Añadir invitado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Quitar invitado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitado"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Predeterminado por el dispositivo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 14d3b5782862..3c2593aa1ed7 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Enne piiratud profiili loomist peate seadistama lukustusekraani, et oma rakendusi ja isiklikke andmeid kaitsta."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Määra lukk"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Lülita kasutajale <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Lisa külaline"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Eemalda külaline"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Külaline"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Seadme vaikeseade"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Keelatud"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Lubatud"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 4a11aa77c7fc..33ce3c73959d 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Profil murriztua sortu aurretik, aplikazioak eta datu pertsonalak babesteko, pantaila blokeatzeko metodo bat konfiguratu beharko duzu."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Ezarri blokeoa"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Aldatu <xliff:g id="USER_NAME">%s</xliff:g> erabiltzailera"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gonbidatua"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Gailuaren balio lehenetsia"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desgaituta"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Gaituta"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 3373f815ebaf..045378886eaa 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"قبل از ایجاد یک نمایه محدود، باید یک قفل صفحه را برای محافظت از برنامه‌ها و داده‌های شخصی خود تنظیم کنید."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"تنظیم قفل"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"رفتن به <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"افزودن مهمان"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"حذف مهمان"</string>
<string name="guest_nickname" msgid="6332276931583337261">"مهمان"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"پیش‌فرض دستگاه"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غیرفعال"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 3d28f1d0a1f5..7f85572ba023 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Ennen kuin voit luoda rajoitetun profiilin, määritä näytön lukitus, joka suojelee sovelluksiasi ja henkilökohtaisia tietojasi."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Aseta lukitus"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Vaihda tähän käyttäjään: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Lisää vieras"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Poista vieras"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Vieras"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Laitteen oletusasetus"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ei käytössä"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Käytössä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 87d3de16f216..1745e0298871 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Avant de créer un profil limité, vous devez définir un écran de verrouillage pour protéger vos applications et vos données personnelles."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Valeur par défaut de l\'appareil"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Désactivé"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 5f5be97c322c..8c753ae26d3a 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Avant de créer un profil limité, vous devez définir un écran de verrouillage pour protéger vos applications et vos données personnelles."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Définir verrouillage écran"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Passer à <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Ajouter un invité"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Supprimer l\'invité"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invité"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Paramètre par défaut"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Désactivé"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index af430991083d..414564b505e4 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restrinxido, precisarás configurar un bloqueo da pantalla para protexer as túas aplicacións e datos persoais."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Engadir convidado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Quitar convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Funcionamento predeterminado"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 894a14ac3b74..634870ce77a4 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"તમે પ્રતિબંધિત પ્રોફાઇલ બનાવી શકો તે પહેલાં, તમારે તમારી ઍપ્લિકેશનો અને વ્યક્તિગત ડેટાની સુરક્ષા માટે એક લૉક સ્ક્રીન સેટ કરવાની જરૂર પડશે."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"લૉક સેટ કરો"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> પર સ્વિચ કરો"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"અતિથિ ઉમેરો"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"અતિથિને કાઢી નાખો"</string>
<string name="guest_nickname" msgid="6332276931583337261">"અતિથિ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ડિવાઇસ ડિફૉલ્ટ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"બંધ છે"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ચાલુ છે"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 6e734bc873b8..bd052c03ccb1 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"इससे पहले कि आप कोई प्रतिबंधित प्रोफ़ाइल बनाएं, आपको अपने ऐप्लिकेशन और व्यक्तिगत डेटा की सुरक्षा करने के लिए एक स्क्रीन लॉक सेट करना होगा."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करें"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> पर जाएं"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"मेहमान जोड़ें"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"मेहमान हटाएं"</string>
<string name="guest_nickname" msgid="6332276931583337261">"मेहमान"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"डिवाइस की डिफ़ॉल्ट सेटिंग"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"बंद है"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"चालू है"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 3edc4527fb01..3cff48068dda 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Prije izrade ograničenog profila trebate postaviti zaključavanje zaslona radi zaštite svojih aplikacija i osobnih podataka."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Postavi zaključavanje"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Prelazak na korisnika <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Dodavanje gosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Zadana postavka uređaja"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogućeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d86d88b3345d..854265caf47b 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Mielőtt létrehozhatna egy korlátozott profilt, be kell állítania egy képernyőzárat, hogy megvédje alkalmazásait és személyes adatait."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Képernyőzár beállítása"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Váltás erre: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Vendég hozzáadása"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Vendég munkamenet eltávolítása"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Vendég"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Alapértelmezett"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Letiltva"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Engedélyezve"</string>
diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml
index d1bea218b95f..9b55f4e02f4a 100644
--- a/packages/SettingsLib/res/values-hy/arrays.xml
+++ b/packages/SettingsLib/res/values-hy/arrays.xml
@@ -55,7 +55,7 @@
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="4045840870658484038">"Երբեք չօգտագործել HDCP ստուգումը"</item>
- <item msgid="8254225038262324761">"Օգտագործել HDCP-ը` միայն DRM-ի բովանդակությունը ստուգելու համար"</item>
+ <item msgid="8254225038262324761">"Օգտագործել HDCP-ը՝ միայն DRM-ի բովանդակությունը ստուգելու համար"</item>
<item msgid="6421717003037072581">"Միշտ օգտագործել HDCP ստուգումը"</item>
</string-array>
<string-array name="bt_hci_snoop_log_entries">
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index ffd95a45b61e..35b218edd1c2 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -343,7 +343,7 @@
<string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"Թարմացվելիս ընդգծել սարքաշարի ծածկույթները կանաչ գույնով"</string>
<string name="debug_hw_overdraw" msgid="8944851091008756796">"Վրիպազերծել GPU գերազանցումները"</string>
<string name="disable_overlays" msgid="4206590799671557143">"Կասեցնել HW վրադրումները"</string>
- <string name="disable_overlays_summary" msgid="1954852414363338166">"Միշտ օգտագործել GPU-ն` էկրանի կազմման համար"</string>
+ <string name="disable_overlays_summary" msgid="1954852414363338166">"Միշտ օգտագործել GPU-ն՝ էկրանի կազմման համար"</string>
<string name="simulate_color_space" msgid="1206503300335835151">"Նմանակել գունատարածքը"</string>
<string name="enable_opengl_traces_title" msgid="4638773318659125196">"Ակտիվացնել OpenGL հետքերը"</string>
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"Անջատել USB աուդիո երթուղումը"</string>
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Նախքան դուք կկարողանաք ստեղծել սահմանափակ պրոֆիլ, դուք պետք է կարգավորեք էկրանի կողպումը` ձեր ծրագրերը և անձնական տվյալները պաշտպանելու համար:"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Կարգավորել կողպումը"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Անցնել <xliff:g id="USER_NAME">%s</xliff:g> պրոֆիլին"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Ավելացնել հյուր"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Հեռացնել հյուրին"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Հյուր"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Կանխադրված տարբերակ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Անջատված է"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Միացված է"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index a6f88465a673..cf6455ffe0c6 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -156,7 +156,7 @@
<string name="launch_defaults_none" msgid="8049374306261262709">"Tidak ada setelan default"</string>
<string name="tts_settings" msgid="8130616705989351312">"Setelan text-to-speech"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Keluaran text-to-speech"</string>
- <string name="tts_default_rate_title" msgid="3964187817364304022">"Laju bicara"</string>
+ <string name="tts_default_rate_title" msgid="3964187817364304022">"Kecepatan ucapan"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Kecepatan teks diucapkan"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Tinggi nada"</string>
<string name="tts_default_pitch_summary" msgid="9132719475281551884">"Memengaruhi nada ucapan yang disintesis"</string>
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Sebelum dapat membuat profil yang dibatasi, Anda perlu menyiapkan kunci layar untuk melindungi aplikasi dan data pribadi Anda."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Setel kunci"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Beralih ke <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Tambahkan tamu"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Hapus tamu"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Tamu"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Default perangkat"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Nonaktif"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktif"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index caf2323813ca..b4be963b44b2 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Áður en þú getur búið til takmarkað snið þarftu að setja upp skjálás til að vernda forritin þín og persónuleg gögn."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Velja lás"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Skipta yfir í <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Bæta gesti við"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Fjarlægja gest"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gestur"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Sjálfgefin stilling tækis"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slökkt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Virkt"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 8d18727e384c..69b1b5eed9c9 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Prima di poter creare un profilo con limitazioni, devi impostare un blocco schermo per proteggere le tue app e i tuoi dati personali."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Imposta blocco"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Passa a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Aggiungi ospite"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Rimuovi ospite"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Ospite"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Parametro predefinito"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Non attivo"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Attivo"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index fff881c46433..158872df8365 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"לפני שתוכל ליצור פרופיל מוגבל, תצטרך להגדיר נעילת מסך כדי להגן על האפליקציות ועל הנתונים האישיים שלך."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"הגדרת נעילה"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"מעבר אל <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"הוספת אורח"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"הסרת אורח"</string>
<string name="guest_nickname" msgid="6332276931583337261">"אורח"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ברירת המחדל של המכשיר"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"מושבת"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"מופעל"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 5e579b758f09..13fd53fc1198 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"制限付きプロファイルを作成する場合は、アプリや個人データを保護するように画面ロックを設定しておく必要があります。"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ロックを設定"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> に切り替え"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ゲストを追加"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ゲストを削除"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ゲスト"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"デバイスのデフォルト"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"無効"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"有効"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 1b5fae9781b9..8302b8da95b7 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -545,9 +545,14 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"შეზღუდული პროფილის შექმნამდე, საკუთარი აპლიკაციებისა და პირადი მონაცემების დასაცავად, უნდა დაბლოკოთ ეკრანი."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"საკეტის დაყენება"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>-ზე გადართვა"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"მიმდინარეობს ახალი მომხმარებლის შექმნა…"</string>
+ <string name="user_nickname" msgid="262624187455825083">"მეტსახელი"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"სტუმრის დამატება"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"სტუმრის ამოშლა"</string>
<string name="guest_nickname" msgid="6332276931583337261">"სტუმარი"</string>
+ <string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
+ <string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
+ <string name="user_image_photo_selector" msgid="433658323306627093">"ფოტოს არჩევა"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"მოწყობილობის ნაგულისხმევი"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"გათიშული"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ჩართული"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 9c290e90dd8d..f411d00c595d 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Шектелген профайл жасақтауға дейін қолданбалар мен жеке деректерді қорғау үшін экран бекітпесін тағайындау қажет."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Бекітпе тағайындау"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> пайдаланушысына ауысу"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Қонақты енгізу"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Қонақты өшіру"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Қонақ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Құрылғыны әдепкісінше реттеу"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өшірулі"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Қосулы"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 2878db192d18..9b7cc4c1ab47 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"មុន​ពេល​អ្នក​អាច​បង្កើត​ប្រវត្តិ​រូប​បាន​ដាក់​កម្រិត អ្នក​ត្រូវ​រៀបចំ​ការ​ចាក់​សោ​អេក្រង់ ដើម្បី​ការពារ​កម្មវិធី និង​ទិន្នន័យ​ផ្ទាល់ខ្លួន​របស់​អ្នក។"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"កំណត់​ការ​ចាក់​សោ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"ប្ដូរទៅ <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"បញ្ចូល​ភ្ញៀវ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"លុប​​​ភ្ញៀវ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ភ្ញៀវ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"លំនាំដើម​របស់ឧបករណ៍"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"បានបិទ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"បានបើក"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 7b010564effa..1f74d9b60cc8 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ನೀವು ನಿರ್ಬಂಧಿತ ಪ್ರೊಫೈಲ್ ಅನ್ನು ರಚಿಸಬಹುದಾದರ ಮೊದಲು, ನಿಮ್ಮ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ವೈಯಕ್ತಿಕ ಡೇಟಾವನ್ನು ರಕ್ಷಿಸಲು ನೀವು ಪರದೆಯ ಲಾಕ್‌ ಹೊಂದಿಸುವ ಅಗತ್ಯವಿದೆ."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ಲಾಕ್ ಹೊಂದಿಸಿ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> ಗೆ ಬದಲಿಸಿ"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ಅತಿಥಿಯನ್ನು ಸೇರಿಸಿ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ಅತಿಥಿಯನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ಅತಿಥಿ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ಸಾಧನದ ಡೀಫಾಲ್ಟ್"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 696ed2955daa..d1308345a1f9 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -538,16 +538,26 @@
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"지금 설정"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"나중에"</string>
<string name="user_add_user_type_title" msgid="551279664052914497">"추가"</string>
- <string name="user_new_user_name" msgid="60979820612818840">"새 사용자"</string>
+ <string name="user_new_user_name" msgid="60979820612818840">"신규 사용자"</string>
<string name="user_new_profile_name" msgid="2405500423304678841">"새 프로필"</string>
<string name="user_info_settings_title" msgid="6351390762733279907">"사용자 정보"</string>
<string name="profile_info_settings_title" msgid="105699672534365099">"프로필 정보"</string>
<string name="user_need_lock_message" msgid="4311424336209509301">"제한된 프로필을 만들기 전에 화면 잠금을 설정하여 앱과 개인 데이터를 보호해야 합니다."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"잠금 설정"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>(으)로 전환"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"게스트 추가"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"게스트 삭제"</string>
<string name="guest_nickname" msgid="6332276931583337261">"게스트"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"기기 기본값"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"사용 중지됨"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"사용 설정됨"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 419c18f3b8c6..5672913174ee 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Чектелген профайл түзөөрдөн мурун, сиз өзүңүздүн колдонмолоруңузду жана жеке маалыматтарыңызды коргош үчүн, бөгөттөө көшөгөсүн орнотушуңуз керек болот."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Бөгөт коюу"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> аккаунтуна которулуу"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Конок"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Түзмөктүн демейки параметри"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өчүк"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Күйүк"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index a72861ee6aac..b3b3c8107d81 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ກ່ອນທ່ານຈະສ້າງໂປຣໄຟລ໌ທີ່ຖືກຈຳກັດນັ້ນ, ທ່ານຈະຕ້ອງຕັ້ງຄ່າການລັອກໜ້າຈໍ ເພື່ອປ້ອງກັນແອັບຯ ແລະຂໍ້ມູນສ່ວນໂຕຂອງທ່ານກ່ອນ."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ຕັ້ງການລັອກ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"ສະຫຼັບໄປ <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ເພີ່ມແຂກ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ລຶບແຂກອອກ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ແຂກ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ຄ່າເລີ່ມຕົ້ນອຸປະກອນ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ປິດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ເປີດການນຳໃຊ້ແລ້ວ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index c72bf21d8b29..2e5385ecc770 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Prieš kuriant apribotą profilį reikės nustatyti ekrano užraktą, kad apsaugotumėte programas ir asmeninius duomenis."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Nustatyti užraktą"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Perjungti į <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Pridėti svečią"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Pašalinti svečią"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Svečias"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Numatyt. įrenginio nustatymas"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Išjungta"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Įgalinta"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index d95e57df320e..f4fe8e30a73b 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Lai varētu izveidot ierobežotu profilu, jums jāiestata ekrāna bloķēšana, kas aizsargās jūsu lietotni un personas datus."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Iestatīt bloķēšanu"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Pārslēgties uz: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Pievienot viesi"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Noņemt viesi"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Viesis"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Ierīces noklusējums"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Atspējots"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Iespējots"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 58d4af1c2c64..1e933fbe5591 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Пред да може да создадете ограничен профил, треба да поставите заклучување на екранот за да ги заштити вашите апликации и лични податоци."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Постави заклучување"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Префрли на <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Додај гостин"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гостин"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Стандардно за уредот"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Оневозможено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Овозможено"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 3c281c8d972f..970f778119e3 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ഒരു നിയന്ത്രിത പ്രൊഫൈൽ സൃഷ്‌ടിക്കുന്നതിനുമുമ്പ്, നിങ്ങളുടെ അപ്ലിക്കേഷനുകളും വ്യക്തിഗത ഡാറ്റയും പരിരക്ഷിക്കുന്നതിന് ഒരു സ്‌ക്രീൻ ലോക്ക് സജ്ജീകരിക്കേണ്ടതുണ്ട്."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ലോക്ക് സജ്ജീകരിക്കുക"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> എന്നതിലേക്ക് മാറുക"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"അതിഥിയെ ചേർക്കുക"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"അതിഥിയെ നീക്കം ചെയ്യുക"</string>
<string name="guest_nickname" msgid="6332276931583337261">"അതിഥി"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ഉപകരണത്തിന്റെ ഡിഫോൾട്ട് പ്രവർത്തനം"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"പ്രവർത്തനക്ഷമമാക്കി"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 37fc5b47619c..3dc498946ff0 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Та хязгаарлагдсан профайл үүсгэхийн өмнө өөрийн апп-ууд болон хувийн өгөгдлийг хамгаалахын тулд дэлгэцийн түгжээг тохируулах шаардлагатай."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Түгжээг тохируулах"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> руу сэлгэх"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Зочин нэмэх"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Зочин хасах"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Зочин"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Төхөөрөмжийн өгөгдмөл"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Идэвхгүй болгосон"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Идэвхжүүлсэн"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 360f15897ebe..7e418a48b9aa 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"तुम्ही एक प्रतिबंधित प्रोफाईल तयार करु शकण्यापूर्वी तुम्हाला तुमचे अ‍ॅप्स आणि वैयक्तिक डेटा संरक्षित करण्यासाठी एक स्क्रीन लॉक सेट करण्याची आवश्यकता राहील."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"लॉक सेट करा"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> वर स्विच करा"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथी जोडा"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"अतिथी काढून टाका"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथी"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"डिव्हाइस डीफॉल्ट"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"बंद केले आहे"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सुरू केले आहे"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 68356df25ef9..4c23a8472c0e 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Sebelum anda boleh membuat profil yang terhad, anda perlu menyediakan kunci skrin untuk melindungi apl dan data peribadi anda."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Tetapkan kunci"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Tukar kepada <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Tambah tetamu"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Alih keluar tetamu"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Tetamu"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Lalai peranti"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Dilumpuhkan"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Didayakan"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 3729a8352abd..aa3cf08de29c 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ကန့်သတ်ကိုယ်ရေးအချက်အလက်တစ်ခုကို မပြုလုပ်မီ သင်၏ အပလီကေးရှင်းများနှင့် ကိုယ်ပိုင်အချက်အလက်များကို ကာကွယ်ရန် မျက်နှာပြင်သော့ချခြင်းကို စီမံရန် လိုအပ်လိမ့်မည်"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"သော့ချရန် သတ်မှတ်ပါ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> သို့ ပြောင်းရန်"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ဧည့်သည့် ထည့်ရန်"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ဧည့်သည်ကို ဖယ်ထုတ်ရန်"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ဧည့်သည်"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"စက်ပစ္စည်းမူရင်း"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ပိတ်ထားသည်"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ဖွင့်ထားသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 0e0e7617b06d..0c5431b6b24e 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Før du kan opprette en begrenset profil, må du konfigurere skjermlåsen for å beskytte appene og de personlige dataene dine."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Angi lås"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Bytt til <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Legg til en gjest"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Fjern gjesten"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gjest"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Standard for enheten"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slått av"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Slått på"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index ee368fd5bfcf..4c230d30050e 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"निषेधयुक्त प्रोफाइल बनाउनु अघि तपाईँको एप र व्यक्तिगत डेटा सुरक्षा गर्नाका लागि तपाईँले स्क्रिन लक सेटअप गर्नु पर्दछ ।"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"लक सेट गर्नुहोस्"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"प्रयोगकर्ता बदलेर <xliff:g id="USER_NAME">%s</xliff:g> पार्नुहोस्"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
<string name="guest_nickname" msgid="6332276931583337261">"अतिथि"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"पूर्वनिर्धारित यन्त्र"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"असक्षम पारिएको छ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सक्षम पारिएको छ"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 27f5dc9cc4d4..ae734a5b3fc8 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Voordat je een beperkt profiel kunt maken, moet je een schermvergrendeling instellen om je apps en persoonsgegevens te beschermen."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Vergrendeling instellen"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Overschakelen naar <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Gast toevoegen"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Gast verwijderen"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gast"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Apparaatstandaard"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Uitgeschakeld"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ingeschakeld"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index d200f502879e..18a88faf2fb4 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ପ୍ରତିବନ୍ଧିତ ପ୍ରୋଫାଇଲ୍‌ ତିଆରି କରିବାବେଳେ, ନିଜ ଆପ୍‌ ଓ ବ୍ୟକ୍ତିଗତ ତଥ୍ୟର ସୁରକ୍ଷା ପାଇଁ ଏକ ସ୍କ୍ରୀନ୍‌ ଲକ୍‌ ସେଟ୍‌ କରନ୍ତୁ।"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ଲକ୍‌ ସେଟ୍‌ କରନ୍ତୁ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>କୁ ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ଅତିଥି ଯୋଗ କରନ୍ତୁ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ଅତିଥିଙ୍କୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ଅତିଥି"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ଡିଭାଇସ୍ ଡିଫଲ୍ଟ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ସକ୍ଷମ କରାଯାଇଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 354ee126d9af..b1bde27fdf17 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ ਤੁਸੀਂ ਇੱਕ ਪ੍ਰਤਿਬੰਧਿਤ ਪ੍ਰੋਫਾਈਲ ਬਣਾ ਸਕੋ, ਤੁਹਾਨੂੰ ਆਪਣੀਆਂ ਐਪਾਂ ਅਤੇ ਨਿੱਜੀ ਡਾਟਾ ਸੁਰੱਖਿਅਤ ਕਰਨ ਲਈ ਇੱਕ ਸਕ੍ਰੀਨ ਲਾਕ ਸੈੱਟ ਅੱਪ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">" ਲਾਕ ਸੈੱਟ ਕਰੋ"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> \'ਤੇ ਜਾਓ"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"ਮਹਿਮਾਨ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"ਮਹਿਮਾਨ ਹਟਾਓ"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ਮਹਿਮਾਨ"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ਡੀਵਾਈਸ ਪੂਰਵ-ਨਿਰਧਾਰਤ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 095412c94502..7ea2d0c3e803 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Zanim utworzysz profil z ograniczeniami, musisz skonfigurować ekran blokady, by chronić aplikacje i osobiste dane."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Ustaw blokadę"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Przełącz na: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gościa"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Usuń gościa"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gość"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Ustawienie domyślne urządzenia"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Wyłączono"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Włączono"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 895a9872f4d4..102961ab31ea 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -545,9 +545,14 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Antes de criar um perfil restrito, configure um bloqueio de tela para proteger seus apps e seus dados pessoais."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
+ <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
+ <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
+ <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
+ <string name="user_image_photo_selector" msgid="433658323306627093">"Selecionar foto"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Padrão do dispositivo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desativado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 2d9f037297fb..de536b28aae9 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Antes de poder criar um perfil restrito, tem de configurar um bloqueio de ecrã para proteger as suas aplicações e dados pessoais."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Predefinição do dispositivo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desativada"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativada"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 895a9872f4d4..102961ab31ea 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -545,9 +545,14 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Antes de criar um perfil restrito, configure um bloqueio de tela para proteger seus apps e seus dados pessoais."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Definir bloqueio"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Mudar para <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Criando novo usuário…"</string>
+ <string name="user_nickname" msgid="262624187455825083">"Apelido"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Adicionar convidado"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Remover convidado"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Convidado"</string>
+ <string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
+ <string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
+ <string name="user_image_photo_selector" msgid="433658323306627093">"Selecionar foto"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Padrão do dispositivo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desativado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 728db174cd7b..934ef64161bb 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Înainte de a putea crea un profil cu permisiuni limitate, va trebui să configurați blocarea ecranului pentru a vă proteja aplicațiile și datele personale."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Configurați blocarea"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Treceți la <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Adăugați un invitat"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ștergeți invitatul"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Invitat"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Prestabilit pentru dispozitiv"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Dezactivat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index ff2115ee3ff0..e75ee7fd77e1 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Чтобы создать профиль с ограниченным доступом, необходимо предварительно настроить блокировку экрана для защиты приложений и личных данных"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Включить блокировку"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Переключиться на этот аккаунт: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Добавить аккаунт гостя"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Удалить аккаунт гостя"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гость"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Вариант по умолчанию"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Отключено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Включено"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index a883cc60d8e1..dc9b70299fbe 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"සීමිත පැතිකඩක් නිර්මාණය කිරීමට කලින්. ඔබගේ යෙදුම් සහ පෞද්ගලික දත්ත ආරක්ෂා කිරීමට තිර අගුලක් සැකසිය යුතුයි."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"අගුල සකසන්න"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> වෙත මාරු වන්න"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"අමුත්තා එක් කරන්න"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"අමුත්තා ඉවත් කරන්න"</string>
<string name="guest_nickname" msgid="6332276931583337261">"අමුත්තා"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"උපාංගයේ පෙරනිමිය"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"අබල කළා"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"සබලයි"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 05c63795382b..203f253019ee 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Pred vytvorením obmedzeného profilu je nutné najprv nastaviť zámku obrazovky na ochranu aplikácií a osobných údajov."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Nastaviť uzamknutie"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Prepnúť na používateľa <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Pridať hosťa"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Odobrať hosťa"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Hosť"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Predvol. nastavenie zariadenia"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuté"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuté"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index fd216e83d927..3bfda06f5f49 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Preden lahko ustvarite profil z omejitvami, morate nastaviti zaklepanje zaslona, da zaščitite aplikacije in osebne podatke."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Nastavi zaklepanje"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Preklop na račun <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gost"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Privzeta nastavitev naprave"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Onemogočeno"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogočeno"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 002c7fcda363..859cc704800f 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Para se të mund të krijosh një profil të kufizuar, duhet të konfigurosh një kyçje të ekranit për të mbrojtur aplikacionet dhe të dhënat e tua personale."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Cakto kyçjen"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Kalo te <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
<string name="guest_nickname" msgid="6332276931583337261">"I ftuar"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Parazgjedhja e pajisjes"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Joaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 25a1beb7af68..6c02c3c1ce6a 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -546,9 +546,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Да бисте могли да направите ограничени профил, треба да подесите закључавање екрана да бисте заштитили апликације и личне податке."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Подеси закључавање"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Пређи на корисника <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Додај госта"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Уклони госта"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гост"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Подразумевано за уређај"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Онемогућено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Омогућено"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 352cb0ab7d19..dc2167561709 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Innan du skapar en begränsad profil måste du konfigurera ett skärmlås för att skydda dina appar och personliga data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Konfigurera lås"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Byt till <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Lägg till gäst"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ta bort gäst"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Gäst"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Enhetens standardinställning"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inaktiverat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiverat"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index c5d70ac866b1..8f80e558e5d8 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Kabla uunde wasifu uliowekekwa vikwazo, utahitajika kuweka skrini iliyofungwa ili kulinda programu zako na data binafsi."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Weka ufunguo"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Badili utumie <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Weka mgeni"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ondoa mgeni"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mgeni"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Hali chaguomsingi ya kifaa"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Imezimwa"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Imewashwa"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 7837dd8d46df..106942104984 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"நீங்கள் வரையறுக்கப்பட்டச் சுயவிவரத்தை உருவாக்குவதற்கு முன்பு, உங்கள் ஆப்ஸ் மற்றும் தனிப்பட்ட தரவைப் பாதுகாக்கும் வகையில் நீங்கள் திரைப் பூட்டை அமைக்க வேண்டும்."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"பூட்டை அமை"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>க்கு மாறு"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"கெஸ்ட்டைச் சேர்"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"கெஸ்ட்டை அகற்று"</string>
<string name="guest_nickname" msgid="6332276931583337261">"கெஸ்ட்"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"சாதனத்தின் இயல்புநிலை"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"முடக்கப்பட்டது"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"இயக்கப்பட்டது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index e252eca5d72a..3ad2375a77dc 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"మీరు పరిమితం చేయబడిన ప్రొఫైల్‌ను సృష్టించడానికి ముందు, మీ అనువర్తనాలు మరియు వ్యక్తిగత డేటాను రక్షించడానికి స్క్రీన్ లాక్‌ను సెటప్ చేయాల్సి ఉంటుంది."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"లాక్‌ను సెట్ చేయి"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g>కు మార్చు"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"అతిథిని జోడించండి"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"అతిథిని తీసివేయండి"</string>
<string name="guest_nickname" msgid="6332276931583337261">"అతిథి"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"పరికర ఆటోమేటిక్ సెట్టింగ్"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"డిజేబుల్ చేయబడింది"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ఎనేబుల్ చేయబడింది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 7468d0450a7e..cc0ac748d124 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ก่อนที่คุณจะสามารถสร้างโปรไฟล์ที่ถูกจำกัดได้ คุณจะต้องตั้งค่าล็อกหน้าจอเพื่อปกป้องแอปและข้อมูลส่วนตัวของคุณ"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"ตั้งค่าล็อก"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"เปลี่ยนเป็น <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"เพิ่มผู้เข้าร่วม"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้เข้าร่วมออก"</string>
<string name="guest_nickname" msgid="6332276931583337261">"ผู้ใช้ชั่วคราว"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"ค่าเริ่มต้นของอุปกรณ์"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ปิดใช้"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"เปิดใช้"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 5d4e9752fd93..77ab7cfcaa0d 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Bago ka makakalikha ng pinaghihigpitang profile, kakailanganin mong mag-set up ng screen lock upang protektahan ang iyong apps at personal na data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Itakda ang lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Lumipat sa <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Magdagdag ng bisita"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Alisin ang bisita"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Bisita"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Default ng device"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Naka-disable"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Na-enable"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index f01f3faed73e..9d980c424ef9 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Kısıtlanmış bir profil oluşturabilmeniz için uygulamalarınızı ve kişisel verilerinizi korumak üzere bir ekran kilidi oluşturmanız gerekir."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Kilidi ayarla"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"<xliff:g id="USER_NAME">%s</xliff:g> hesabına geç"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Misafir ekle"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Misafir"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Cihaz varsayılanı"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Devre dışı"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Etkin"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 9ca2f062127a..9a7b89fc8f58 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -547,9 +547,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Перш ніж створювати обмежений профіль, потрібно налаштувати блокування екрана, щоб захистити свої програми та особисті дані."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Налаштувати блокування"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Перейти до користувача <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Гість"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"За умовчанням для пристрою"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Вимкнено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Увімкнено"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 8953f50078f1..1cc0859971f7 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"ایک محدود پروفائل بنانے سے پہلے، آپ کو اپنی ایپس اور ذاتی ڈیٹا کو محفوظ کرنے کیلئے ایک اسکرین لاک سیٹ اپ کرنا ہوگا۔"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"لاک سیٹ کریں"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"‫<xliff:g id="USER_NAME">%s</xliff:g> پر سوئچ کریں"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"مہمان کو شامل کریں"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"مہمان کو ہٹائیں"</string>
<string name="guest_nickname" msgid="6332276931583337261">"مہمان"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"آلہ ڈیفالٹ"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"غیر فعال"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index f25b3ac3c37f..3df33b681470 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -545,9 +545,14 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Cheklangan profil yaratish uchun, shaxsiy ilovlar va ma‘lumotlarni himoyalash maqsadida avval ekran qulfini yaratish lozim."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Qulf o‘rnatish"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Bunga almashish: <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Yangi foydalanuvchi yaratilmoqda…"</string>
+ <string name="user_nickname" msgid="262624187455825083">"Nik"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Mehmon kiritish"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Mehmon rejimini olib tashlash"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Mehmon"</string>
+ <string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
+ <string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
+ <string name="user_image_photo_selector" msgid="433658323306627093">"Surat tanlash"</string>
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Qurilma standarti"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Yoqilmagan"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Yoniq"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index b5798f31d0f9..2ed6ca8e6beb 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Trước khi bạn có thể tạo tiểu sử bị hạn chế, bạn sẽ cần thiết lập một màn hình khóa để bảo vệ các ứng dụng và dữ liệu cá nhân của bạn."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Thiết lập khóa"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Chuyển sang <xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Thêm khách"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Xóa phiên khách"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Khách"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Theo giá trị mặc định của thiết bị"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Đã tắt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Đã bật"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index c4dcfff1b579..024ea790f8f4 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"您需要先设置锁定屏幕来保护您的应用和个人数据,然后才可以创建受限个人资料。"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"设置屏幕锁定方式"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"切换到<xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"添加访客"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"移除访客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"访客"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"设备默认设置"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已启用"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index e04651cac5f6..75f050f21fee 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"建立限制存取的個人檔案前,您必須先設定上鎖畫面來保護您的應用程式和個人資料。"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"設定上鎖畫面"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"裝置預設設定"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index a1ae6b6c27a1..9866b476f310 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"如要建立設有限制的個人資料,你必須先設定螢幕鎖定來保護你的應用程式和個人資料。"</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"設定鎖定"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"切換至<xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"新增訪客"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"移除訪客"</string>
<string name="guest_nickname" msgid="6332276931583337261">"訪客"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"裝置預設設定"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 2dafad8114d8..b1825c49232c 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -545,9 +545,19 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Ngaphambi kokuthi ungadala iphrofayela ekhawulelwe, kuzomele usethe ukukhiya isikrini ukuze uvikele izinhlelo zakho zokusebenza nedatha yakho yomuntu siqu."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Setha ukukhiya"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Shintshela ku-<xliff:g id="USER_NAME">%s</xliff:g>"</string>
+ <!-- no translation found for creating_new_user_dialog_message (7232880257538970375) -->
+ <skip />
+ <!-- no translation found for user_nickname (262624187455825083) -->
+ <skip />
<string name="guest_new_guest" msgid="3482026122932643557">"Engeza isivakashi"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Susa isihambeli"</string>
<string name="guest_nickname" msgid="6332276931583337261">"Isihambeli"</string>
+ <!-- no translation found for user_image_take_photo (467512954561638530) -->
+ <skip />
+ <!-- no translation found for user_image_choose_photo (1363820919146782908) -->
+ <skip />
+ <!-- no translation found for user_image_photo_selector (433658323306627093) -->
+ <skip />
<string name="cached_apps_freezer_device_default" msgid="2616594131750144342">"Idivayisi ezenzakalelayo"</string>
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ikhutshaziwe"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Inikwe amandla"</string>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index e552d78e1a45..ef4b97f7743f 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -97,4 +97,8 @@
<!-- Size of advanced icon -->
<dimen name="advanced_icon_size">18dp</dimen>
+
+ <!-- Minimum width for the popup for updating a user's photo. -->
+ <dimen name="update_user_photo_popup_min_width">300dp</dimen>
+
</resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0afd5a88155a..37ba7a22dfc4 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1369,6 +1369,11 @@
<string name="user_set_lock_button">Set lock</string>
<!-- Label for switching to other user in the user switcher [CHAR LIMIT=35] -->
<string name="user_switch_to_user">Switch to <xliff:g id="user_name" example="John Doe">%s</xliff:g></string>
+ <!-- Dialog message when creating a new user [CHAR LIMIT=40] -->
+ <string name="creating_new_user_dialog_message">Creating new user…</string>
+
+ <!-- Title for the preference to enter the nickname of the user to display in the user switcher [CHAR LIMIT=25]-->
+ <string name="user_nickname">Nickname</string>
<!-- Label for adding a new guest in the user switcher [CHAR LIMIT=35] -->
<string name="guest_new_guest">Add guest</string>
@@ -1377,6 +1382,13 @@
<!-- Name for the guest user [CHAR LIMIT=35] -->
<string name="guest_nickname">Guest</string>
+ <!-- An option in a photo selection dialog to take a new photo [CHAR LIMIT=50] -->
+ <string name="user_image_take_photo">Take a photo</string>
+ <!-- An option in a photo selection dialog to choose a pre-existing image [CHAR LIMIT=50] -->
+ <string name="user_image_choose_photo">Choose an image</string>
+ <!-- Accessibility message for the photo selector which is a button/popup with the current photo [CHAR LIMIT=50] -->
+ <string name="user_image_photo_selector">Select photo</string>
+
<!-- List entry in developer settings to choose default device/system behavior for the app freezer [CHAR LIMIT=30]-->
<string name="cached_apps_freezer_device_default">Device default</string>
<!-- List entry in developer settings to disable the app freezer in developer settings [CHAR LIMIT=30]-->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 81a3fece02b3..bc0f54c8988d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -560,12 +560,13 @@ public class LocalBluetoothProfileManager {
mPbapProfile.setEnabled(device, true);
}
- if (mMapClientProfile != null) {
+ if ((mMapClientProfile != null)
+ && BluetoothUuid.containsAnyUuid(uuids, MapClientProfile.UUIDS)) {
profiles.add(mMapClientProfile);
removedProfiles.remove(mMapClientProfile);
}
- if ((mPbapClientProfile != null) && ArrayUtils.contains(localUuids, BluetoothUuid.PBAP_PCE)
+ if ((mPbapClientProfile != null)
&& BluetoothUuid.containsAnyUuid(uuids, PbapClientProfile.SRC_UUIDS)) {
profiles.add(mPbapClientProfile);
removedProfiles.remove(mPbapClientProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 19cb2f59f321..4881a86b398a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -47,8 +47,6 @@ public final class MapClientProfile implements LocalBluetoothProfile {
private final LocalBluetoothProfileManager mProfileManager;
static final ParcelUuid[] UUIDS = {
- BluetoothUuid.MAP,
- BluetoothUuid.MNS,
BluetoothUuid.MAS,
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 6c7e03f104dd..f83c7a2e3963 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -361,6 +361,36 @@ public class InfoMediaManager extends MediaManager {
return null;
}
+ boolean shouldDisableMediaOutput(String packageName) {
+ boolean shouldDisableMediaOutput = false;
+ if (TextUtils.isEmpty(packageName)) {
+ Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
+ return false;
+ }
+ final List<MediaRoute2Info> infos = mRouterManager.getAvailableRoutes(packageName);
+ if (infos.size() == 1) {
+ final MediaRoute2Info info = infos.get(0);
+ final int deviceType = info.getType();
+ switch (deviceType) {
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ case TYPE_GROUP:
+ shouldDisableMediaOutput = true;
+ break;
+ default:
+ shouldDisableMediaOutput = false;
+ break;
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "shouldDisableMediaOutput() MediaRoute2Info size : " + infos.size()
+ + ", package name : " + packageName + ", shouldDisableMediaOutput : "
+ + shouldDisableMediaOutput);
+ }
+ return shouldDisableMediaOutput;
+ }
+
private void refreshDevices() {
mMediaDevices.clear();
mCurrentConnectedDevice = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 72a6074ff89c..32419f49d8c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -402,6 +402,13 @@ public class LocalMediaManager implements BluetoothCallback {
return mPackageName;
}
+ /**
+ * Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
+ */
+ public boolean shouldDisableMediaOutput(String packageName) {
+ return mInfoMediaManager.shouldDisableMediaOutput(packageName);
+ }
+
@VisibleForTesting
MediaDevice updateCurrentConnectedDevice() {
MediaDevice connectedDevice = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index 0d54d7ee9b5c..fc16eb6b4277 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -72,6 +72,12 @@ public class MediaOutputSliceConstants {
"com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG";
/**
+ * An intent action to dismiss media output dialog.
+ */
+ public static final String ACTION_DISMISS_MEDIA_OUTPUT_DIALOG =
+ "com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG";
+
+ /**
* Settings package name.
*/
public static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
@@ -86,4 +92,10 @@ public class MediaOutputSliceConstants {
* SystemUi package name.
*/
public static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui";
+
+ /**
+ * An intent action to close settings panel.
+ */
+ public static final String ACTION_CLOSE_PANEL =
+ "com.android.settings.panel.action.CLOSE_PANEL";
}
diff --git a/libs/hwui/shader/BitmapShader.cpp b/packages/SettingsLib/src/com/android/settingslib/users/ActivityStarter.java
index fe653e85a021..081b5075ebbe 100644
--- a/libs/hwui/shader/BitmapShader.cpp
+++ b/packages/SettingsLib/src/com/android/settingslib/users/ActivityStarter.java
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-#include "BitmapShader.h"
+package com.android.settingslib.users;
-#include "SkImagePriv.h"
+import android.content.Intent;
-namespace android::uirenderer {
-BitmapShader::BitmapShader(const sk_sp<SkImage>& image, const SkTileMode tileModeX,
- const SkTileMode tileModeY, const SkMatrix* matrix)
- : Shader(matrix), skShader(image->makeShader(tileModeX, tileModeY)) {}
+/**
+ * An interface to start activities for result. This is used as a callback from controllers where
+ * activity starting isn't possible but we want to keep the intent building logic there.
+ */
+public interface ActivityStarter {
-sk_sp<SkShader> BitmapShader::makeSkShader() {
- return skShader;
+ /**
+ * Launch an activity for which you would like a result when it finished.
+ */
+ void startActivityForResult(Intent intent, int requestCode);
}
-
-BitmapShader::~BitmapShader() {}
-} // namespace android::uirenderer \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
new file mode 100644
index 000000000000..58599532d9cb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 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.settingslib.users;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+
+import java.io.File;
+import java.util.function.BiConsumer;
+
+/**
+ * This class encapsulates a Dialog for editing the user nickname and photo.
+ */
+public class EditUserInfoController {
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_SAVED_PHOTO = "pending_photo";
+
+ private Dialog mEditUserInfoDialog;
+ private Bitmap mSavedPhoto;
+ private EditUserPhotoController mEditUserPhotoController;
+ private boolean mWaitingForActivityResult = false;
+ private final String mFileAuthority;
+
+ public EditUserInfoController(String fileAuthority) {
+ mFileAuthority = fileAuthority;
+ }
+
+ private void clear() {
+ if (mEditUserPhotoController != null) {
+ mEditUserPhotoController.removeNewUserPhotoBitmapFile();
+ }
+ mEditUserInfoDialog = null;
+ mSavedPhoto = null;
+ }
+
+ /**
+ * This should be called when the container activity/fragment got re-initialized from a
+ * previously saved state.
+ */
+ public void onRestoreInstanceState(Bundle icicle) {
+ String pendingPhoto = icicle.getString(KEY_SAVED_PHOTO);
+ if (pendingPhoto != null) {
+ mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(new File(pendingPhoto));
+ }
+ mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
+ }
+
+ /**
+ * Should be called from the container activity/fragment when it's onSaveInstanceState is
+ * called.
+ */
+ public void onSaveInstanceState(Bundle outState) {
+ if (mEditUserInfoDialog != null && mEditUserPhotoController != null) {
+ // Bitmap cannot be stored into bundle because it may exceed parcel limit
+ // Store it in a temporary file instead
+ File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
+ if (file != null) {
+ outState.putString(KEY_SAVED_PHOTO, file.getPath());
+ }
+ }
+ outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+ }
+
+ /**
+ * Should be called from the container activity/fragment when an activity has started for
+ * take/choose/crop photo actions.
+ */
+ public void startingActivityForResult() {
+ mWaitingForActivityResult = true;
+ }
+
+ /**
+ * Should be called from the container activity/fragment after it receives a result from
+ * take/choose/crop photo activity.
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+
+ if (mEditUserPhotoController != null && mEditUserInfoDialog != null) {
+ mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ /**
+ * Creates a user edit dialog with option to change the user's name and photo.
+ *
+ * @param activityStarter - ActivityStarter is called with appropriate intents and request
+ * codes to take photo/choose photo/crop photo.
+ */
+ public Dialog createDialog(Activity activity, ActivityStarter activityStarter,
+ @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+ BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
+ LayoutInflater inflater = LayoutInflater.from(activity);
+ View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
+
+ EditText userNameView = content.findViewById(R.id.user_name);
+ userNameView.setText(defaultUserName);
+
+ ImageView userPhotoView = content.findViewById(R.id.user_photo);
+
+ // if oldUserIcon param is null then we use a default gray user icon
+ Drawable defaultUserIcon = oldUserIcon != null ? oldUserIcon : UserIcons.getDefaultUserIcon(
+ activity.getResources(), UserHandle.USER_NULL, false);
+ // in case a new photo was selected and the activity got recreated we have to load the image
+ Drawable userIcon = getUserIcon(activity, defaultUserIcon);
+ userPhotoView.setImageDrawable(userIcon);
+
+ if (canChangePhoto(activity)) {
+ mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
+ userPhotoView);
+ } else {
+ // some users can't change their photos so we need to remove suggestive
+ // background from the photoView
+ userPhotoView.setBackground(null);
+ }
+
+ mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
+ defaultUserName, title, successCallback, cancelCallback);
+
+ // Make sure the IME is up.
+ mEditUserInfoDialog.getWindow()
+ .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
+
+ return mEditUserInfoDialog;
+ }
+
+ private Drawable getUserIcon(Activity activity, Drawable defaultUserIcon) {
+ if (mSavedPhoto != null) {
+ return CircleFramedDrawable.getInstance(activity, mSavedPhoto);
+ }
+ return defaultUserIcon;
+ }
+
+ private Dialog buildDialog(Activity activity, View content, EditText userNameView,
+ @Nullable Drawable oldUserIcon, String defaultUserName, String title,
+ BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
+ return new AlertDialog.Builder(activity)
+ .setTitle(title)
+ .setView(content)
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ Drawable newUserIcon = mEditUserPhotoController != null
+ ? mEditUserPhotoController.getNewUserPhotoDrawable()
+ : null;
+ Drawable userIcon = newUserIcon != null
+ ? newUserIcon
+ : oldUserIcon;
+
+ String newName = userNameView.getText().toString().trim();
+ String userName = !newName.isEmpty() ? newName : defaultUserName;
+
+ clear();
+ if (successCallback != null) {
+ successCallback.accept(userName, userIcon);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+ clear();
+ if (cancelCallback != null) {
+ cancelCallback.run();
+ }
+ })
+ .setOnCancelListener(dialog -> {
+ clear();
+ if (cancelCallback != null) {
+ cancelCallback.run();
+ }
+ })
+ .create();
+ }
+
+ @VisibleForTesting
+ boolean canChangePhoto(Context context) {
+ return (PhotoCapabilityUtils.canCropPhoto(context)
+ && PhotoCapabilityUtils.canChoosePhoto(context))
+ || PhotoCapabilityUtils.canTakePhoto(context);
+ }
+
+ @VisibleForTesting
+ EditUserPhotoController createEditUserPhotoController(Activity activity,
+ ActivityStarter activityStarter, ImageView userPhotoView) {
+ return new EditUserPhotoController(activity, activityStarter, userPhotoView,
+ mSavedPhoto, mWaitingForActivityResult, mFileAuthority);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
new file mode 100644
index 000000000000..ecd40667843e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2013 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.settingslib.users;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.StrictMode;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract.DisplayPhoto;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListPopupWindow;
+import android.widget.TextView;
+
+import androidx.core.content.FileProvider;
+
+import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+
+import libcore.io.Streams;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class contains logic for starting activities to take/choose/crop photo, reads and transforms
+ * the result image.
+ */
+public class EditUserPhotoController {
+ private static final String TAG = "EditUserPhotoController";
+
+ // It seems that this class generates custom request codes and they may
+ // collide with ours, these values are very unlikely to have a conflict.
+ private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001;
+ private static final int REQUEST_CODE_TAKE_PHOTO = 1002;
+ private static final int REQUEST_CODE_CROP_PHOTO = 1003;
+ // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
+ // so we need a default photo size
+ private static final int DEFAULT_PHOTO_SIZE = 500;
+
+ private static final String IMAGES_DIR = "multi_user";
+ private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg";
+ private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg";
+ private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
+
+ private final int mPhotoSize;
+
+ private final Activity mActivity;
+ private final ActivityStarter mActivityStarter;
+ private final ImageView mImageView;
+ private final String mFileAuthority;
+
+ private final File mImagesDir;
+ private final Uri mCropPictureUri;
+ private final Uri mTakePictureUri;
+
+ private Bitmap mNewUserPhotoBitmap;
+ private Drawable mNewUserPhotoDrawable;
+
+ public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+ ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) {
+ mActivity = activity;
+ mActivityStarter = activityStarter;
+ mImageView = view;
+ mFileAuthority = fileAuthority;
+
+ mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
+ mImagesDir.mkdir();
+ mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting);
+ mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting);
+ mPhotoSize = getPhotoSize(activity);
+ mImageView.setOnClickListener(v -> showUpdatePhotoPopup());
+ mNewUserPhotoBitmap = bitmap;
+ }
+
+ /**
+ * Handles activity result from containing activity/fragment after a take/choose/crop photo
+ * action result is received.
+ */
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (resultCode != Activity.RESULT_OK) {
+ return false;
+ }
+ final Uri pictureUri = data != null && data.getData() != null
+ ? data.getData() : mTakePictureUri;
+ switch (requestCode) {
+ case REQUEST_CODE_CROP_PHOTO:
+ onPhotoCropped(pictureUri);
+ return true;
+ case REQUEST_CODE_TAKE_PHOTO:
+ case REQUEST_CODE_CHOOSE_PHOTO:
+ if (mTakePictureUri.equals(pictureUri)) {
+ if (PhotoCapabilityUtils.canCropPhoto(mActivity)) {
+ cropPhoto();
+ } else {
+ onPhotoNotCropped(pictureUri);
+ }
+ } else {
+ copyAndCropPhoto(pictureUri);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public Drawable getNewUserPhotoDrawable() {
+ return mNewUserPhotoDrawable;
+ }
+
+ private void showUpdatePhotoPopup() {
+ final Context context = mImageView.getContext();
+ final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context);
+ final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context);
+
+ if (!canTakePhoto && !canChoosePhoto) {
+ return;
+ }
+
+ final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>();
+
+ if (canTakePhoto) {
+ final String title = context.getString(R.string.user_image_take_photo);
+ items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
+ this::takePhoto));
+ }
+
+ if (canChoosePhoto) {
+ final String title = context.getString(R.string.user_image_choose_photo);
+ items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON,
+ this::choosePhoto));
+ }
+
+ final ListPopupWindow listPopupWindow = new ListPopupWindow(context);
+
+ listPopupWindow.setAnchorView(mImageView);
+ listPopupWindow.setModal(true);
+ listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+ listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items));
+
+ final int width = Math.max(mImageView.getWidth(), context.getResources()
+ .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width));
+ listPopupWindow.setWidth(width);
+ listPopupWindow.setDropDownGravity(Gravity.START);
+
+ listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
+ listPopupWindow.dismiss();
+ final RestrictedMenuItem item =
+ (RestrictedMenuItem) parent.getAdapter().getItem(position);
+ item.doAction();
+ });
+
+ listPopupWindow.show();
+ }
+
+ private void takePhoto() {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE);
+ appendOutputExtra(intent, mTakePictureUri);
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);
+ }
+
+ private void choosePhoto() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ appendOutputExtra(intent, mTakePictureUri);
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO);
+ }
+
+ private void copyAndCropPhoto(final Uri pictureUri) {
+ // TODO: Replace AsyncTask
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ final ContentResolver cr = mActivity.getContentResolver();
+ try (InputStream in = cr.openInputStream(pictureUri);
+ OutputStream out = cr.openOutputStream(mTakePictureUri)) {
+ Streams.copy(in, out);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to copy photo", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (!mActivity.isFinishing() && !mActivity.isDestroyed()) {
+ cropPhoto();
+ }
+ }
+ }.execute();
+ }
+
+ private void cropPhoto() {
+ // TODO: Use a public intent, when there is one.
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(mTakePictureUri, "image/*");
+ appendOutputExtra(intent, mCropPictureUri);
+ appendCropExtras(intent);
+ if (intent.resolveActivity(mActivity.getPackageManager()) != null) {
+ try {
+ StrictMode.disableDeathOnFileUriExposure();
+ mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO);
+ } finally {
+ StrictMode.enableDeathOnFileUriExposure();
+ }
+ } else {
+ onPhotoNotCropped(mTakePictureUri);
+ }
+ }
+
+ private void appendOutputExtra(Intent intent, Uri pictureUri) {
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri));
+ }
+
+ private void appendCropExtras(Intent intent) {
+ intent.putExtra("crop", "true");
+ intent.putExtra("scale", true);
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", mPhotoSize);
+ intent.putExtra("outputY", mPhotoSize);
+ }
+
+ private void onPhotoCropped(final Uri data) {
+ // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ InputStream imageStream = null;
+ try {
+ imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ return BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ return null;
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ onPhotoProcessed(bitmap);
+
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ private void onPhotoNotCropped(final Uri data) {
+ // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ // Scale and crop to a square aspect ratio
+ Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize,
+ Config.ARGB_8888);
+ Canvas canvas = new Canvas(croppedImage);
+ Bitmap fullImage;
+ try {
+ InputStream imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ fullImage = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ return null;
+ }
+ if (fullImage != null) {
+ int rotation = getRotation(mActivity, data);
+ final int squareSize = Math.min(fullImage.getWidth(),
+ fullImage.getHeight());
+ final int left = (fullImage.getWidth() - squareSize) / 2;
+ final int top = (fullImage.getHeight() - squareSize) / 2;
+
+ Matrix matrix = new Matrix();
+ RectF rectSource = new RectF(left, top,
+ left + squareSize, top + squareSize);
+ RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize);
+ matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER);
+ matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f);
+ canvas.drawBitmap(fullImage, matrix, new Paint());
+ return croppedImage;
+ } else {
+ // Bah! Got nothin.
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ onPhotoProcessed(bitmap);
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ /**
+ * Reads the image's exif data and determines the rotation degree needed to display the image
+ * in portrait mode.
+ */
+ private int getRotation(Context context, Uri selectedImage) {
+ int rotation = -1;
+ try {
+ InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
+ ExifInterface exif = new ExifInterface(imageStream);
+ rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+ } catch (IOException exception) {
+ Log.e(TAG, "Error while getting rotation", exception);
+ }
+
+ switch (rotation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ private void onPhotoProcessed(Bitmap bitmap) {
+ if (bitmap != null) {
+ mNewUserPhotoBitmap = bitmap;
+ mNewUserPhotoDrawable = CircleFramedDrawable
+ .getInstance(mImageView.getContext(), mNewUserPhotoBitmap);
+ mImageView.setImageDrawable(mNewUserPhotoDrawable);
+ }
+ new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete();
+ new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete();
+ }
+
+ private static int getPhotoSize(Context context) {
+ try (Cursor cursor = context.getContentResolver().query(
+ DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) {
+ if (cursor != null) {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ } else {
+ return DEFAULT_PHOTO_SIZE;
+ }
+ }
+ }
+
+ private Uri createTempImageUri(Context context, String fileName, boolean purge) {
+ final File fullPath = new File(mImagesDir, fileName);
+ if (purge) {
+ fullPath.delete();
+ }
+ return FileProvider.getUriForFile(context, mFileAuthority, fullPath);
+ }
+
+ File saveNewUserPhotoBitmap() {
+ if (mNewUserPhotoBitmap == null) {
+ return null;
+ }
+ try {
+ File file = new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME);
+ OutputStream os = new FileOutputStream(file);
+ mNewUserPhotoBitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ os.flush();
+ os.close();
+ return file;
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot create temp file", e);
+ }
+ return null;
+ }
+
+ static Bitmap loadNewUserPhotoBitmap(File file) {
+ return BitmapFactory.decodeFile(file.getAbsolutePath());
+ }
+
+ void removeNewUserPhotoBitmapFile() {
+ new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete();
+ }
+
+ private static final class RestrictedMenuItem {
+ private final Context mContext;
+ private final String mTitle;
+ private final Runnable mAction;
+ private final RestrictedLockUtils.EnforcedAdmin mAdmin;
+ // Restriction may be set by system or something else via UserManager.setUserRestriction().
+ private final boolean mIsRestrictedByBase;
+
+ /**
+ * The menu item, used for popup menu. Any element of such a menu can be disabled by admin.
+ *
+ * @param context A context.
+ * @param title The title of the menu item.
+ * @param restriction The restriction, that if is set, blocks the menu item.
+ * @param action The action on menu item click.
+ */
+ RestrictedMenuItem(Context context, String title, String restriction,
+ Runnable action) {
+ mContext = context;
+ mTitle = title;
+ mAction = action;
+
+ final int myUserId = UserHandle.myUserId();
+ mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
+ restriction, myUserId);
+ mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
+ restriction, myUserId);
+ }
+
+ @Override
+ public String toString() {
+ return mTitle;
+ }
+
+ void doAction() {
+ if (isRestrictedByBase()) {
+ return;
+ }
+
+ if (isRestrictedByAdmin()) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin);
+ return;
+ }
+
+ mAction.run();
+ }
+
+ boolean isRestrictedByAdmin() {
+ return mAdmin != null;
+ }
+
+ boolean isRestrictedByBase() {
+ return mIsRestrictedByBase;
+ }
+ }
+
+ /**
+ * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where
+ * any element can be restricted by admin (profile owner or device owner).
+ */
+ private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> {
+ RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) {
+ super(context, R.layout.restricted_popup_menu_item, R.id.text, items);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View view = super.getView(position, convertView, parent);
+ final RestrictedMenuItem item = getItem(position);
+ final TextView text = (TextView) view.findViewById(R.id.text);
+ final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon);
+
+ text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase());
+ image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase()
+ ? ImageView.VISIBLE : ImageView.GONE);
+
+ return view;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
new file mode 100644
index 000000000000..165c2808f16d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.MediaStore;
+
+/**
+ * Utility class that contains helper methods to determine if the current user has permission and
+ * the device is in a proper state to start an activity for a given action.
+ */
+public class PhotoCapabilityUtils {
+
+ /**
+ * Check if the current user can perform any activity for
+ * android.media.action.IMAGE_CAPTURE action.
+ */
+ public static boolean canTakePhoto(Context context) {
+ return context.getPackageManager().queryIntentActivities(
+ new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+ PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+ }
+
+ /**
+ * Check if the current user can perform any activity for
+ * android.intent.action.GET_CONTENT action for images.
+ * Returns false if the device is currently locked and
+ * requires a PIN, pattern or password to unlock.
+ */
+ public static boolean canChoosePhoto(Context context) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ boolean canPerformActivityForGetImage =
+ context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
+ // on locked device we can't access the images
+ return canPerformActivityForGetImage && !isDeviceLocked(context);
+ }
+
+ /**
+ * Check if the current user can perform any activity for
+ * com.android.camera.action.CROP action for images.
+ * Returns false if the device is currently locked and
+ * requires a PIN, pattern or password to unlock.
+ */
+ public static boolean canCropPhoto(Context context) {
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setType("image/*");
+ boolean canPerformActivityForCropping =
+ context.getPackageManager().queryIntentActivities(intent, 0).size() > 0;
+ // on locked device we can't start a cropping activity
+ return canPerformActivityForCropping && !isDeviceLocked(context);
+ }
+
+ private static boolean isDeviceLocked(Context context) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ return keyguardManager == null || keyguardManager.isDeviceLocked();
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java b/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java
new file mode 100644
index 000000000000..075635c87b1b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/UserCreatingDialog.java
@@ -0,0 +1,58 @@
+/*
+ * 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.settingslib.users;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+
+/**
+ * Dialog to show when a user creation is in progress.
+ */
+public class UserCreatingDialog extends AlertDialog {
+
+ public UserCreatingDialog(Context context) {
+ // hardcoding theme to be consistent with UserSwitchingDialog's theme
+ // todo replace both to adapt to the device's theme
+ super(context, com.android.internal.R.style.Theme_DeviceDefault_Light_Dialog_Alert);
+
+ inflateContent();
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+
+ WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
+ | WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ getWindow().setAttributes(attrs);
+ }
+
+ private void inflateContent() {
+ // using the same design as UserSwitchingDialog
+ setCancelable(false);
+ View view = LayoutInflater.from(getContext())
+ .inflate(R.layout.user_creation_progress_dialog, null);
+ String message = getContext().getString(R.string.creating_new_user_dialog_message);
+ view.setAccessibilityPaneTitle(message);
+ ((TextView) view.findViewById(R.id.message)).setText(message);
+ setView(view);
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index f506b7c12d81..0bde5c030eb0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -1,7 +1,9 @@
# Default reviewers for this and subdirectories.
-qal@google.com
+andychou@google.com
arcwang@google.com
-govenliu@google.com
asapperstein@google.com
+goldmanj@google.com
+qal@google.com
+wengsu@google.com
-# Emergency approvers in case the above are not available \ No newline at end of file
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index 81cf118031bf..b0a913617ba0 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -1090,7 +1090,7 @@ public class WifiTrackerTest {
// Verify second update AP is the same object as the first update AP
assertThat(passpointAccessPointsFirstUpdate.get(0))
- .isSameAs(passpointAccessPointsSecondUpdate.get(0));
+ .isSameInstanceAs(passpointAccessPointsSecondUpdate.get(0));
// Verify second update AP has the average of the first and second update RSSIs
assertThat(passpointAccessPointsSecondUpdate.get(0).getRssi())
.isEqualTo((prevRssi + newRssi) / 2);
@@ -1210,7 +1210,8 @@ public class WifiTrackerTest {
providersAndScans, cachedAccessPoints);
// Verify second update AP is the same object as the first update AP
- assertThat(osuAccessPointsFirstUpdate.get(0)).isSameAs(osuAccessPointsSecondUpdate.get(0));
+ assertThat(osuAccessPointsFirstUpdate.get(0))
+ .isSameInstanceAs(osuAccessPointsSecondUpdate.get(0));
// Verify second update AP has the average of the first and second update RSSIs
assertThat(osuAccessPointsSecondUpdate.get(0).getRssi())
.isEqualTo((prevRssi + newRssi) / 2);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
index a83d7e099e51..b392c5e5d772 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
@@ -69,7 +69,7 @@ public class IpAddressPreferenceControllerTest {
assertWithMessage("Intent filter should contain expected intents")
.that(ipAddressPreferenceController.getConnectivityIntents())
- .asList().containsAllIn(expectedIntents);
+ .asList().containsAtLeastElementsIn(expectedIntents);
}
private static class ConcreteIpAddressPreferenceController extends
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
index 40b9b13ef55f..37052673eb4d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -90,7 +90,7 @@ public class WifiMacAddressPreferenceControllerTest {
assertWithMessage("Intent filter should contain expected intents")
.that(mController.getConnectivityIntents())
- .asList().containsAllIn(expectedIntents);
+ .asList().containsAtLeastElementsIn(expectedIntents);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 176905305506..906e06e81e2b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -284,7 +284,7 @@ public class TileUtilsTest {
assertThat(outTiles).hasSize(1);
final Bundle newMetaData = outTiles.get(0).getMetaData();
- assertThat(newMetaData).isNotSameAs(oldMetadata);
+ assertThat(newMetaData).isNotSameInstanceAs(oldMetadata);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 68f162ec0226..58ca73468316 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -27,6 +27,7 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -721,4 +722,47 @@ public class InfoMediaManagerTest {
assertThat(mInfoMediaManager.mMediaDevices.size()).isEqualTo(0);
}
+
+ @Test
+ public void shouldDisableMediaOutput_infosSizeEqual1_returnsTrue() {
+ final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ final List<MediaRoute2Info> infos = new ArrayList<>();
+ infos.add(info);
+ mShadowRouter2Manager.setAvailableRoutes(infos);
+
+ when(mRouterManager.getAvailableRoutes(anyString())).thenReturn(infos);
+ when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+
+ assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
+ }
+
+ @Test
+ public void shouldDisableMediaOutput_infosSizeEqual1AndNotCastDevice_returnsFalse() {
+ final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ final List<MediaRoute2Info> infos = new ArrayList<>();
+ infos.add(info);
+ mShadowRouter2Manager.setAvailableRoutes(infos);
+
+ when(mRouterManager.getAvailableRoutes(anyString())).thenReturn(infos);
+ when(info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+
+ assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
+ }
+
+
+ @Test
+ public void shouldDisableMediaOutput_infosSizeOverThan1_returnsFalse() {
+ final MediaRoute2Info info = mock(MediaRoute2Info.class);
+ final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
+ final List<MediaRoute2Info> infos = new ArrayList<>();
+ infos.add(info);
+ infos.add(info2);
+ mShadowRouter2Manager.setAvailableRoutes(infos);
+
+ when(mRouterManager.getAvailableRoutes(anyString())).thenReturn(infos);
+ when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(info2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+
+ assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
new file mode 100644
index 000000000000..d6c8816ecc58
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.widget.EditText;
+import android.widget.ImageView;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowDialog;
+
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+public class EditUserInfoControllerTest {
+ private static final int MAX_USER_NAME_LENGTH = 100;
+
+ @Mock
+ private Drawable mCurrentIcon;
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ private boolean mCanChangePhoto;
+ private Activity mActivity;
+ private TestEditUserInfoController mController;
+
+ public class TestEditUserInfoController extends EditUserInfoController {
+ private EditUserPhotoController mPhotoController;
+
+ TestEditUserInfoController() {
+ super("file_authority");
+ }
+
+ private EditUserPhotoController getPhotoController() {
+ return mPhotoController;
+ }
+
+ @Override
+ EditUserPhotoController createEditUserPhotoController(Activity activity,
+ ActivityStarter activityStarter, ImageView userPhotoView) {
+ mPhotoController = mock(EditUserPhotoController.class, Answers.RETURNS_DEEP_STUBS);
+ return mPhotoController;
+ }
+
+ @Override
+ boolean canChangePhoto(Context context) {
+ return mCanChangePhoto;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mActivity = spy(ActivityController.of(new FragmentActivity()).get());
+ mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mController = new TestEditUserInfoController();
+ mCanChangePhoto = true;
+ }
+
+ @Test
+ public void photoControllerOnActivityResult_whenWaiting_isCalled() {
+ mController.createDialog(mActivity, mActivityStarter, mCurrentIcon, "test user",
+ "title", null, null);
+ mController.startingActivityForResult();
+ Intent resultData = new Intent();
+ mController.onActivityResult(0, 0, resultData);
+ EditUserPhotoController photoController = mController.getPhotoController();
+
+ assertThat(photoController).isNotNull();
+ verify(photoController).onActivityResult(0, 0, resultData);
+ }
+
+ @Test
+ @Config(shadows = ShadowDialog.class)
+ public void userNameView_inputLongName_shouldBeConstrained() {
+ // generate a string of 200 'A's
+ final String longName = Stream.generate(
+ () -> String.valueOf('A')).limit(200).collect(Collectors.joining());
+
+ final AlertDialog dialog = (AlertDialog) mController.createDialog(mActivity,
+ mActivityStarter, mCurrentIcon,
+ "test user", "title", null,
+ null);
+ dialog.show();
+ final EditText userNameEditText = dialog.findViewById(R.id.user_name);
+ userNameEditText.setText(longName);
+
+ assertThat(userNameEditText.getText().length()).isEqualTo(MAX_USER_NAME_LENGTH);
+ }
+
+ @Test
+ public void cancelCallback_isCalled_whenCancelled() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, mCurrentIcon, "test",
+ "title", successCallback, cancelCallback);
+ dialog.show();
+ dialog.cancel();
+
+ verifyZeroInteractions(successCallback);
+ verify(cancelCallback, times(1))
+ .run();
+ }
+
+ @Test
+ public void cancelCallback_isCalled_whenNegativeClicked() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, mCurrentIcon, "test",
+ "title", successCallback, cancelCallback);
+ dialog.show();
+ dialog.getButton(Dialog.BUTTON_NEGATIVE).performClick();
+
+ verifyZeroInteractions(successCallback);
+ verify(cancelCallback, times(1))
+ .run();
+ }
+
+ @Test
+ public void successCallback_isCalled_whenNothingChanged() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ Drawable oldUserIcon = mCurrentIcon;
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, oldUserIcon, "test",
+ "title", successCallback, cancelCallback);
+ // No change to the photo.
+ when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
+ dialog.show();
+ dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+ verify(successCallback, times(1))
+ .accept("test", oldUserIcon);
+ verifyZeroInteractions(cancelCallback);
+ }
+
+ @Test
+ public void successCallback_calledWithNullIcon_whenOldIconIsNullAndNothingChanged() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, null, "test",
+ "title", successCallback, cancelCallback);
+ // No change to the photo.
+ when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
+ dialog.show();
+ dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+ verify(successCallback, times(1))
+ .accept("test", null);
+ verifyZeroInteractions(cancelCallback);
+ }
+
+ @Test
+ public void successCallback_isCalled_whenLabelChanges() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, mCurrentIcon, "test",
+ "title", successCallback, cancelCallback);
+ // No change to the photo.
+ when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
+ dialog.show();
+ String expectedNewName = "new test user";
+ EditText editText = (EditText) dialog.findViewById(R.id.user_name);
+ editText.setText(expectedNewName);
+ dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+ verify(successCallback, times(1))
+ .accept(expectedNewName, mCurrentIcon);
+ verifyZeroInteractions(cancelCallback);
+ }
+
+ @Test
+ public void successCallback_isCalled_whenPhotoChanges() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, mCurrentIcon, "test",
+ "title", successCallback, cancelCallback);
+ // A different drawable.
+ Drawable newPhoto = mock(Drawable.class);
+ when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
+ dialog.show();
+ dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+ verify(successCallback, times(1))
+ .accept("test", newPhoto);
+ verifyZeroInteractions(cancelCallback);
+ }
+
+ @Test
+ public void successCallback_isCalledWithChangedPhoto_whenOldIconIsNullAndPhotoChanges() {
+ BiConsumer<String, Drawable> successCallback = mock(BiConsumer.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mController.createDialog(
+ mActivity, mActivityStarter, null, "test",
+ "title", successCallback, cancelCallback);
+ // A different drawable.
+ Drawable newPhoto = mock(Drawable.class);
+ when(mController.getPhotoController().getNewUserPhotoDrawable()).thenReturn(newPhoto);
+ dialog.show();
+ dialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
+
+ verify(successCallback, times(1))
+ .accept("test", newPhoto);
+ verifyZeroInteractions(cancelCallback);
+ }
+
+ @Test
+ public void createDialog_canNotChangePhoto_nullPhotoController() {
+ mCanChangePhoto = false;
+
+ mController.createDialog(mActivity, mActivityStarter, mCurrentIcon,
+ "test", "title", null, null);
+
+ assertThat(mController.mPhotoController).isNull();
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index bcd2ff71b57f..c1bdb56b9da7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -60,6 +60,9 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE,
Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR,
+ Settings.Secure.FORCE_BOLD_TEXT,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS,
Settings.Secure.TTS_DEFAULT_RATE,
Settings.Secure.TTS_DEFAULT_PITCH,
Settings.Secure.TTS_DEFAULT_SYNTH,
@@ -175,6 +178,8 @@ public class SecureSettings {
Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
Settings.Secure.PANIC_GESTURE_ENABLED,
Settings.Secure.PANIC_SOUND_ENABLED,
- Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED
+ Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED,
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 3630f257f583..388bf283625c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -29,6 +29,7 @@ import static android.provider.settings.validators.SettingsValidators.NONE_NEGAT
import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NULLABLE_COMPONENT_NAME_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.TILE_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
@@ -101,6 +102,9 @@ public class SecureSettingsValidators {
Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
new InclusiveFloatRangeValidator(0.5f, 2.0f));
VALIDATORS.put(Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FORCE_BOLD_TEXT, new DiscreteValueValidator(new String[] {"1", "2"}));
+ VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_RATE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_PITCH, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_SYNTH, PACKAGE_NAME_VALIDATOR);
@@ -264,5 +268,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.PANIC_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.PANIC_SOUND_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
+ VALIDATORS.put(Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 079c8082ae62..c20f4443edb7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -48,6 +48,8 @@ class SettingsProtoDumpUtil {
ConfigSettingsProto.ACTIVITY_MANAGER_SETTINGS);
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
ConfigSettingsProto.ACTIVITY_MANAGER_NATIVE_BOOT_SETTINGS);
+ namespaceToFieldMap.put(DeviceConfig.NAMESPACE_ALARM_MANAGER,
+ ConfigSettingsProto.ALARM_MANAGER_SETTINGS);
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_APP_COMPAT,
ConfigSettingsProto.APP_COMPAT_SETTINGS);
namespaceToFieldMap.put(DeviceConfig.NAMESPACE_AUTOFILL,
@@ -183,9 +185,6 @@ class SettingsProtoDumpUtil {
p.end(airplaneModeToken);
dumpSetting(s, p,
- Settings.Global.ALARM_MANAGER_CONSTANTS,
- GlobalSettingsProto.ALARM_MANAGER_CONSTANTS);
- dumpSetting(s, p,
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
GlobalSettingsProto.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED);
dumpSetting(s, p,
@@ -765,6 +764,9 @@ class SettingsProtoDumpUtil {
Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
dumpSetting(s, p,
+ Settings.Global.ANGLE_EGL_FEATURES,
+ GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
+ dumpSetting(s, p,
Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX,
GlobalSettingsProto.Gpu.SHOW_ANGLE_IN_USE_DIALOG);
dumpSetting(s, p,
@@ -890,9 +892,6 @@ class SettingsProtoDumpUtil {
Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
GlobalSettingsProto.Location.SETTINGS_LINK_TO_PERMISSIONS_ENABLED);
dumpSetting(s, p,
- Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
- GlobalSettingsProto.Location.GLOBAL_KILL_SWITCH);
- dumpSetting(s, p,
Settings.Global.GNSS_SATELLITE_BLACKLIST,
GlobalSettingsProto.Location.GNSS_SATELLITE_BLACKLIST);
dumpSetting(s, p,
@@ -1879,6 +1878,15 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE);
p.end(assistToken);
+ final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ SecureSettingsProto.AssistHandles.LEARNING_TIME_ELAPSED_MILLIS);
+ dumpSetting(s, p,
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ SecureSettingsProto.AssistHandles.LEARNING_EVENT_COUNT);
+ p.end(assistHandlesToken);
+
final long autofillToken = p.start(SecureSettingsProto.AUTOFILL);
dumpSetting(s, p,
Settings.Secure.AUTOFILL_SERVICE,
@@ -2287,6 +2295,18 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.Notification.IN_CALL_NOTIFICATION_ENABLED);
p.end(notificationToken);
+ final long oneHandedToken = p.start(SecureSettingsProto.ONEHANDED);
+ dumpSetting(s, p,
+ Settings.Secure.ONE_HANDED_MODE_ENABLED,
+ SecureSettingsProto.OneHanded.ONE_HANDED_MODE_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
+ SecureSettingsProto.OneHanded.ONE_HANDED_MODE_TIMEOUT);
+ dumpSetting(s, p,
+ Settings.Secure.TAPS_APP_TO_EXIT,
+ SecureSettingsProto.OneHanded.TAPS_APP_TO_EXIT);
+ p.end(oneHandedToken);
+
final long parentalControlToken = p.start(SecureSettingsProto.PARENTAL_CONTROL);
dumpSetting(s, p,
Settings.Secure.PARENTAL_CONTROL_ENABLED,
@@ -2326,6 +2346,18 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.QuickSettings.AUTO_ADDED_TILES);
p.end(qsToken);
+ final long reduceBrightColorsToken = p.start(SecureSettingsProto.REDUCE_BRIGHT_COLORS);
+ dumpSetting(s, p,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ SecureSettingsProto.ReduceBrightColors.ACTIVATED);
+ dumpSetting(s, p,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+ SecureSettingsProto.ReduceBrightColors.LEVEL);
+ dumpSetting(s, p,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS,
+ SecureSettingsProto.ReduceBrightColors.PERSIST_ACROSS_REBOOTS);
+ p.end(reduceBrightColorsToken);
+
final long rotationToken = p.start(SecureSettingsProto.ROTATION);
dumpSetting(s, p,
Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
@@ -2455,6 +2487,9 @@ class SettingsProtoDumpUtil {
p.end(soundsToken);
dumpSetting(s, p,
+ Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
+ SecureSettingsProto.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.SYNC_PARENT_SOUNDS,
SecureSettingsProto.SYNC_PARENT_SOUNDS);
dumpSetting(s, p,
@@ -2575,22 +2610,6 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.Zen.SETTINGS_SUGGESTION_VIEWED);
p.end(zenToken);
- dumpSetting(s, p,
- Settings.Secure.ONE_HANDED_MODE_ENABLED,
- SecureSettingsProto.OneHanded.ONE_HANDED_MODE_ENABLED);
-
- dumpSetting(s, p,
- Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
- SecureSettingsProto.OneHanded.ONE_HANDED_MODE_TIMEOUT);
-
- dumpSetting(s, p,
- Settings.Secure.TAPS_APP_TO_EXIT,
- SecureSettingsProto.OneHanded.TAPS_APP_TO_EXIT);
-
- dumpSetting(s, p,
- Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
- SecureSettingsProto.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
-
// Please insert new settings using the same order as in SecureSettingsProto.
p.end(token);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 710d004a1943..a51771b9500e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -328,10 +328,6 @@ public class SettingsProvider extends ContentProvider {
return SettingsState.getUserIdFromKey(key);
}
- public static String settingTypeToString(int type) {
- return SettingsState.settingTypeToString(type);
- }
-
public static String keyToString(int key) {
return SettingsState.keyToString(key);
}
@@ -373,8 +369,7 @@ public class SettingsProvider extends ContentProvider {
}
case Settings.CALL_METHOD_GET_SECURE: {
- Setting setting = getSecureSetting(name, requestingUserId,
- /*enableOverride=*/ true);
+ Setting setting = getSecureSetting(name, requestingUserId);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
@@ -581,7 +576,7 @@ public class SettingsProvider extends ContentProvider {
}
private ArrayList<String> buildSettingsList(Cursor cursor) {
- final ArrayList<String> lines = new ArrayList<String>();
+ final ArrayList<String> lines = new ArrayList<>();
try {
while (cursor != null && cursor.moveToNext()) {
lines.add(cursor.getString(1) + "=" + cursor.getString(2));
@@ -1381,10 +1376,6 @@ public class SettingsProvider extends ContentProvider {
}
private Setting getSecureSetting(String name, int requestingUserId) {
- return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
- }
-
- private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
if (DEBUG) {
Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
}
@@ -1414,14 +1405,6 @@ public class SettingsProvider extends ContentProvider {
return getSsaidSettingLocked(callingPkg, owningUserId);
}
}
- if (enableOverride) {
- if (Secure.LOCATION_MODE.equals(name)) {
- final Setting overridden = getLocationModeSetting(owningUserId);
- if (overridden != null) {
- return overridden;
- }
- }
- }
// Not the SSAID; do a straight lookup
synchronized (mLock) {
@@ -1511,35 +1494,6 @@ public class SettingsProvider extends ContentProvider {
return null;
}
- private Setting getLocationModeSetting(int owningUserId) {
- synchronized (mLock) {
- final Setting setting = getGlobalSetting(
- Global.LOCATION_GLOBAL_KILL_SWITCH);
- if (!"1".equals(setting.getValue())) {
- return null;
- }
- // Global kill-switch is enabled. Return an empty value.
- final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
- SETTINGS_TYPE_SECURE, owningUserId);
- return settingsState.new Setting(
- Secure.LOCATION_MODE,
- "", // value
- "", // tag
- "", // default value
- "", // package name
- false, // from system
- "0" // id
- ) {
- @Override
- public boolean update(String value, boolean setDefault, String packageName,
- String tag, boolean forceNonSystemPackage, boolean overrideableByRestore) {
- Slog.wtf(LOG_TAG, "update shouldn't be called on this instance.");
- return false;
- }
- };
- }
- }
-
private boolean insertSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, boolean forceNotify,
boolean overrideableByRestore) {
@@ -1808,12 +1762,8 @@ public class SettingsProvider extends ContentProvider {
private boolean hasWriteSecureSettingsPermission() {
// Write secure settings is a more protected permission. If caller has it we are good.
- if (getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
-
- return false;
+ return getContext().checkCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
}
private void validateSystemSettingValue(String name, String value) {
@@ -3174,12 +3124,6 @@ public class SettingsProvider extends ContentProvider {
if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
final long token = Binder.clearCallingIdentity();
try {
- if (Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)
- && isGlobalSettingsKey(key)) {
- // When the global kill switch is updated, send the
- // change notification for the location setting.
- notifyLocationChangeForRunningUsers();
- }
notifySettingChangeForRunningUsers(key, name);
} finally {
Binder.restoreCallingIdentity(token);
@@ -3257,26 +3201,6 @@ public class SettingsProvider extends ContentProvider {
}
}
- private void notifyLocationChangeForRunningUsers() {
- final List<UserInfo> users = mUserManager.getAliveUsers();
-
- for (int i = 0; i < users.size(); i++) {
- final int userId = users.get(i).id;
-
- if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
- continue;
- }
-
- // Increment the generation first, so observers always see the new value
- final int key = makeKey(SETTINGS_TYPE_SECURE, userId);
- mGenerationRegistry.incrementGeneration(key);
-
- final Uri uri = getNotificationUriFor(key, Secure.LOCATION_MODE);
- mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
- userId, 0, uri).sendToTarget();
- }
- }
-
private boolean isConfigSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_CONFIG;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 6678cf6f1033..b061df1423ba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -34,7 +34,6 @@ import android.os.Message;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.providers.settings.SettingsOperationProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -47,7 +46,6 @@ import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import libcore.io.IoUtils;
@@ -817,13 +815,6 @@ final class SettingsState {
for (int i = 0; i < settingCount; i++) {
Setting setting = settings.valueAt(i);
- if (setting.isTransient()) {
- if (DEBUG_PERSISTENCE) {
- Slog.i(LOG_TAG, "[SKIPPED PERSISTING]" + setting.getName());
- }
- continue;
- }
-
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
setting.getTag(), setting.isDefaultFromSystem(),
@@ -1109,8 +1100,7 @@ final class SettingsState {
ATTR_DEFAULT_VALUE_BASE64);
String isPreservedInRestoreString = parser.getAttributeValue(null,
ATTR_PRESERVE_IN_RESTORE);
- boolean isPreservedInRestore = isPreservedInRestoreString != null
- && Boolean.parseBoolean(isPreservedInRestoreString);
+ boolean isPreservedInRestore = Boolean.parseBoolean(isPreservedInRestoreString);
String tag = null;
boolean fromSystem = false;
if (defaultValue != null) {
@@ -1308,14 +1298,6 @@ final class SettingsState {
/* resetToDefault */ true);
}
- public boolean isTransient() {
- switch (getTypeFromKey(getKey())) {
- case SETTINGS_TYPE_GLOBAL:
- return ArrayUtils.contains(Global.TRANSIENT_SETTINGS, getName());
- }
- return false;
- }
-
public boolean update(String value, boolean setDefault, String packageName, String tag,
boolean forceNonSystemPackage, boolean overrideableByRestore) {
return update(value, setDefault, packageName, tag, forceNonSystemPackage,
@@ -1444,7 +1426,7 @@ final class SettingsState {
}
private static String fromBytes(byte[] bytes) {
- final StringBuffer sb = new StringBuffer(bytes.length / 2);
+ final StringBuilder sb = new StringBuilder(bytes.length / 2);
final int last = bytes.length - 1;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d8f772d7f440..69be14413583 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -117,7 +117,6 @@ public class SettingsBackupTest {
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
- Settings.Global.ALARM_MANAGER_CONSTANTS,
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
Settings.Global.ALWAYS_FINISH_ACTIVITIES,
Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
@@ -160,6 +159,7 @@ public class SettingsBackupTest {
Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
Settings.Global.BLE_SCAN_BACKGROUND_MODE,
+ Settings.Global.BLOCK_UNTRUSTED_TOUCHES_MODE,
Settings.Global.BLOCKED_SLICES,
Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
@@ -229,6 +229,7 @@ public class SettingsBackupTest {
Settings.Global.DEVELOPMENT_FORCE_RTL,
Settings.Global.DEVELOPMENT_ENABLE_SIZECOMPAT_FREEFORM,
Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
+ Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_SV,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.BATTERY_SAVER_ADAPTIVE_CONSTANTS,
Settings.Global.BATTERY_SAVER_CONSTANTS,
@@ -332,6 +333,7 @@ public class SettingsBackupTest {
Settings.Global.MAX_ERROR_BYTES_PREFIX,
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+ Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
Settings.Global.MDC_INITIAL_MAX_RETRY,
Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
Settings.Global.MHL_POWER_CHARGE_ENABLED,
@@ -437,6 +439,8 @@ public class SettingsBackupTest {
Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED,
Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+ Settings.Global.SHOW_PEOPLE_SPACE,
+ Settings.Global.SHOW_NEW_LOCKSCREEN,
Settings.Global.SHOW_RESTART_IN_CRASH_DIALOG,
Settings.Global.SHOW_TEMPERATURE_WARNING,
Settings.Global.SHOW_USB_TEMPERATURE_ALARM,
@@ -502,6 +506,7 @@ public class SettingsBackupTest {
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_VALUES,
Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST,
+ Settings.Global.ANGLE_EGL_FEATURES,
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
Settings.Global.UPDATABLE_DRIVER_PRERELEASE_OPT_IN_APPS,
@@ -741,7 +746,8 @@ public class SettingsBackupTest {
Settings.Secure.NEARBY_SHARING_COMPONENT, // not user configurable
Settings.Secure.WINDOW_MAGNIFICATION,
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
- Settings.Secure.SUPPRESS_DOZE);
+ Settings.Secure.SUPPRESS_DOZE,
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 190015cebe30..5f018a0322a3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -323,6 +323,21 @@
<!-- Permissions required for CTS test - AdbManagerTest -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permission needed for CTS test - DisplayTest -->
+ <uses-permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS" />
+
+ <!-- Permission needed for CTS test - TimeManagerTest -->
+ <uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+
+ <!-- Permission required for CTS test - android.server.biometrics -->
+ <uses-permission android:name="android.permission.USE_BIOMETRIC" />
+
+ <!-- Permission required for CTS test - android.server.biometrics -->
+ <uses-permission android:name="android.permission.TEST_BIOMETRIC" />
+
+ <!-- Permissions required for CTS test - NotificationManagerTest -->
+ <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index ce427cbe776b..0eac4add7b09 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -511,14 +511,14 @@ public class BugreportProgressService extends Service {
}
if (msg.what != MSG_SERVICE_COMMAND) {
- // Sanity check.
+ // Confidence check.
Log.e(TAG, "Invalid message type: " + msg.what);
return;
}
// At this point it's handling onStartCommand(), with the intent passed as an Extra.
if (!(msg.obj instanceof Intent)) {
- // Sanity check.
+ // Confidence check.
Log.wtf(TAG, "handleMessage(): invalid msg.obj type: " + msg.obj);
return;
}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index cd3cad1d6351..3b02e3b46557 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -466,7 +466,7 @@ public class BugreportReceiverTest {
// Clear properties
mContext.getSharedPreferences(PREFS_BUGREPORT, Context.MODE_PRIVATE)
.edit().clear().commit();
- // Sanity check...
+ // Confidence check...
assertEquals("Did not reset properties", STATE_UNKNOWN,
getWarningState(mContext, STATE_UNKNOWN));
} else {
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index 9018e660d8fc..a617733737e7 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -21,7 +21,7 @@
<string name="alarm_sound_default" msgid="4787646764557462649">"Standart signal tovushi"</string>
<string name="add_ringtone_text" msgid="6642389991738337529">"Rington qo‘shish"</string>
<string name="add_alarm_text" msgid="3545497316166999225">"Signal qo‘shish"</string>
- <string name="add_notification_text" msgid="4431129543300614788">"Bildirishnoma qo‘shish"</string>
+ <string name="add_notification_text" msgid="4431129543300614788">"Bildirishnoma kiritish"</string>
<string name="delete_ringtone_text" msgid="201443984070732499">"O‘chirish"</string>
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 05225f9a4338..588097e285df 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -591,15 +591,19 @@ public final class RingtonePickerActivity extends AlertActivity implements
}
private Uri getCurrentlySelectedRingtoneUri() {
- if (getCheckedItem() == mDefaultRingtonePos) {
- // Use the default Uri that they originally gave us.
- return mUriForDefaultItem;
- } else if (getCheckedItem() == mSilentPos) {
- // Use a null Uri for the 'Silent' item.
- return null;
- } else {
- return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
- }
+ if (getCheckedItem() == POS_UNKNOWN) {
+ // When the getCheckItem is POS_UNKNOWN, it is not the case we expected.
+ // We return null for this case.
+ return null;
+ } else if (getCheckedItem() == mDefaultRingtonePos) {
+ // Use the default Uri that they originally gave us.
+ return mUriForDefaultItem;
+ } else if (getCheckedItem() == mSilentPos) {
+ // Use a null Uri for the 'Silent' item.
+ return null;
+ } else {
+ return mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(getCheckedItem()));
+ }
}
private void saveAnyPlayingRingtone() {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 490c16d10d52..d5c84e175f6d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -519,7 +519,7 @@
<!-- started from PipController -->
<activity
- android:name=".pip.tv.PipMenuActivity"
+ android:name="com.android.wm.shell.pip.tv.PipMenuActivity"
android:permission="com.android.systemui.permission.SELF"
android:exported="false"
android:theme="@style/PipTheme"
@@ -573,6 +573,13 @@
</intent-filter>
</activity>
+ <!-- People Space UI Screen -->
+ <activity
+ android:name=".people.PeopleSpaceActivity"
+ android:exported="true"
+ android:theme="@android:style/Theme.Material.NoActionBar">
+ </activity>
+
<!-- a gallery of delicious treats -->
<service
android:name=".DessertCaseDream"
@@ -607,6 +614,14 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".user.CreateUserActivity"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:finishOnCloseSystemDialogs="true"
+ android:launchMode="singleInstance"
+ android:theme="@style/Theme.CreateUser" />
+
<activity android:name=".Somnambulator"
android:label="@string/start_dreams"
android:icon="@mipmap/ic_launcher_dreams"
@@ -777,6 +792,7 @@
android:exported="true">
<intent-filter>
<action android:name="com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG" />
+ <action android:name="com.android.systemui.action.DISMISS_MEDIA_OUTPUT_DIALOG" />
</intent-filter>
</receiver>
diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md
index 9fe2e181971a..cde5094f652e 100644
--- a/packages/SystemUI/docs/plugin_hooks.md
+++ b/packages/SystemUI/docs/plugin_hooks.md
@@ -1,33 +1,34 @@
# Plugin hooks
### Action: com.android.systemui.action.PLUGIN_OVERLAY
-Expected interface: [OverlayPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java)
+Expected interface: [OverlayPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android
+/systemui/plugins/OverlayPlugin.java)
Use: Allows plugin access to the status bar and nav bar window for whatever nefarious purposes you can imagine.
### Action: com.android.systemui.action.PLUGIN_QS
-Expected interface: [QS](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java)
+Expected interface: [QS](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java)
Use: Allows the entire QS panel to be replaced with something else that is optionally expandable.
Notes: To not mess up the notification panel interaction, much of the QSContainer interface needs to actually be implemented.
### Action: com.android.systemui.action.PLUGIN_QS_FACTORY
-Expected interface: [QSFactory](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSFactory.java)
+Expected interface: [QSFactory](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSFactory.java)
Use: Controls the creation of QS Tiles and their views, can used to add or change system QS tiles, can also be used to change the layout/interaction of the tile views.
### Action: com.android.systemui.action.PLUGIN_NAV_BUTTON
-Expected interface: [NavBarButtonProvider](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java)
+Expected interface: [NavBarButtonProvider](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java)
Use: Allows a plugin to create a new nav bar button, or override an existing one with a view of its own.
### Action: com.android.systemui.action.PLUGIN_NAV_GESTURE
-Expected interface: [NavGesture](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java)
+Expected interface: [NavGesture](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java)
Use: Allows touch events from the nav bar to be intercepted and used for other gestures.
### Action: com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON
-Expected interface: [IntentButtonProvider](/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java)
+Expected interface: [IntentButtonProvider](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/IntentButtonProvider.java)
Use: Allows a plugin to specify the icon for the bottom right lock screen button, and the intent that gets launched when it is activated.
@@ -37,28 +38,33 @@ Expected interface: [IntentButtonProvider](/packages/SystemUI/plugin/src/com/and
Use: Allows a plugin to specify the icon for the bottom left lock screen button, and the intent that gets launched when it is activated.
### Action: com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS
-Expected interface: [GlobalActions](/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java)
+Expected interface: [GlobalActions](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java)
Use: Allows the long-press power menu to be completely replaced.
### Action: com.android.systemui.action.PLUGIN_VOLUME
-Expected interface: [VolumeDialog](/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java)
+Expected interface: [VolumeDialog](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java)
Use: Allows replacement of the volume dialog.
### Action: com.android.systemui.action.PLUGIN_NOTIFICATION_SWIPE_ACTION
-Expected interface: [NotificationSwipeActionHelper](/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java)
+Expected interface: [NotificationSwipeActionHelper](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java)
Use: Control over swipes/input for notification views, can be used to control what happens when you swipe/long-press
### Action: com.android.systemui.action.PLUGIN_CLOCK
-Expected interface: [ClockPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java)
+Expected interface: [ClockPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java)
Use: Allows replacement of the keyguard main clock.
+### Action: com.android.systemui.action.PLUGIN_TOAST
+Expected interface: [ToastPlugin](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java)
+
+Use: Allows replacement of uncustomized toasts created via Toast.makeText().
+
# Global plugin dependencies
These classes can be accessed by any plugin using PluginDependency as long as they @Requires them.
-[VolumeDialogController](/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java) - Mostly just API for the volume plugin
+[VolumeDialogController](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java) - Mostly just API for the volume plugin
-[ActivityStarter](/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java) - Allows starting of intents while co-operating with keyguard unlocks.
+[ActivityStarter](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java) - Allows starting of intents while co-operating with keyguard unlocks.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
new file mode 100644
index 000000000000..0831e0ef7795
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ToastPlugin.java
@@ -0,0 +1,107 @@
+/*
+ * 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.plugins;
+
+import android.animation.Animator;
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Customize toasts displayed by SystemUI (via Toast#makeText)
+ */
+@ProvidesInterface(action = ToastPlugin.ACTION, version = ToastPlugin.VERSION)
+public interface ToastPlugin extends Plugin {
+
+ String ACTION = "com.android.systemui.action.PLUGIN_TOAST";
+ int VERSION = 1;
+
+ /**
+ * Creates a CustomPluginToast.
+ */
+ @NonNull Toast createToast(CharSequence text, String packageName, int userId);
+
+ /**
+ * Custom Toast with the ability to change toast positioning, styling and animations.
+ */
+ interface Toast {
+ /**
+ * Retrieve the Toast view's gravity.
+ * If no changes, returns null.
+ */
+ default Integer getGravity() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast view's X-offset.
+ * If no changes, returns null.
+ */
+ default Integer getXOffset() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast view's Y-offset.
+ * If no changes, returns null.
+ */
+ default Integer getYOffset() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast view's horizontal margin.
+ * If no changes, returns null.
+ */
+ default Integer getHorizontalMargin() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast view's vertical margin.
+ * If no changes, returns null.
+ */
+ default Integer getVerticalMargin() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast view to show.
+ * If no changes, returns null.
+ */
+ default View getView() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast's animate in.
+ * If no changes, returns null.
+ */
+ default Animator getInAnimation() {
+ return null;
+ }
+
+ /**
+ * Retrieve the Toast's animate out.
+ * If no changes, returns null.
+ */
+ default Animator getOutAnimation() {
+ return null;
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 24b5c23a6732..52b7fab768c5 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -100,7 +100,7 @@
<string name="kg_pin_accepted" msgid="1625501841604389716">"កូដត្រូវ​បានទទួល​យក!"</string>
<string name="keyguard_carrier_default" msgid="6359808469637388586">"គ្មាន​សេវា​ទេ។"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"ប្ដូរ​វិធី​បញ្ចូល"</string>
- <string name="airplane_mode" msgid="2528005343938497866">"មុខងារ​ពេល​ជិះ​យន្តហោះ"</string>
+ <string name="airplane_mode" msgid="2528005343938497866">"​ពេល​ជិះ​យន្តហោះ"</string>
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media.xml b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
index e43c4b471db4..6a368d503866 100644
--- a/packages/SystemUI/res/drawable-television/ic_volume_media.xml
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media.xml
@@ -16,11 +16,11 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="@dimen/tv_volume_icons_size"
+ android:height="@dimen/tv_volume_icons_size"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/tv_volume_dialog_accent"
- android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+ android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM10,8.83v6.34L7.83,13L5,13v-2h2.83L10,8.83zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77 0,-4.28 -2.99,-7.86 -7,-8.77z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
index 0f6dc9517f53..6eb944fd257a 100644
--- a/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_low.xml
@@ -16,11 +16,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="@dimen/tv_volume_icons_size"
+ android:height="@dimen/tv_volume_icons_size"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
- <path
- android:fillColor="@color/tv_volume_dialog_accent"
- android:pathData="M3,15V9H7L12,4V20L7,15H3ZM14,7.97C15.48,8.71 16.5,10.23 16.5,12C16.5,13.77 15.48,15.29 14,16.02V7.97Z"/>
+ <group android:translateX="-2">
+ <path
+ android:fillColor="@color/tv_volume_dialog_accent"
+ android:pathData="M16,7.97v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02 0,-1.77 -1.02,-3.29 -2.5,-4.03zM5,9v6h4l5,5L14,4L9,9L5,9zM12,8.83v6.34L9.83,13L7,13v-2h2.83L12,8.83z"/>
+ </group>
</vector>
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
index 4b59e13516d2..b68308972f6e 100644
--- a/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_mute.xml
@@ -16,12 +16,13 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
+ android:width="@dimen/tv_volume_icons_size"
+ android:height="@dimen/tv_volume_icons_size"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
- <path
- android:fillColor="@color/tv_volume_dialog_accent"
- android:pathData="M16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v2.21l2.45,2.45c0.03,-0.2 0.05,-0.41 0.05,-0.63zM19,12c0,0.94 -0.2,1.82 -0.54,2.64l1.51,1.51C20.63,14.91 21,13.5 21,12c0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM4.27,3L3,4.27 7.73,9L3,9v6h4l5,5v-6.73l4.25,4.25c-0.67,0.52 -1.42,0.93 -2.25,1.18v2.06c1.38,-0.31 2.63,-0.95 3.69,-1.81L19.73,21 21,19.73l-9,-9L4.27,3zM12,4L9.91,6.09 12,8.18L12,4z"/>
+ <group android:translateX="-4">
+ <path
+ android:fillColor="@color/tv_volume_dialog_accent"
+ android:pathData="M14,8.83v6.34L11.83,13H9v-2h2.83L14,8.83M16,4l-5,5H7v6h4l5,5V4z"/>
+ </group>
</vector>
-
diff --git a/packages/SystemUI/res/drawable-television/ic_volume_media_off.xml b/packages/SystemUI/res/drawable-television/ic_volume_media_off.xml
new file mode 100644
index 000000000000..7a44aa6cbcc9
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/ic_volume_media_off.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="@dimen/tv_volume_icons_size"
+ android:height="@dimen/tv_volume_icons_size"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="@color/tv_volume_dialog_accent"
+ android:pathData="M4.34,2.93L2.93,4.34 7.29,8.7 7,9L3,9v6h4l5,5v-6.59l4.18,4.18c-0.65,0.49 -1.38,0.88 -2.18,1.11v2.06c1.34,-0.3 2.57,-0.92 3.61,-1.75l2.05,2.05 1.41,-1.41L4.34,2.93zM10,15.17L7.83,13L5,13v-2h2.83l0.88,-0.88L10,11.41v3.76zM19,12c0,0.82 -0.15,1.61 -0.41,2.34l1.53,1.53c0.56,-1.17 0.88,-2.48 0.88,-3.87 0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM12,4l-1.88,1.88L12,7.76zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v1.79l2.48,2.48c0.01,-0.08 0.02,-0.16 0.02,-0.24z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_off.xml b/packages/SystemUI/res/drawable/ic_volume_media_off.xml
new file mode 100644
index 000000000000..875b7b6d1f40
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_off.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_volume_media_mute" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/people_space_tile_view_card.xml b/packages/SystemUI/res/drawable/people_space_tile_view_card.xml
new file mode 100644
index 000000000000..6b81703bd343
--- /dev/null
+++ b/packages/SystemUI/res/drawable/people_space_tile_view_card.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <padding
+ android:left="1dp"
+ android:top="1dp"
+ android:right="1dp"
+ android:bottom="1dp" />
+ <corners
+ android:bottomRightRadius="7dp"
+ android:bottomLeftRadius="7dp"
+ android:topRightRadius="7dp"
+ android:topLeftRadius="7dp" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
new file mode 100644
index 000000000000..fe76b639f15a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="@color/tv_volume_dialog_seek_bar_background" />
+ <corners android:radius="@dimen/tv_volume_seek_bar_width" />
+ </shape>
+ </item>
+ <item android:id="@android:id/progress">
+ <clip>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/tv_volume_dialog_seek_bar_fill" />
+ <corners android:radius="@dimen/tv_volume_seek_bar_width" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.xml
new file mode 100644
index 000000000000..588782ddf316
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_volume_row_seek_thumb.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+ <solid android:color="@color/tv_volume_dialog_accent" />
+ <size android:width="@dimen/tv_volume_seek_bar_thumb_diameter"
+ android:height="@dimen/tv_volume_seek_bar_thumb_diameter" />
+ <stroke android:width="@dimen/tv_volume_seek_bar_thumb_focus_ring_width"
+ android:color="@color/tv_volume_dialog_seek_thumb_focus_ring"/>
+ <item name="android:shadowColor">@color/tv_volume_dialog_seek_thumb_shadow</item>
+ <item name="android:shadowRadius">@dimen/tv_volume_seek_bar_thumb_shadow_radius</item>
+ <item name="android:shadowDy">@dimen/tv_volume_seek_bar_thumb_shadow_dy</item>
+</shape>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index d28d5664d725..4f6cb0140d09 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -40,7 +40,7 @@
android:fontFeatureSettings="tnum"
android:background="@drawable/tv_volume_dialog_circle"
android:textSize="@dimen/tv_volume_number_text_size"
- android:textColor="@color/accent_tint_color_selector"/>
+ android:textColor="@color/tv_volume_dialog_accent"/>
<TextView
android:id="@+id/volume_row_header"
android:layout_width="wrap_content"
@@ -61,6 +61,12 @@
android:layout_width="@dimen/volume_dialog_row_height"
android:layout_height="match_parent"
android:layout_gravity="center"
+ android:layoutDirection="ltr"
+ android:maxHeight="@dimen/tv_volume_seek_bar_width"
+ android:minHeight="@dimen/tv_volume_seek_bar_width"
+ android:thumb="@drawable/tv_volume_row_seek_thumb"
+ android:progressDrawable="@drawable/tv_volume_row_seek_bar"
+ android:splitTrack="false"
android:rotation="270" />
</FrameLayout>
<com.android.keyguard.AlphaOptimizedImageButton
diff --git a/packages/SystemUI/res/layout/activity_create_new_user.xml b/packages/SystemUI/res/layout/activity_create_new_user.xml
new file mode 100644
index 000000000000..ba434e6d4ac6
--- /dev/null
+++ b/packages/SystemUI/res/layout/activity_create_new_user.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:orientation="vertical">
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 0229e6e9d4dd..73beefc9da83 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -32,7 +32,7 @@
android:id="@+id/header_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingEnd="16dp"/>
+ android:paddingEnd="@dimen/media_output_dialog_header_icon_padding"/>
<LinearLayout
android:layout_width="match_parent"
@@ -70,36 +70,14 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
android:orientation="vertical">
- <View
- android:layout_width="match_parent"
- android:layout_height="12dp"/>
-
- <include
- layout="@layout/media_output_list_item"
- android:id="@+id/group_item_controller"
- android:visibility="gone"/>
-
- <View
- android:id="@+id/group_item_divider"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?android:attr/listDivider"
- android:visibility="gone"/>
-
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_result"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"/>
-
- <View
- android:id="@+id/list_bottom_padding"
- android:layout_width="match_parent"
- android:layout_height="12dp"/>
</LinearLayout>
<View
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 92d0858a1a31..ac8b7b5812bd 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -75,18 +75,6 @@
android:textSize="12sp"
android:fontFamily="roboto-regular"
android:visibility="gone"/>
- <ProgressBar
- android:id="@+id/volume_indeterminate_progress"
- style="@*android:style/Widget.Material.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="15dp"
- android:layout_marginBottom="1dp"
- android:layout_alignParentBottom="true"
- android:indeterminate="true"
- android:indeterminateOnly="true"
- android:visibility="gone"/>
<SeekBar
android:id="@+id/volume_seekbar"
android:layout_width="match_parent"
@@ -94,6 +82,17 @@
android:layout_alignParentBottom="true"/>
</RelativeLayout>
+ <ProgressBar
+ android:id="@+id/volume_indeterminate_progress"
+ style="@*android:style/Widget.Material.ProgressBar.Horizontal"
+ android:layout_width="258dp"
+ android:layout_height="18dp"
+ android:layout_marginStart="68dp"
+ android:layout_marginTop="40dp"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:visibility="gone"/>
+
<View
android:layout_width="1dp"
android:layout_height="36dp"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index fd89c0bf39dd..10ad8291636e 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -34,7 +34,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:clipChildren="false"
- android:paddingTop="11dp"
+ android:paddingTop="@dimen/notification_guts_header_top_padding"
android:clipToPadding="true">
<ImageView
android:id="@+id/conversation_icon"
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 92b3ff335cba..0a33d5e4429b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -30,10 +30,11 @@
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:layout_height="wrap_content"
android:gravity="center_vertical"
android:clipChildren="false"
- android:clipToPadding="false">
+ android:paddingTop="@dimen/notification_guts_header_top_padding"
+ android:clipToPadding="true">
<ImageView
android:id="@+id/pkg_icon"
android:layout_width="@dimen/notification_guts_conversation_icon_size"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index c353d089895c..af66f8ba4cd6 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -30,10 +30,11 @@
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
- android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:layout_height="wrap_content"
android:gravity="center_vertical"
android:clipChildren="false"
- android:clipToPadding="false">
+ android:paddingTop="@dimen/notification_guts_header_top_padding"
+ android:clipToPadding="true">
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/notification_guts_conversation_icon_size"
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
new file mode 100644
index 000000000000..67ecdaa5d7b6
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_activity.xml
@@ -0,0 +1,45 @@
+<!--
+ ~ 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.
+ -->
+<androidx.core.widget.NestedScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scroll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/holo_blue_light">
+
+ <LinearLayout
+ android:id="@+id/people_space_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="vertical"
+ android:padding="16dp"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <ImageView
+ android:id="@+id/people_space_cloud"
+ android:layout_width="67dp"
+ android:layout_height="67dp"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="fitCenter"
+ android:focusable="false"
+ android:paddingBottom="16dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:src="@drawable/cloud" />
+
+ </LinearLayout>
+</androidx.core.widget.NestedScrollView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
new file mode 100644
index 000000000000..80bb07070b31
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_tile_view.xml
@@ -0,0 +1,65 @@
+<?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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tile_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:background="@drawable/people_space_tile_view_card"
+ android:orientation="vertical"
+ android:padding="16dp"
+ android:layout_marginBottom="24dp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:elevation="4dp"
+ android:gravity="start">
+
+ <ImageView
+ android:id="@+id/tile_view_package_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="end" />
+
+ <ImageView
+ android:id="@+id/tile_view_person_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="start" />
+
+ <TextView
+ android:id="@+id/tile_view_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:paddingVertical="4dp"
+ android:textSize="22sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start" />
+
+ <TextView
+ android:id="@+id/tile_view_status"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:paddingVertical="4dp"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:layout_gravity="start" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
index f9336a540376..b62018d7cb9e 100644
--- a/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
+++ b/packages/SystemUI/res/layout/tv_audio_recording_indicator.xml
@@ -22,80 +22,17 @@
android:padding="12dp">
<FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
+ android:layout_width="34dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:background="@drawable/tv_rect_shadow_rounded">
- <LinearLayout
- android:id="@+id/icon_texts_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent">
-
- <View
- android:id="@+id/icon_container_bg"
- android:layout_width="50dp"
- android:layout_height="match_parent"
- android:background="@drawable/tv_rect_dark_left_rounded"/>
-
- <FrameLayout
- android:id="@+id/icon_mic"
- android:layout_width="34dp"
- android:layout_height="24dp"
- android:layout_gravity="center"
- android:background="@drawable/tv_rect_shadow_rounded">
-
- <ImageView
- android:layout_width="13dp"
- android:layout_height="13dp"
- android:layout_gravity="center"
- android:background="@drawable/tv_ic_mic_white"/>
- </FrameLayout>
-
- </FrameLayout>
-
- <LinearLayout
- android:id="@+id/texts_container"
- android:layout_width="wrap_content"
- android:layout_height="47dp"
- android:background="@color/tv_audio_recording_indicator_background"
- android:orientation="vertical"
- android:visibility="visible">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="14dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="1dp"
- android:text="@string/mic_active"
- android:textColor="@android:color/white"
- android:fontFamily="sans-serif"
- android:textSize="10dp"/>
-
- <TextView
- android:id="@+id/text"
- android:layout_width="wrap_content"
- android:layout_height="14dp"
- android:singleLine="true"
- android:text="SomeApplication accessed your microphone"
- android:textColor="@android:color/white"
- android:fontFamily="sans-serif"
- android:textSize="8dp"/>
-
- </LinearLayout>
-
- </LinearLayout>
+ <ImageView
+ android:layout_width="13dp"
+ android:layout_height="13dp"
+ android:layout_gravity="center"
+ android:src="@drawable/tv_ic_mic_white"/>
</FrameLayout>
- <View
- android:id="@+id/bg_end"
- android:layout_width="12dp"
- android:layout_height="47dp"
- android:background="@drawable/tv_rect_dark_right_rounded"
- android:visibility="visible"/>
-
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 6ced97836358..efd24c7d9d4f 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -17,24 +17,26 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
+ android:layout_height="wrap_content"
+ android:screenReaderFocusable="true">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_outer_border_margin"
+ android:importantForAccessibility="no"
android:background="@android:color/black"/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_inner_border_margin"
+ android:importantForAccessibility="no"
android:background="@color/magnification_border_color"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
+ android:importantForAccessibility="noHideDescendants">
<View
android:id="@+id/left_handle"
@@ -76,6 +78,7 @@
android:layout_margin="@dimen/magnification_outer_border_margin"
android:layout_gravity="right|bottom"
android:scaleType="center"
+ android:importantForAccessibility="no"
android:src="@drawable/ic_move_magnification"/>
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 556c39091cce..b50be52c3acc 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Wys laeprioriteit-kennisgewingikone"</string>
<string name="other" msgid="429768510980739978">"Ander"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"verwyder teël"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"voeg teël aan einde by"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Skuif teël"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Voeg teël by"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Skuif na <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Voeg by posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kitsinstellingswysiger."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-kennisgewing: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Maak instellings oop."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Instellings"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Vergrotingvenster"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Vergrotingvensterkontroles"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoem in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoem uit"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Skuif op"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Skuif af"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Beweeg links"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Beweeg regs"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrotingwisselaar"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Vergroot die hele skerm"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Vergroot \'n deel van die skerm"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Wissel"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Toestelkontroles"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Voeg kontroles vir jou gekoppelde toestelle by"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Stel toestelkontroles op"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ontkoppel)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kon nie koppel nie. Probeer weer."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bind nuwe toestel saam"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Bounommer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Bounommer is na knipbord gekopieer."</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index c8462976528c..9e8c8acc8d66 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"አነስተኛ ቅድሚያ ያላቸው የማሳወቂያ አዶዎችን አሳይ"</string>
<string name="other" msgid="429768510980739978">"ሌላ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ሰቅ አስወግድ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ሰቅ መጨረሻው ላይ አክል"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ሰቁን ውሰድ"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ሰቅ ያክሉ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ውሰድ"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ወደ <xliff:g id="POSITION">%1$d</xliff:g> ቦታ አክል"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"የ<xliff:g id="POSITION">%1$d</xliff:g> አቀማመጥ"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"የፈጣን ቅንብሮች አርታዒ።"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"የ<xliff:g id="ID_1">%1$s</xliff:g> ማሳወቂያ፦ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ቅንብሮችን ክፈት።"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ቅንብሮች"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"የማጉያ መስኮት"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"የማጉያ መስኮት መቆጣጠሪያዎች"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"አጉላ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"አሳንስ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ወደ ላይ ውሰድ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ወደ ታች ውሰድ"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ወደ ግራ ውሰድ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ወደ ቀኝ ውሰድ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"የማጉላት ማብሪያ/ማጥፊያ"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ሙሉውን ማያ ገጽ አጉላ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"የማያ ገጹን ክፍል አጉላ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ማብሪያ/ማጥፊያ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"የመሣሪያ መቆጣጠሪያዎች"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ለእርስዎ የተገናኙ መሣሪያዎች መቆጣጠሪያዎችን ያክሉ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"የመሣሪያ መቆጣጠሪያዎችን ያቀናብሩ"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ግንኙነት ተቋርጧል)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ማገናኘት አልተቻለም። እንደገና ይሞክሩ።"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"አዲስ መሣሪያ ያጣምሩ"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"የግንብ ቁጥር"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"የገንባ ቁጥር ወደ ቅንጥብ ሰሌዳ ተቀድቷል።"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 785f8feb551a..caae9fbdb5b6 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -899,20 +899,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"إظهار رموز الإشعارات ذات الأولوية المنخفضة"</string>
<string name="other" msgid="429768510980739978">"غير ذلك"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"إزالة بطاقة"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"إضافة بطاقة إلى نهاية الإعدادات السريعة"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"نقل بطاقة"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"إضافة بطاقة"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"الانتقال إلى <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"الإضافة إلى الموضع <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"الموضع: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"برنامج تعديل الإعدادات السريعة."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"إشعار <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"فتح الإعدادات."</string>
@@ -1039,6 +1032,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"الإعدادات"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"نافذة التكبير"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"عناصر التحكم في نافذة التكبير"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"تكبير"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"تصغير"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"نقل للأعلى"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"نقل للأسفل"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"نقل لليسار"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"نقل لليمين"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"مفتاح تبديل وضع التكبير"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"تكبير الشاشة بالكامل"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"تكبير جزء من الشاشة"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"تبديل"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"أدوات التحكم بالأجهزة"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"إضافة عناصر تحكّم لأجهزتك المتصلة"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"إعداد أدوات التحكم بالجهاز"</string>
@@ -1056,7 +1059,7 @@
<string name="accessibility_control_favorite" msgid="8694362691985545985">"تمت الإضافة إلى المفضّلة"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"تمت الإضافة إلى المفضّلة، الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"تمت الإزالة من المفضّلة"</string>
- <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"إضافة إلى المُفضلة"</string>
+ <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"إضافة إلى المحتوى المفضّل"</string>
<string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"إزالة من المفضّلة"</string>
<string name="accessibility_control_move" msgid="8980344493796647792">"نقل إلى الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="controls_favorite_default_title" msgid="967742178688938137">"عناصر التحكّم"</string>
@@ -1106,4 +1109,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غير متّصل)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"تعذّر الاتصال. يُرجى إعادة المحاولة."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"إقران جهاز جديد"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"رقم الإصدار"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"تم نسخ رقم الإصدار إلى الحافظة."</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 059c4bca8181..f5f109e7d0b7 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম গুৰুত্বপূৰ্ণ জাননীৰ আইকনসমূহ দেখুৱাওক"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল আঁতৰাবলৈ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"টাইল শেষত যোগ দিবলৈ"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল স্থানান্তৰ কৰক"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"টাইল যোগ দিয়ক"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰলৈ স্থানান্তৰ কৰক"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থানত যোগ দিয়ক"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> নম্বৰ স্থান"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ক্ষিপ্ৰ ছেটিংসমূহৰ সম্পাদক।"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> জাননী: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ছেটিংসমূহ খোলক।"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ছেটিংসমূহ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"বিবৰ্ধন ৱিণ্ড’"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"বিবৰ্ধন ৱিণ্ড’ৰ নিয়ন্ত্ৰণসমূহ"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"জুম ইন কৰক"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"জুম আউট কৰক"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ওপৰলৈ নিয়ক"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"তললৈ নিয়ক"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"বাওঁফাললৈ নিয়ক"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"সোঁফাললৈ নিয়ক"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"বিবৰ্ধনৰ ছুইচ"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"গোটেই স্ক্ৰীনখন বিবৰ্ধন কৰক"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"স্ক্ৰীনৰ কিছু অংশ বিবৰ্ধন কৰক"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ছুইচ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"আপোনাৰ সংযোজিত ডিভাইচসমূহৰ বাবে নিয়ন্ত্ৰণসমূহ যোগ কৰক"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ ছেট আপ কৰক"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (সংযোগ বিচ্ছিন্ন হৈছে)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"সংযোগ কৰিব পৰা নগ’ল। পুনৰ চেষ্টা কৰক।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইচ পেয়াৰ কৰক"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ডৰ নম্বৰ"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ক্লিপব’ৰ্ডলৈ বিল্ডৰ নম্বৰ প্ৰতিলিপি কৰা হ’ল।"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 2381d3330730..89149f0b5bbb 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -797,7 +797,7 @@
<string name="keyboard_key_page_up" msgid="173914303254199845">"Yuxarı Səhifə"</string>
<string name="keyboard_key_page_down" msgid="9035902490071829731">"Aşağı Səhifə"</string>
<string name="keyboard_key_forward_del" msgid="5325501825762733459">"Silin"</string>
- <string name="keyboard_key_move_home" msgid="3496502501803911971">"Əsas səhifə"</string>
+ <string name="keyboard_key_move_home" msgid="3496502501803911971">"Home"</string>
<string name="keyboard_key_move_end" msgid="99190401463834854">"Son"</string>
<string name="keyboard_key_insert" msgid="4621692715704410493">"Daxil edin"</string>
<string name="keyboard_key_num_lock" msgid="7209960042043090548">"Nömrələr"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Aşağı prioritet bildiriş işarələrini göstərin"</string>
<string name="other" msgid="429768510980739978">"Digər"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"lövhəni silin"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"sona lövhə əlavə edin"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Lövhəni köçürün"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lövhə əlavə edin"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə köçürün"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə əlavə edin"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sürətli ayarlar redaktoru."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> bildiriş: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ayarları açın."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ayarlar"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Böyütmə Pəncərəsi"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Böyütmə Pəncərəsi Kontrolları"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Yaxınlaşdırın"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Uzaqlaşdırın"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Yuxarı köçürün"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Aşağı köçürün"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sola köçürün"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sağa köçürün"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Böyütmə dəyişdiricisi"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Bütün ekranı böyüdün"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir hissəsini böyüdün"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Dəyişdirici"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Cihaz idarəetmələri"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Qoşulmuş cihazlarınız üçün nizamlayıcılar əlavə edin"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Cihaz idarəetmələrini ayarlayın"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlantı kəsilib)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Qoşulmaq alınmadı. Yenə cəhd edin."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yeni cihazı qoşalaşdırın"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versiya nömrəsi"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Versiya nömrəsi mübadilə buferinə kopyalandı."</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 55abfda98bc8..b3b722c9c05a 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obaveštenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Drugo"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"uklonili pločicu"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"dodali pločicu na kraj"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Premestite pločicu"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajte pločicu"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premestite na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajte na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač za Brza podešavanja."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Obaveštenja za <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otvori Podešavanja."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Podešavanja"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Prozor za uvećanje"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za uvećanje"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Uvećajte"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Umanjite"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Pomerite nagore"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pomerite nadole"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pomerite nalevo"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pomerite nadesno"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prelazak na drugi režim uvećanja"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Uvećajte ceo ekran"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećajte deo ekrana"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pređi"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrole za povezane uređaje"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Podesite kontrole uređaja"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspelo. Probajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Upari novi uređaj"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u privremenu memoriju."</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 47f9861f640b..f68b69cdc845 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Паказваць значкі апавяшчэнняў з нізкім прыярытэтам"</string>
<string name="other" msgid="429768510980739978">"Іншае"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"выдаліць плітку"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"дадаць плітку ў канец"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перамясціць плітку"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Дадаць плітку"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перамясціць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Рэдактар хуткіх налад."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Апавяшчэнне <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Адкрыць налады."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Налады"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Акно павелічэння"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Налады акна павелічэння"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Павялічыць маштаб"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Паменшыць маштаб"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Перамясціць уверх"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Перамясціць ніжэй"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Перамясціць улева"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Перамясціць управа"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Пераключальнік павелічэння"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Павялічыць на ўвесь экран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Павялічыць частку экрана"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пераключальнік"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Элементы кіравання прыладай"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Дадайце элементы кіравання для падключаных прылад"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Наладзіць элементы кіравання прыладай"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (адключана)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не ўдалося падключыцца. Паўтарыце спробу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спалучыць з новай прыладай"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Нумар зборкі"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Нумар зборкі скапіраваны ў буфер абмену."</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index efe12673fe78..7a4b957e5dda 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показване на иконите за известията с нисък приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"премахване на панел"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"добавяне на панел в края"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Преместване на панел"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавяне на панел"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместване към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавяне към позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор за бързи настройки."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Известие от <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Отваряне на настройките."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Настройки"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Прозорец за ниво на мащаба"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Контроли за прозореца за ниво на мащаба"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Увеличаване на мащаба"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Намаляване на мащаба"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Преместване нагоре"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Преместване надолу"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Преместване наляво"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Преместване надясно"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Превключване на увеличението"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увеличаване на целия екран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличаване на част от екрана"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Превключване"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроли за устройството"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Добавяне на контроли за свързаните ви устройства"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Настройване на контролите за устройството"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (връзката е прекратена)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Неуспешно свързване. Опитайте отново."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Сдвояване на ново устройство"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер на компилацията"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Номерът на компилацията е копиран в буферната памет."</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index fcdd286b6f55..0b5d65e3e22b 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"কম-গুরুত্বপূর্ণ বিজ্ঞপ্তির আইকন দেখুন"</string>
<string name="other" msgid="429768510980739978">"অন্যান্য"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"টাইল সরান"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"শেষে টাইল যোগ করুন"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"টাইল সরান"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"টাইল যোগ করুন"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>-এ সরান"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>-এ যোগ করুন"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"অবস্থান <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"দ্রুত সেটিংস সম্পাদক৷"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> বিজ্ঞপ্তি: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"সেটিংস খুলুন।"</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"সেটিংস"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"উইন্ডো বড় করে দেখা"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"উইন্ডো কন্ট্রোল বড় করে দেখা"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"বড় করুন"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ছোট করুন"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"উপরে তুলুন"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"নিচে নামান"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"বাঁদিকে সরান"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ডানদিকে সরান"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইস কন্ট্রোল"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"আপনার কানেক্ট করা ডিভাইসের জন্য কন্ট্রোল যোগ করুন"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ডিভাইস কন্ট্রোল সেট-আপ করুন"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (কানেক্ট করা নেই)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"কানেক্ট করা যায়নি। আবার চেষ্টা করুন।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"নতুন ডিভাইস পেয়ার করুন"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"বিল্ড নম্বর"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"বিল্ড নম্বর ক্লিপবোর্ডে কপি করা হয়েছে।"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 811a971ab02b..1d67443e4423 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -726,7 +726,7 @@
<string name="bubble_overflow_empty_title" msgid="3120029421991510842">"Nema nedavnih oblačića"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2030874469510497397">"Nedavni i odbačeni oblačići će se pojaviti ovdje"</string>
<string name="notification_unblockable_desc" msgid="2073030886006190804">"Ta obavještenja se ne mogu izmijeniti."</string>
- <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ovdje nije moguće konfigurirati ovu grupu obavještenja"</string>
+ <string name="notification_multichannel_desc" msgid="7414593090056236179">"Ovu grupu obavještenja nije moguće konfigurirati ovdje"</string>
<string name="notification_delegate_header" msgid="1264510071031479920">"Obavještenje preko proksi servera"</string>
<string name="notification_channel_dialog_title" msgid="6856514143093200019">"Sva obavještenja aplikacije <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="see_more_title" msgid="7409317011708185729">"Prikaži više"</string>
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavještenja niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"uklanjanje kartice"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"dodavanje kartice na kraj"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pomjeranje kartice"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pomjeranje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivanje brzih postavki"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> obavještenje: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otvori postavke."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Postavke"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Prozor za uvećavanje"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za uvećavanje"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Uvećavanje"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Umanjivanje"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Pomjeranje prema gore"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pomjeranje prema dolje"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pomjeranje lijevo"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pomjeranje desno"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prekidač za uvećavanje"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Uvećavanje cijelog ekrana"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Uvećavanje dijela ekrana"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prekidač"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrole za povezane uređaje"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Postavite kontrole uređaja"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (veza je prekinuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije uspjelo. Pokušajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uparite novi uređaj"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj verzije"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Broj verzije je kopiran u međumemoriju."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 793c44b4544b..5e06f5569bd6 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -92,10 +92,10 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processant gravació de pantalla"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Vols iniciar la gravació?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"Quan graves contingut, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou les contrasenyes, la informació de pagament, les fotos, els missatges i l\'àudio."</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"Durant la gravació, el sistema Android pot capturar qualsevol informació sensible que es mostri a la pantalla o que es reprodueixi al dispositiu. Això inclou contrasenyes, informació de pagament, fotos, missatges i àudio."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Grava l\'àudio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Àudio del dispositiu"</string>
- <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sons del dispositiu, com ara la música, les trucades i els sons de trucada"</string>
+ <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"So del dispositiu, com ara música, trucades i sons de trucada"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"Micròfon"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Àudio del dispositiu i micròfon"</string>
<string name="screenrecord_start" msgid="330991441575775004">"Inicia"</string>
@@ -387,7 +387,7 @@
<string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="2325362583903258677">"AUTOMÀTICA"</string>
- <string name="quick_settings_inversion_label" msgid="5078769633069667698">"Inverteix els colors"</string>
+ <string name="quick_settings_inversion_label" msgid="5078769633069667698">"Inverteix colors"</string>
<string name="quick_settings_color_space_label" msgid="537528291083575559">"Mode de correcció de color"</string>
<string name="quick_settings_more_settings" msgid="2878235926753776694">"Més opcions"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostra les icones de notificació amb prioritat baixa"</string>
<string name="other" msgid="429768510980739978">"Altres"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"suprimir el mosaic"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"afegir un mosaic al final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mou el mosaic"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Afegeix un mosaic"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mou a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuració ràpida."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificació de <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Obre la configuració."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Configuració"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Finestra d\'ampliació"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Finestra de controls d\'ampliació"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Amplia"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Redueix"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mou cap amunt"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mou cap avall"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mou cap a l\'esquerra"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mou cap a la dreta"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Canvia al mode d\'ampliació"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Amplia tota la pantalla"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Amplia una part de la pantalla"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Canvia"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Afegeix controls per als teus dispositius connectats"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configura els controls de dispositius"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconnectat)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No s\'ha pogut connectar. Torna-ho a provar."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincula un dispositiu nou"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilació"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"El número de compilació s\'ha copiat al porta-retalls."</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 3f3d6be63f9c..1b03762f8885 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobrazit ikony oznámení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Jiné"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranit dlaždici"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"přidat dlaždici na konec"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Přesunout dlaždici"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Přidat dlaždici"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Přesunout na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rychlého nastavení"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Oznámení aplikace <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otevřít nastavení."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Nastavení"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Zvětšovací okno"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Ovládací prvky zvětšovacího okna"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Přiblížit"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Oddálit"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Přesunout nahoru"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Přesunout dolů"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Přesunout doleva"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Přesunout doprava"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Přepínač zvětšení"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Zvětšit celou obrazovku"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zvětšit část obrazovky"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Přepnout"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ovládání zařízení"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Přidejte ovládací prvky pro připojená zařízení"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavení ovládání zařízení"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojeno)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Spojení se nezdařilo. Zkuste to znovu."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovat nové zařízení"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo sestavení"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Číslo sestavení bylo zkopírováno do schránky."</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 1d9238932283..fce888e2d089 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for notifikationer med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Andet"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjern kortet"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"føj kortet til slutningen"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flyt kortet"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tilføj et kort"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flyt til <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Føj til placering <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Placering <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsværktøj til Kvikmenu."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-notifikation: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Åbn Indstillinger."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Indstillinger"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Vindue med forstørrelse"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Vindue med forstørrelsesstyring"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom ind"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom ud"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Flyt op"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flyt ned"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flyt til venstre"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flyt til højre"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Skift forstørrelsestilstand"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Forstør hele skærmen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstør en del af skærmen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Skift"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhedsstyring"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Tilføj styring af dine tilsluttede enheder"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurer enhedsstyring"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ingen forbindelse)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Der kunne ikke oprettes forbindelse. Prøv igen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Par ny enhed"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildnummer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Buildnummeret blev kopieret til udklipsholderen."</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 5976a1c95362..a6d2dfd21e4d 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -28,15 +28,15 @@
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> verbleibend"</string>
<string name="battery_low_percent_format_hybrid" msgid="3985614339605686167">"Noch <xliff:g id="PERCENTAGE">%1$s</xliff:g> übrig; bei deinem Nutzungsmuster hast du noch ca. <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="battery_low_percent_format_hybrid_short" msgid="5917433188456218857">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> ausstehend; noch ca. <xliff:g id="TIME">%2$s</xliff:g>"</string>
- <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"Noch <xliff:g id="PERCENTAGE">%s</xliff:g>. Der Energiesparmodus ist aktiviert."</string>
+ <string name="battery_low_percent_format_saver_started" msgid="4968468824040940688">"Noch <xliff:g id="PERCENTAGE">%s</xliff:g>. Der Stromsparmodus ist aktiviert."</string>
<string name="invalid_charger" msgid="4370074072117767416">"Aufladen über USB nicht möglich. Verwende das mit dem Gerät gelieferte Ladegerät."</string>
<string name="invalid_charger_title" msgid="938685362320735167">"Aufladen über USB nicht möglich"</string>
<string name="invalid_charger_text" msgid="2339310107232691577">"Verwende das mit dem Gerät gelieferte Ladegerät"</string>
<string name="battery_low_why" msgid="2056750982959359863">"Einstellungen"</string>
- <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Energiesparmodus aktivieren?"</string>
- <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Über den Energiesparmodus"</string>
+ <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Stromsparmodus aktivieren?"</string>
+ <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"Über den Stromsparmodus"</string>
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivieren"</string>
- <string name="battery_saver_start_action" msgid="4553256017945469937">"Energiesparmodus aktivieren"</string>
+ <string name="battery_saver_start_action" msgid="4553256017945469937">"Stromsparmodus aktivieren"</string>
<string name="status_bar_settings_settings_button" msgid="534331565185171556">"Einstellungen"</string>
<string name="status_bar_settings_wifi_button" msgid="7243072479837270946">"WLAN"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Bildschirm automatisch drehen"</string>
@@ -419,7 +419,7 @@
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"An um <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"Bis <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"Dunkles Design"</string>
- <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Energiesparmodus"</string>
+ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Stromsparmodus"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"An bei Sonnenuntergang"</string>
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Bis Sonnenaufgang"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"An um <xliff:g id="TIME">%s</xliff:g>"</string>
@@ -497,9 +497,9 @@
<string name="user_remove_user_title" msgid="9124124694835811874">"Nutzer entfernen?"</string>
<string name="user_remove_user_message" msgid="6702834122128031833">"Alle Apps und Daten dieses Nutzers werden gelöscht."</string>
<string name="user_remove_user_remove" msgid="8387386066949061256">"Entfernen"</string>
- <string name="battery_saver_notification_title" msgid="8419266546034372562">"Energiesparmodus ist aktiviert"</string>
+ <string name="battery_saver_notification_title" msgid="8419266546034372562">"Stromsparmodus ist aktiviert"</string>
<string name="battery_saver_notification_text" msgid="2617841636449016951">"Reduzierung der Leistung und Hintergrunddaten"</string>
- <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Energiesparmodus deaktivieren"</string>
+ <string name="battery_saver_notification_action_text" msgid="6022091913807026887">"Stromsparmodus deaktivieren"</string>
<string name="media_projection_dialog_text" msgid="1755705274910034772">"Die App \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise angezeigte Passwörter und Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
<string name="media_projection_dialog_service_text" msgid="958000992162214611">"Der Anbieter dieser App erhält Zugriff auf alle Informationen, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden, während du aufnimmst oder streamst. Dazu gehören beispielsweise Passwörter, Zahlungsdetails, Fotos, Nachrichten und Audioinhalte."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Aufnahme oder Stream starten?"</string>
@@ -773,8 +773,8 @@
<item quantity="one">%d Minute</item>
</plurals>
<string name="battery_panel_title" msgid="5931157246673665963">"Akkunutzung"</string>
- <string name="battery_detail_charging_summary" msgid="8821202155297559706">"Der Energiesparmodus ist beim Aufladen nicht verfügbar."</string>
- <string name="battery_detail_switch_title" msgid="6940976502957380405">"Energiesparmodus"</string>
+ <string name="battery_detail_charging_summary" msgid="8821202155297559706">"Der Stromsparmodus ist beim Aufladen nicht verfügbar."</string>
+ <string name="battery_detail_switch_title" msgid="6940976502957380405">"Stromsparmodus"</string>
<string name="battery_detail_switch_summary" msgid="3668748557848025990">"Reduzierung der Leistung und Hintergrunddaten"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"Taste <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Pos1"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Symbole für Benachrichtigungen mit einer niedrigen Priorität anzeigen"</string>
<string name="other" msgid="429768510980739978">"Sonstiges"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"die Kachel zu entfernen"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"die Kachel am Ende hinzuzufügen"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Kachel verschieben"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Kachel hinzufügen"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Auf Position <xliff:g id="POSITION">%1$d</xliff:g> verschieben"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Zur Position <xliff:g id="POSITION">%1$d</xliff:g> hinzufügen"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor für Schnelleinstellungen."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Benachrichtigung von <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Einstellungen öffnen."</string>
@@ -968,11 +961,11 @@
<string name="slice_permission_checkbox" msgid="4242888137592298523">"<xliff:g id="APP">%1$s</xliff:g> darf Teile aus jeder beliebigen App anzeigen"</string>
<string name="slice_permission_allow" msgid="6340449521277951123">"Zulassen"</string>
<string name="slice_permission_deny" msgid="6870256451658176895">"Ablehnen"</string>
- <string name="auto_saver_title" msgid="6873691178754086596">"Tippen zum Planen des Energiesparmodus"</string>
+ <string name="auto_saver_title" msgid="6873691178754086596">"Tippen zum Planen des Stromsparmodus"</string>
<string name="auto_saver_text" msgid="3214960308353838764">"Aktivieren, wenn der Akku wahrscheinlich nicht mehr lange hält"</string>
<string name="no_auto_saver_action" msgid="7467924389609773835">"Nein danke"</string>
- <string name="auto_saver_enabled_title" msgid="4294726198280286333">"Geplanter Energiesparmodus aktiviert"</string>
- <string name="auto_saver_enabled_text" msgid="7889491183116752719">"Der Energiesparmodus wird bei einem Akkustand von <xliff:g id="PERCENTAGE">%d</xliff:g> %% automatisch aktiviert."</string>
+ <string name="auto_saver_enabled_title" msgid="4294726198280286333">"Geplanter Stromsparmodus aktiviert"</string>
+ <string name="auto_saver_enabled_text" msgid="7889491183116752719">"Der Stromsparmodus wird bei einem Akkustand von <xliff:g id="PERCENTAGE">%d</xliff:g> %% automatisch aktiviert."</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"Einstellungen"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"Ok"</string>
<string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Einstellungen"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Vergrößerungsfenster"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Einstellungen für Vergrößerungsfenster"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Heranzoomen"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Herauszoomen"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Nach oben bewegen"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Nach unten bewegen"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Nach links bewegen"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Nach rechts bewegen"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrößerungsschalter"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ganzen Bildschirm vergrößern"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Teil des Bildschirms vergrößern"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schalter"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Gerätesteuerung"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Steuerelemente für verbundene Geräte hinzufügen"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Gerätesteuerung einrichten"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nicht verbunden)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Verbindung nicht möglich. Versuch es noch einmal."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Neues Gerät koppeln"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-Nummer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build-Nummer in Zwischenablage kopiert."</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 1eae5d4a12f9..fc4e78b17ddd 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Εμφάνιση εικονιδίων ειδοποιήσεων χαμηλής προτεραιότητας"</string>
<string name="other" msgid="429768510980739978">"Άλλο"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"κατάργηση πλακιδίου"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"προσθήκη πλακιδίου στο τέλος"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Μετακίνηση πλακιδίου"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Προσθήκη πλακιδίου"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Μετακίνηση στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Προσθήκη στη θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Θέση <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Επεξεργασία γρήγορων ρυθμίσεων."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Ειδοποίηση <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Άνοιγμα ρυθμίσεων."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ρυθμίσεις"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Παράθυρο μεγέθυνσης"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Στοιχεία ελέγχου παραθύρου μεγέθυνσης"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Μεγέθυνση"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Σμίκρυνση"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Μετακίνηση επάνω"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Μετακίνηση κάτω"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Μετακίνηση αριστερά"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Μετακίνηση δεξιά"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Εναλλαγή μεγιστοποίησης"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Μεγέθυνση ολόκληρης της οθόνης"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Μεγέθυνση μέρους της οθόνης"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Εναλλαγή"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Στοιχεία ελέγχου συσκευής"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Προσθήκη στοιχείων ελέγχου για τις συνδεδεμένες συσκευές σας."</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Ρύθμιση στοιχείων ελέγχου συσκευής"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (αποσυνδέθηκε)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Δεν ήταν δυνατή η σύνδεση. Δοκιμάστε ξανά."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Σύζευξη νέας συσκευής"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Αριθμός έκδοσης"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Ο αριθμός έκδοσης αντιγράφηκε στο πρόχειρο."</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4ea0341f8c39..709a506304e3 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -92,7 +92,7 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -1012,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Settings"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Move down"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Move left"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Move right"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Magnification switch"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Magnify entire screen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Add controls for your connected devices"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Set up device controls"</string>
@@ -1075,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 3916e58ba076..79a28442ca18 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -92,7 +92,7 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -1012,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Settings"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Move down"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Move left"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Move right"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Magnification switch"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Magnify entire screen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Add controls for your connected devices"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Set up device controls"</string>
@@ -1075,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4ea0341f8c39..709a506304e3 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -92,7 +92,7 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -1012,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Settings"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Move down"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Move left"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Move right"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Magnification switch"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Magnify entire screen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Add controls for your connected devices"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Set up device controls"</string>
@@ -1075,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4ea0341f8c39..709a506304e3 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -92,7 +92,7 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
<string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
<string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
@@ -1012,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Settings"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Move down"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Move left"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Move right"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Magnification switch"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Magnify entire screen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Add controls for your connected devices"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Set up device controls"</string>
@@ -1075,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Couldn\'t connect. Try again."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Pair new device"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build number"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 7702337f080f..ab6699b87aed 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1012,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎Settings‎‏‎‎‏‎"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‎‏‎Magnification Window‎‏‎‎‏‎"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎Magnification Window Controls‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎Zoom in‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‎Zoom out‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎Move up‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎Move down‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎Move left‎‏‎‎‏‎"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎Move right‎‏‎‎‏‎"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎Magnification switch‎‏‎‎‏‎"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎Magnify entire screen‎‏‎‎‏‎"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎Magnify part of screen‎‏‎‎‏‎"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎Switch‎‏‎‎‏‎"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎Device controls‎‏‎‎‏‎"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎Add controls for your connected devices‎‏‎‎‏‎"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎Set up device controls‎‏‎‎‏‎"</string>
@@ -1075,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ (disconnected)‎‏‎‎‏‎"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎Couldn\'t connect. Try again.‎‏‎‎‏‎"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎Pair new device‎‏‎‎‏‎"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎Build number‎‏‎‎‏‎"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎Build number copied to clipboard.‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b1ee41f4c53c..a1e21a10d67d 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -246,7 +246,7 @@
<string name="accessibility_remove_notification" msgid="1641455251495815527">"Eliminar notificación"</string>
<string name="accessibility_gps_enabled" msgid="4061313248217660858">"GPS habilitado"</string>
<string name="accessibility_gps_acquiring" msgid="896207402196024040">"Adquisición de GPS"</string>
- <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter habilitado"</string>
+ <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
<string name="accessibility_ringer_silent" msgid="8994620163934249882">"Timbre en silencio"</string>
<!-- no translation found for accessibility_casting (8708751252897282313) -->
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar íconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar tarjeta"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"agregar tarjeta al final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover la tarjeta"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Agregar tarjeta"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Agregar a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de Configuración rápida"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificación de <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir Configuración"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Configuración"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Ventana de ampliación"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Controles de ampliación de la ventana"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Acercar"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Alejar"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover hacia arriba"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover hacia abajo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover hacia la izquierda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover hacia la derecha"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Botón de ampliación"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar toda la pantalla"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Botón"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles de dispositivos"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Agrega controles para los dispositivos conectados"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar controles de dispositivos"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se pudo establecer la conexión. Vuelve a intentarlo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo nuevo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Se copió el número de compilación en el portapapeles."</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 7e5361b4f70c..2003a7d09539 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar iconos de notificaciones con prioridad baja"</string>
<string name="other" msgid="429768510980739978">"Otros"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar icono"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"añadir icono al final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover icono"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Añadir icono"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Añadir a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de ajustes rápidos."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificación de <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir ajustes."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ajustes"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Ventana de ampliación"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Ventana de controles de ampliación"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Ampliar"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Reducir"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover hacia arriba"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover hacia abajo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover hacia la izquierda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover hacia la derecha"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Botón para cambiar el modo de ampliación"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar toda la pantalla"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte de la pantalla"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Cambiar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Añade controles para tus dispositivos conectados"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar control de dispositivos"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"No se ha podido conectar. Inténtalo de nuevo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular nuevo dispositivo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Número de compilación copiado en el portapapeles."</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 78de793517f4..286a42b99eea 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Kuva madala prioriteediga märguande ikoonid"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"paani eemaldamiseks"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"paani lõppu lisamiseks"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Teisalda paan"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisa paan"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Teisaldamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Asend <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kiirseadete redigeerija."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Teenuse <xliff:g id="ID_1">%1$s</xliff:g> märguanne: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ava seaded."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Seaded"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Suurendamisaken"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Suurendamisakna juhtelemendid"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Suumi sisse"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Suumi välja"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Teisalda üles"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Teisalda alla"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Teisalda vasakule"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Teisalda paremale"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suurenduse lüliti"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Kogu ekraanikuva suurendamine"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekraanikuva osa suurendamine"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Lüliti"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Seadmete juhikud"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Lisage juhtelemendid ühendatud seadmete jaoks"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Seadmete juhtimisvidinate seadistamine"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (pole ühendatud)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ühenduse loomine ebaõnnestus. Proovige uuesti."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Uue seadme sidumine"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Järgunumber"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Järgunumber kopeeriti lõikelauale."</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index b5d84447271a..b6beec061d39 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Erakutsi lehentasun txikiko jakinarazpenen ikonoak"</string>
<string name="other" msgid="429768510980739978">"Beste bat"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"kendu lauza"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"gehitu lauza amaieran"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mugitu lauza"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Gehitu lauza"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Eraman <xliff:g id="POSITION">%1$d</xliff:g>garren lekura"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ezarpen bizkorren editorea."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> zerbitzuaren jakinarazpena: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ireki ezarpenak."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ezarpenak"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Lupa-leihoa"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Lupa-leihoaren aukerak"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Handitu"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Txikitu"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Eraman gora"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Eraman behera"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Eraman ezkerrera"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Eraman eskuinera"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Lupa aplikatzeko botoia"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Handitu pantaila osoa"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Handitu pantailaren zati bat"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Botoia"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Gailuak kontrolatzeko widgetak"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Gehitu konektatutako gailuak kontrolatzeko widgetak"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfiguratu gailuak kontrolatzeko widgetak"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (deskonektatuta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ezin izan da konektatu. Saiatu berriro."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parekatu beste gailu batekin"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Konpilazio-zenbakia"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Kopiatu da konpilazio-zenbakia arbelean."</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index d453f91da504..7849ea4c6bc0 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -246,7 +246,7 @@
<string name="accessibility_remove_notification" msgid="1641455251495815527">"پاک کردن اعلان"</string>
<string name="accessibility_gps_enabled" msgid="4061313248217660858">"‏GPS فعال شد."</string>
<string name="accessibility_gps_acquiring" msgid="896207402196024040">"‏دستیابی به GPS."</string>
- <string name="accessibility_tty_enabled" msgid="1123180388823381118">"‏TeleTypewriter فعال شد."</string>
+ <string name="accessibility_tty_enabled" msgid="1123180388823381118">"تله‌تایپ فعال شد."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"زنگ لرزشی."</string>
<string name="accessibility_ringer_silent" msgid="8994620163934249882">"زنگ بی‌صدا."</string>
<!-- no translation found for accessibility_casting (8708751252897282313) -->
@@ -431,7 +431,7 @@
<string name="quick_settings_screen_record_start" msgid="1574725369331638985">"شروع"</string>
<string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"توقف"</string>
<string name="media_seamless_remote_device" msgid="177033467332920464">"دستگاه"</string>
- <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"برای تغییر برنامه‌ها،‌ تند به بالا بکشید"</string>
+ <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"برای تغییر برنامه‌ها،‌ تند به‌بالا بکشید"</string>
<string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"برای جابه‌جایی سریع میان برنامه‌ها، به چپ بکشید"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تغییر وضعیت نمای کلی"</string>
<string name="expanded_header_battery_charged" msgid="5307907517976548448">"شارژ کامل شد"</string>
@@ -450,8 +450,8 @@
<string name="keyguard_more_overflow_text" msgid="5819512373606638727">"+<xliff:g id="NUMBER_OF_NOTIFICATIONS">%d</xliff:g>"</string>
<string name="speed_bump_explanation" msgid="7248696377626341060">"اعلان‌های کمتر فوری در زیر"</string>
<string name="notification_tap_again" msgid="4477318164947497249">"دوباره ضربه بزنید تا باز شود"</string>
- <string name="keyguard_unlock" msgid="8031975796351361601">"برای باز کردن، انگشتتان را تند به بالا بکشید"</string>
- <string name="keyguard_retry" msgid="886802522584053523">"برای امتحان مجدد، انگشتتان را تند به بالا بکشید"</string>
+ <string name="keyguard_unlock" msgid="8031975796351361601">"برای باز کردن، انگشتتان را تند به‌بالا بکشید"</string>
+ <string name="keyguard_retry" msgid="886802522584053523">"برای امتحان مجدد، انگشتتان را تند به‌بالا بکشید"</string>
<string name="do_disclosure_generic" msgid="4896482821974707167">"این دستگاه به سازمان شما تعلق دارد"</string>
<string name="do_disclosure_with_name" msgid="2091641464065004091">"این دستگاه به <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> تعلق دارد"</string>
<string name="phone_hint" msgid="6682125338461375925">"انگشتتان را از نماد تلفن تند بکشید"</string>
@@ -740,7 +740,7 @@
<string name="feedback_promoted" msgid="8075757485407091976">"سیستمْ این اعلان را ارتقا داده است."</string>
<string name="feedback_demoted" msgid="5848066008939031913">"سیستمْ این اعلان را تنزل داده است."</string>
<string name="feedback_prompt" msgid="2278631214125128281">"این مورد درست بود؟"</string>
- <string name="feedback_response" msgid="4671729244976641339">"از بازخورد شما سپاس‌گذاریم!"</string>
+ <string name="feedback_response" msgid="4671729244976641339">"از بازخوردتان سپاس‌گزاریم!"</string>
<string name="feedback_ok" msgid="6481426753298857144">"تأیید"</string>
<string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"کنترل‌های اعلان برای <xliff:g id="APP_NAME">%1$s</xliff:g> باز شد"</string>
<string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"کنترل‌های اعلان برای <xliff:g id="APP_NAME">%1$s</xliff:g> بسته شد"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"نمایش نمادهای اعلان کم‌اهمیت"</string>
<string name="other" msgid="429768510980739978">"موارد دیگر"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"برداشتن کاشی"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"افزودن کاشی به انتها"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"انتقال کاشی"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"افزودن کاشی"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"انتقال به <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"افزودن به موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"موقعیت <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ویرایشگر تنظیمات سریع."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"اعلان <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"باز کردن تنظیمات."</string>
@@ -967,7 +960,7 @@
<string name="slice_permission_text_2" msgid="6758906940360746983">"- می‌تواند در <xliff:g id="APP">%1$s</xliff:g> اقدام انجام دهد"</string>
<string name="slice_permission_checkbox" msgid="4242888137592298523">"به <xliff:g id="APP">%1$s</xliff:g> اجازه داده شود تکه‌هایی از برنامه‌ها نشان دهد"</string>
<string name="slice_permission_allow" msgid="6340449521277951123">"مجاز"</string>
- <string name="slice_permission_deny" msgid="6870256451658176895">"رد کردن"</string>
+ <string name="slice_permission_deny" msgid="6870256451658176895">"مجاز نبودن"</string>
<string name="auto_saver_title" msgid="6873691178754086596">"برای زمان‌بندی «بهینه‌سازی باتری» ضربه بزنید"</string>
<string name="auto_saver_text" msgid="3214960308353838764">"وقتی باتری روبه‌اتمام است، بهینه‌سازی باتری را روشن کنید"</string>
<string name="no_auto_saver_action" msgid="7467924389609773835">"نه متشکرم"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"تنظیمات"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"پنجره بزرگ‌نمایی"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"کنترل‌های پنجره بزرگ‌نمایی"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"بزرگ کردن"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"کوچک کردن"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"انتقال به بالا"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"انتقال به پایین"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"انتقال به راست"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"انتقال به چپ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"کلید درشت‌نمایی"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"درشت‌نمایی تمام صفحه"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"درشت‌نمایی بخشی از صفحه"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"کلید"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"کنترل‌های دستگاه"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"افزودن کنترل‌ها برای دستگاه‌های متصل"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"تنظیم کنترل‌های دستگاه"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (اتصال قطع شد)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"متصل نشد. دوباره امتحان کنید."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"مرتبط کردن دستگاه جدید"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"شماره ساخت"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"شماره ساخت در بریده‌دان کپی شد."</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index dde2449d8894..3e5afc784a9c 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -379,7 +379,7 @@
<string name="quick_settings_wifi_on_label" msgid="2489928193654318511">"Wi-Fi on käytössä"</string>
<string name="quick_settings_wifi_detail_empty_text" msgid="483130889414601732">"Ei Wi-Fi-verkkoja käytettävissä"</string>
<string name="quick_settings_wifi_secondary_label_transient" msgid="7501659015509357887">"Otetaan käyttöön…"</string>
- <string name="quick_settings_cast_title" msgid="2279220930629235211">"Näytön suoratoisto"</string>
+ <string name="quick_settings_cast_title" msgid="2279220930629235211">"Näytön striimaus"</string>
<string name="quick_settings_casting" msgid="1435880708719268055">"Lähetetään"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimetön laite"</string>
<string name="quick_settings_cast_device_default_description" msgid="2580520859212250265">"Valmis lähetystä varten"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Näytä vähemmän tärkeät ilmoituskuvakkeet"</string>
<string name="other" msgid="429768510980739978">"Muu"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"poista kiekko"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"lisää kiekko loppuun"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Siirrä kiekkoa"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisää kiekko"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Siirrä paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisää paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Pika-asetusten muokkausnäkymä"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Ilmoitus kohteesta <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Avaa asetukset."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Asetukset"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Suurennusikkuna"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Suurennusikkunan ohjaimet"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Lähennä"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Loitonna"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Siirrä ylös"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Siirrä alas"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Siirrä vasemmalle"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Siirrä oikealle"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suurennusvalinta"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Suurenna koko näyttö"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Suurenna osa näytöstä"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Vaihda"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Laitteiden hallinta"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Lisää ohjaimia yhdistettyjä laitteita varten"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Laitteiden hallinnan käyttöönotto"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (yhteys katkaistu)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ei yhteyttä. Yritä uudelleen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Muodosta uusi laitepari"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Koontiversion numero"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Koontiversion numero kopioitu leikepöydälle"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index ea6de66db50f..250183e60915 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification de faible priorité"</string>
<string name="other" msgid="429768510980739978">"Autre"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"retirer la tuile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ajouter la tuile à la fin"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la tuile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter la tuile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de paramètres rapides."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notification <xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ouvrir les paramètres."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Paramètres"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Fenêtre d\'agrandissement"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Commandes pour la fenêtre d\'agrandissement"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Effectuer un zoom avant"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Effectuer un zoom arrière"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Déplacer vers le haut"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Déplacer vers le bas"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Déplacer vers la gauche"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Déplacer vers la droite"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Commutateur d\'agrandissement"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Agrandir l\'écran entier"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Commutateur"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Ajoutez des commandes pour vos appareils connectés"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurer les commandes des appareils"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un autre appareil"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de version"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Le numéro de version a été copié dans le presse-papiers."</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index fc2691e3570d..2a4c95af7c46 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afficher les icônes de notification à faible priorité"</string>
<string name="other" msgid="429768510980739978">"Autre"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"supprimer la carte"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ajouter la carte à la fin"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Déplacer la carte"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter une carte"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Éditeur de configuration rapide."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notification <xliff:g id="ID_1">%1$s</xliff:g> : <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ouvrir les paramètres."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Paramètres"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Fenêtre d\'agrandissement"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Fenêtre des commandes d\'agrandissement"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Faire un zoom avant"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Faire un zoom arrière"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Déplacer vers le haut"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Déplacer vers le bas"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Déplacer vers la gauche"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Déplacer vers la droite"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Changer de mode d\'agrandissement"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Agrandir tout l\'écran"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Agrandir une partie de l\'écran"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Changer"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Ajouter des commandes pour vos appareils connectés"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurer les commandes des appareils"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (déconnecté)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossible de se connecter. Réessayez."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Associer un nouvel appareil"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numéro de build"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Numéro de build copié dans le presse-papiers."</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index aba2d8fd389a..fedbe2cf5a9a 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -246,7 +246,7 @@
<string name="accessibility_remove_notification" msgid="1641455251495815527">"Eliminar notificación."</string>
<string name="accessibility_gps_enabled" msgid="4061313248217660858">"GPS activado"</string>
<string name="accessibility_gps_acquiring" msgid="896207402196024040">"Obtendo GPS."</string>
- <string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter activado"</string>
+ <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo activado"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
<string name="accessibility_ringer_silent" msgid="8994620163934249882">"Timbre silenciado"</string>
<!-- no translation found for accessibility_casting (8708751252897282313) -->
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar iconas das notificacións que teñan baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"quitar tarxeta"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"engadir tarxeta ao final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover tarxeta"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Engadir tarxeta"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engadir á posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configuración rápida."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificación de <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir configuración."</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Configuración"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Ventá de superposición"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Controis de ampliación da ventá"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Achegar"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Afastar"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover cara arriba"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover cara abaixo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover cara á esquerda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover cara á dereita"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Engade controis para os dispositivos conectados"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar o control de dispositivos"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (dispositivo desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Non se puido establecer a conexión. Téntao de novo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Vincular dispositivo novo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número de compilación"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Copiouse o número de compilación no portapapeis."</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 1acef350034a..d9c5b72be3b2 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ઓછી પ્રાધાન્યતાનું નોટિફિકેશન આઇકન બતાવો"</string>
<string name="other" msgid="429768510980739978">"અન્ય"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ટાઇલ કાઢી નાખો"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ટાઇલને અંતે ઉમેરો"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ટાઇલ ખસેડો"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ટાઇલ ઉમેરો"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> પર ખસેડો"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"જગ્યા પર <xliff:g id="POSITION">%1$d</xliff:g> ઉમેરો"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"જગ્યા <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ઝડપી સેટિંગ્સ સંપાદક."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> નોટિફિકેશન: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"સેટિંગ્સ ખોલો."</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"સેટિંગ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"વિસ્તૃતીકરણ વિંડો"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"વિસ્તૃતીકરણ વિંડોના નિયંત્રણો"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"મોટું કરો"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"નાનું કરો"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ઉપર ખસેડો"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"નીચે ખસેડો"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ડાબી બાજુ ખસેડો"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"જમણી બાજુ ખસેડો"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"ડિવાઇસનાં નિયંત્રણો"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"તમારા કનેક્ટ કરેલા ડિવાઇસ માટે નિયંત્રણો ઉમેરો"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ડિવાઇસનાં નિયંત્રણો સેટઅપ કરો"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ડિસ્કનેક્ટ થયેલું)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"કનેક્ટ કરી શકાયું નહીં. ફરી પ્રયાસ કરો."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"નવા ડિવાઇસ સાથે જોડાણ કરો"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"બિલ્ડ નંબર"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"બિલ્ડ નંબર ક્લિપબૉર્ડ પર કૉપિ કર્યો."</string>
</resources>
diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml
new file mode 100644
index 000000000000..966066ffe56b
--- /dev/null
+++ b/packages/SystemUI/res/values-h740dp-port/dimens.xml
@@ -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
+ -->
+
+<resources>
+ <dimen name="qs_tile_height">106dp</dimen>
+ <dimen name="qs_tile_margin_vertical">24dp</dimen>
+
+ <!-- The height of the qs customize header. Should be
+ (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+ (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
+ -->
+ <dimen name="qs_customize_header_min_height">46dp</dimen>
+ <dimen name="qs_tile_margin_top">18dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 1a69e81a8191..181ae74e20cc 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -881,20 +881,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकता वाली सूचना के आइकॉन दिखाएं"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाएं"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"टाइल को आखिरी पोज़िशन पर जोड़ें"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल को किसी और पोज़िशन पर ले जाएं"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल जोड़ें"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर ले जाएं"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल को <xliff:g id="POSITION">%1$d</xliff:g> पोज़िशन पर जोड़ें"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"टाइल की पोज़िशन <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"त्वरित सेटिंग संपादक."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> सूचना: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"सेटिंग खोलें."</string>
@@ -1021,6 +1014,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"सेटिंग"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"स्क्रीन को बड़ा करके दिखाने वाली विंडो"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"स्क्रीन को बड़ा करके दिखाने वाली विंडो के नियंत्रण"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ज़ूम इन करें"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ज़ूम आउट करें"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ऊपर ले जाएं"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"नीचे ले जाएं"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"बाईं ओर ले जाएं"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"दाईं ओर ले जाएं"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ज़ूम करने की सुविधा वाला स्विच"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"पूरी स्क्रीन को ज़ूम करें"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"स्क्रीन के किसी हिस्से को ज़ूम करें"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"स्विच"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"डिवाइस कंट्रोल"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"कनेक्ट किए गए डिवाइस के लिए कंट्रोल जोड़ें"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"डिवाइस कंट्रोल सेट अप करें"</string>
@@ -1084,4 +1087,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिसकनेक्ट किया गया)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट नहीं किया जा सका. फिर से कोशिश करें."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नया डिवाइस जोड़ें"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर को क्लिपबोर्ड पर कॉपी किया गया."</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 9b3f99748375..f8318d5ac4f8 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Prikaži ikone obavijesti niskog prioriteta"</string>
<string name="other" msgid="429768510980739978">"Ostalo"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"uklanjanje kartice"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"dodavanje kartice na kraj"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Premještanje kartice"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premještanje u prostoriju <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Uređivač brzih postavki."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> obavijest: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otvaranje postavki."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Postavke"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Prozor za povećavanje"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrole prozora za povećavanje"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Povećaj"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Smanji"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Premjesti gore"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Premjesti dolje"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Premjesti ulijevo"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Premjesti udesno"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prebacivanje povećavanja"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Povećaj cijeli zaslon"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povećaj dio zaslona"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prebaci"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodavanje kontrola za povezane uređaje"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Postavljanje kontrola uređaja"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nije povezano)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezivanje nije bilo moguće. Pokušajte ponovo."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Upari novi uređaj"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Broj međuverzije"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Broj međuverzije kopiran je u međuspremnik."</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 7f4fa9d0acd3..690508a56513 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Alacsony prioritású értesítési ikonok mutatása"</string>
<string name="other" msgid="429768510980739978">"Egyéb"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"mozaik eltávolításához"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"mozaiknak a végéhez való hozzáadásához"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mozaik áthelyezése"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Mozaik hozzáadása"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Áthelyezés ide: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Hozzáadás a következő pozícióhoz: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. hely"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Gyorsbeállítások szerkesztője"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-értesítések: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Beállítások megnyitása."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Beállítások"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Nagyítás ablaka"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Nagyítási vezérlők ablaka"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Nagyítás"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Kicsinyítés"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mozgatás felfelé"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mozgatás lefelé"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mozgatás balra"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mozgatás jobbra"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Nagyításváltó"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Teljes képernyő nagyítása"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Képernyő bizonyos részének nagyítása"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Váltás"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Eszközvezérlők"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Vezérlők hozzáadása a csatlakoztatott eszközökhöz"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Eszközvezérlők beállítása"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (leválasztva)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Sikertelen csatlakozás. Próbálja újra."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Új eszköz párosítása"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Buildszám"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Buildszám a vágólapra másolva."</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 70cbf493feca..0a966a1a905f 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -246,7 +246,7 @@
<string name="accessibility_remove_notification" msgid="1641455251495815527">"Մաքրել ծանուցումը:"</string>
<string name="accessibility_gps_enabled" msgid="4061313248217660858">"GPS-ը միացված է:"</string>
<string name="accessibility_gps_acquiring" msgid="896207402196024040">"GPS-ի ստացում:"</string>
- <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Հեռամուտքագրիչը միացված է:"</string>
+ <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Հեռատիպը միացված է:"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Թրթռազանգ:"</string>
<string name="accessibility_ringer_silent" msgid="8994620163934249882">"Զանգակը լռեցված է:"</string>
<!-- no translation found for accessibility_casting (8708751252897282313) -->
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Ցուցադրել ցածր առաջնահերթության ծանուցումների պատկերակները"</string>
<string name="other" msgid="429768510980739978">"Այլ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"հեռացնել սալիկը"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ավելացնել սալիկը վերջում"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Տեղափոխել սալիկը"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ավելացնել սալիկ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Տեղափոխել դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ավելացնել դիրք <xliff:g id="POSITION">%1$d</xliff:g>-ում"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Դիրք <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Արագ կարգավորումների խմբագրիչ:"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> ծանուցում՝ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Բացել կարգավորումները:"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Կարգավորումներ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Խոշորացման պատուհան"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Խոշորացման պատուհանի կառավարման տարրեր"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Մեծացնել"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Փոքրացնել"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Տեղափոխել վերև"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Տեղափոխել ներքև"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Տեղափոխել ձախ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Տեղափոխել աջ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Խոշորացման փոփոխություն"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Խոշորացնել ամբողջ էկրանը"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Խոշորացնել էկրանի որոշակի հատվածը"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Փոխել"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Սարքերի կառավարման տարրեր"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Ավելացրեք կառավարման տարրեր ձեր միացված սարքերի համար"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Սարքերի կառավարման տարրերի կարգավորում"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (անջատված է)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Չհաջողվեց միանալ։ Նորից փորձեք։"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Նոր սարքի զուգակցում"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Կառուցման համարը"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Կառուցման համարը պատճենվեց սեղմատախտակին։"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index dc0a5128b808..750a38f58220 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -597,7 +597,7 @@
<string name="screen_pinning_description_accessible" msgid="7386449191953535332">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Ringkasan untuk melepas pin."</string>
<string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"Ini akan terus ditampilkan sampai Anda melepas pin. Sentuh lama tombol Beranda untuk melepas pin."</string>
<string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Data pribadi dapat diakses (seperti kontak dan konten email)."</string>
- <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Aplikasi yang dipasangi pin dapat membuka aplikasi lain."</string>
+ <string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Aplikasi yang disematkan dapat membuka aplikasi lain."</string>
<string name="screen_pinning_toast" msgid="8177286912533744328">"Untuk melepas pin aplikasi ini, sentuh &amp; lama tombol Kembali dan Ringkasan"</string>
<string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"Untuk melepas pin aplikasi ini, sentuh &amp; lama tombol Kembali dan Layar utama"</string>
<string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"Untuk melepas pin aplikasi ini, geser ke atas &amp; tahan"</string>
@@ -862,7 +862,7 @@
<string name="left_icon" msgid="5036278531966897006">"Ikon kiri"</string>
<string name="right_icon" msgid="1103955040645237425">"Ikon kanan"</string>
<string name="drag_to_add_tiles" msgid="8933270127508303672">"Tahan dan tarik untuk menambahkan kartu"</string>
- <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Tahan dan tarik untuk mengatur ulang kartu"</string>
+ <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Tahan dan tarik untuk menata ulang kartu"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Tarik ke sini untuk menghapus"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Anda membutuhkan setidaknya <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> kartu"</string>
<string name="qs_edit" msgid="5583565172803472437">"Edit"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tampilkan ikon notifikasi prioritas rendah"</string>
<string name="other" msgid="429768510980739978">"Lainnya"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"menghapus kartu"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"menambahkan kartu ke akhir"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pindahkan kartu"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan kartu"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pindahkan ke <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan ke posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor setelan cepat."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notifikasi <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Buka setelan."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Setelan"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Jendela Pembesaran"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrol Jendela Pembesaran"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Perbesar"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Perkecil"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Pindahkan ke atas"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pindahkan ke bawah"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pindahkan ke kiri"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pindahkan ke kanan"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Tombol pembesaran"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Perbesar seluruh layar"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Perbesar sebagian layar"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Alihkan"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrol perangkat"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Tambahkan kontrol untuk perangkat terhubung"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Siapkan kontrol perangkat"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (terputus)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak dapat terhubung. Coba lagi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sambungkan perangkat baru"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nomor versi"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Nomor versi disalin ke papan klip."</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index f79796b6276d..944e13faf3cb 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -861,10 +861,10 @@
<string name="right_keycode" msgid="2480715509844798438">"Lykiltákn til hægri"</string>
<string name="left_icon" msgid="5036278531966897006">"Tákn til vinstri"</string>
<string name="right_icon" msgid="1103955040645237425">"Tákn til hægri"</string>
- <string name="drag_to_add_tiles" msgid="8933270127508303672">"Haltu inni og dragðu til að bæta við reitum"</string>
+ <string name="drag_to_add_tiles" msgid="8933270127508303672">"Haltu inni og dragðu til að bæta við flísum"</string>
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Haltu og dragðu til að endurraða flísum"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Dragðu hingað til að fjarlægja"</string>
- <string name="drag_to_remove_disabled" msgid="933046987838658850">"Reitirnir mega ekki vera færri en <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g>"</string>
+ <string name="drag_to_remove_disabled" msgid="933046987838658850">"Flísarnar mega ekki vera færri en <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g>"</string>
<string name="qs_edit" msgid="5583565172803472437">"Breyta"</string>
<string name="tuner_time" msgid="2450785840990529997">"Tími"</string>
<string-array name="clock_options">
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Sýna tákn fyrir tilkynningar með litlum forgangi"</string>
<string name="other" msgid="429768510980739978">"Annað"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjarlægja flís"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"bæta flís við aftast"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Færa flís"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Bæta flís við"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Færa í <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bæta við í stöðu <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Staða <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Flýtistillingaritill."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> tilkynning: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Opna stillingar."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Stillingar"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Stækkunargluggi"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Stækkunarstillingar glugga"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Auka aðdrátt"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Minnka aðdrátt"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Færa upp"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Færa niður"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Færa til vinstri"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Færa til hægri"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stækkunarrofi"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Stækka allan skjáinn"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Stækka hluta skjásins"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Rofi"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Tækjastjórnun"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Bæta við stýringum fyrir tengd tæki"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Setja upp tækjastjórnun"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (aftengt)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tenging mistókst. Reyndu aftur."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Para nýtt tæki"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Útgáfunúmer smíðar"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Útgáfunúmer smíðar afritað á klippiborð."</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 3b99dc2e1f6e..68c64f0b6955 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostra icone di notifiche con priorità bassa"</string>
<string name="other" msgid="429768510980739978">"Altro"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"rimuovere il riquadro"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"aggiungere il riquadro alla fine"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Sposta riquadro"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Aggiungi riquadro"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Sposta nella posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Aggiungi alla posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor di impostazioni rapide."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notifica di <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Apri le impostazioni."</string>
@@ -975,7 +968,7 @@
<string name="auto_saver_enabled_text" msgid="7889491183116752719">"Il Risparmio energetico verrà attivato automaticamente quando la carica della batteria sarà inferiore a <xliff:g id="PERCENTAGE">%d</xliff:g>%%."</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"Impostazioni"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"OK"</string>
- <string name="heap_dump_tile_name" msgid="2464189856478823046">"Esegui dump heap SysUI"</string>
+ <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump heap SysUI"</string>
<string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"L\'app <xliff:g id="APP">%1$s</xliff:g> sta usando <xliff:g id="TYPES_LIST">%2$s</xliff:g>."</string>
<string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Le app stanno usando <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
<string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Impostazioni"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Finestra ingrandimento"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Finestra controlli di ingrandimento"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Aumenta lo zoom"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Diminuisci lo zoom"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Sposta su"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Sposta giù"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sposta a sinistra"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sposta a destra"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Opzione Ingrandimento"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ingrandisci l\'intero schermo"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ingrandisci parte dello schermo"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Opzione"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controllo dei dispositivi"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Aggiungi controlli per i dispositivi connessi"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configura il controllo dei dispositivi"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (disconnesso)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Impossibile connettersi. Riprova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Accoppia nuovo dispositivo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero build"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Numero build copiato negli appunti."</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 3bb8ea5fe1dd..e1ad35db002a 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"הצגת סמלי התראות בעדיפות נמוכה"</string>
<string name="other" msgid="429768510980739978">"אחר"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"הסרת האריח"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"הוספת האריח בסוף הרשימה"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"העברת האריח"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"הוספת אריח"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"העברה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"הוספה למיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"מיקום <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"עורך הגדרות מהירות."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"התראות <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"פתיחת הגדרות."</string>
@@ -948,7 +941,7 @@
<string name="notification_channel_general" msgid="4384774889645929705">"הודעות כלליות"</string>
<string name="notification_channel_storage" msgid="2720725707628094977">"אחסון"</string>
<string name="notification_channel_hints" msgid="7703783206000346876">"טיפים"</string>
- <string name="instant_apps" msgid="8337185853050247304">"אפליקציות אינסטנט"</string>
+ <string name="instant_apps" msgid="8337185853050247304">"אפליקציות ללא התקנה"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> פועלת"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"האפליקציה נפתחת בלי התקנה."</string>
<string name="instant_apps_message_with_help" msgid="1816952263531203932">"האפליקציה נפתחת בלי התקנה. אפשר להקיש כדי לקבל מידע נוסף."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"הגדרות"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"חלון הגדלה"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"בקרות של חלון ההגדלה"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"התקרבות"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"התרחקות"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"הזזה למעלה"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"הזזה למטה"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"הזזה שמאלה"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"הזזה ימינה"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"מעבר למצב הגדלה"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"הגדלת כל המסך"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"הגדלת חלק מהמסך"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"מעבר"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"פקדי מכשירים"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"יש להוסיף פקדים למכשירים המחוברים"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"הגדרה של פקדי מכשירים"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (מנותק)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"לא ניתן היה להתחבר. יש לנסות שוב."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"התאמה של מכשיר חדש"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"‏מספר Build"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"‏מספר ה-Build הועתק ללוח."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 2c55569bb96a..763a80e48547 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"優先度の低い通知アイコンを表示"</string>
<string name="other" msgid="429768510980739978">"その他"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"タイルを削除"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"タイルを最後に追加"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"タイルを移動"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"タイルを追加"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> に移動"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ポジション <xliff:g id="POSITION">%1$d</xliff:g> に追加"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"クイック設定エディタ"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> の通知: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"設定を開きます。"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"設定"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"拡大ウィンドウ"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"拡大ウィンドウ コントロール"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"拡大"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"縮小"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"上に移動"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"下に移動"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"左に移動"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"右に移動"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"拡大スイッチ"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"画面全体を拡大します"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"画面の一部を拡大します"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"スイッチ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"デバイス コントロール"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"接続済みデバイスのコントロールを追加します"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"デバイス コントロールの設定"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(未接続)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"接続できませんでした。もう一度お試しください。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"新しいデバイスとのペア設定"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ビルド番号"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ビルド番号をクリップボードにコピーしました。"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index ca5b7da19247..0086868ace6b 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"დაბალი პრიორიტეტის მქონე შეტყობინებების ხატულების ჩვენება"</string>
<string name="other" msgid="429768510980739978">"სხვა"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"მოზაიკის ფილის წაშლა"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ფილის ბოლოში დამატება"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"მოზაიკის გადატანა"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"მოზაიკის დამატება"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"გადატანა <xliff:g id="POSITION">%1$d</xliff:g>-ზე"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"დამატება პოზიციაზე <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"პოზიცია <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"სწრაფი პარამეტრების რედაქტორი."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> შეტყობინება: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"პარამეტრების გახსნა."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"პარამეტრები"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"გადიდების ფანჯარა"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"გადიდების კონტროლის ფანჯარა"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"მასშტაბის გადიდება"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"მასშტაბის შემცირება"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ზემოთ გადატანა"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ქვემოთ გადატანა"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"მარცხნივ გადატანა"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"მარჯვნივ გადატანა"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"გადიდების გადართვა"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"მთლიანი ეკრანის გადიდება"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ეკრანის ნაწილის გადიდება"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"გადართვა"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"მოწყობილ. მართვის საშუალებები"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"მართვის საშუალებების დამატება თქვენს დაკავშირებულ მოწყობილობებზე"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"მოწყობილობის მართვის საშუალებების დაყენება"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (კავშირი გაწყვეტილია)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"დაკავშირება ვერ მოხერხდა. ცადეთ ხელახლა."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ახალი მოწყობილობის დაწყვილება"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ანაწყობის ნომერი"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ანაწყობის ნომერი დაკოპირებულია გაცვლის ბუფერში."</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index a4bf498edfff..f1c012169998 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -865,7 +865,7 @@
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Элементтердің ретін өзгерту үшін оларды басып тұрып сүйреңіз"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Керексіздерін осы жерге сүйреңіз"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Кемінде <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> бөлшек қажет."</string>
- <string name="qs_edit" msgid="5583565172803472437">"Өңдеу"</string>
+ <string name="qs_edit" msgid="5583565172803472437">"Өзгерту"</string>
<string name="tuner_time" msgid="2450785840990529997">"Уақыт"</string>
<string-array name="clock_options">
<item msgid="3986445361435142273">"Сағаттарды, минуттарды және секундтарды көрсету"</item>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Маңызды емес хабарландыру белгішелерін көрсету"</string>
<string name="other" msgid="429768510980739978">"Басқа"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"бөлшекті өшіру"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"бөлшекті соңына қосу"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Бөлшекті жылжыту"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Бөлшек қосу"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> орнына жылжыту"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> орнына қосу"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> орны"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Жылдам параметрлер өңдегіші."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> хабарландыруы: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Параметрлерді ашу."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Параметрлер"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Ұлғайту терезесі"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Ұлғайту терезесінің басқару элементтері"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Ұлғайту"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Кішірейту"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Жоғары қарай жылжыту"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Төмен қарай жылжыту"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Солға жылжыту"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Оңға жылжыту"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Ұлғайту режиміне ауыстырғыш"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Толық экранды ұлғайту"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экранның бөлігін ұлғайту"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Ауысу"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Жалғанған құрылғылар үшін басқару виджеттерін қосу"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Құрылғыны басқару элементтерін реттеу"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылған)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Қосылмады. Қайта қосылып көріңіз."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңа құрылғыны жұптау"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Құрама нөмірі"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Құрама нөмірі буферге көшірілді."</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 7eaa3039947a..92d7cbfe54b1 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"បង្ហាញ​រូប​ការជូនដំណឹង​ដែលមានអាទិភាពទាប"</string>
<string name="other" msgid="429768510980739978">"ផ្សេងៗ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ដកប្រអប់ចេញ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"បញ្ចូល​ប្រអប់ទៅ​ខាងចុង"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ផ្លាស់ទី​ប្រអប់"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"បញ្ចូល​ប្រអប់"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ផ្លាស់​ទីទៅ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"បញ្ចូលទៅ​ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ទីតាំងទី <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"កម្មវិធីកែការកំណត់រហ័ស"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> ការជូនដំណឹង៖ <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"បើកការកំណត់"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ការកំណត់"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"វិនដូ​ការពង្រីក"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"វិនដូគ្រប់គ្រង​​ការពង្រីក"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ពង្រីក"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"បង្រួម"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ផ្លាស់ទី​ឡើង​លើ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ផ្លាស់ទី​ចុះ​ក្រោម"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ផ្លាស់ទី​ទៅ​ឆ្វេង"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ផ្លាស់ទីទៅ​ស្តាំ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ប៊ូតុងបិទបើកការ​ពង្រីក"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ពង្រីក​អេក្រង់​ទាំងមូល"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ពង្រីក​ផ្នែកនៃ​អេក្រង់"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ប៊ូតុងបិទបើក"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"បញ្ចូល​ផ្ទាំងគ្រប់គ្រង​សម្រាប់​ឧបករណ៍​ដែលអ្នកបានភ្ជាប់"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"រៀបចំ​ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (បាន​ផ្ដាច់)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"មិន​អាច​ភ្ជាប់​បាន​ទេ។ សូមព្យាយាមម្ដងទៀត។"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ផ្គូផ្គង​ឧបករណ៍ថ្មី"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"លេខ​កំណែបង្កើត"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"បានចម្លងលេខ​កំណែបង្កើតទៅឃ្លីបបត។"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79f845290365..2e47e9e7c55b 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -387,7 +387,7 @@
<string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="2325362583903258677">"ಸ್ವಯಂ"</string>
- <string name="quick_settings_inversion_label" msgid="5078769633069667698">"ಬಣ್ಣಗಳನ್ನು ಬದಲಾಯಿಸಿ"</string>
+ <string name="quick_settings_inversion_label" msgid="5078769633069667698">"ಬಣ್ಣಗಳನ್ನು ಇನ್ವರ್ಟ್ ಮಾಡಿ"</string>
<string name="quick_settings_color_space_label" msgid="537528291083575559">"ಬಣ್ಣ ತಿದ್ದುಪಡಿ ಮೋಡ್"</string>
<string name="quick_settings_more_settings" msgid="2878235926753776694">"ಹೆಚ್ಚಿನ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ಕಡಿಮೆ-ಆದ್ಯತೆ ಸೂಚನೆಯ ಐಕಾನ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
<string name="other" msgid="429768510980739978">"ಇತರ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ಟೈಲ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ಕೊನೆಯಲ್ಲಿ ಟೈಲ್ ಸೇರಿಸಿ"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ಟೈಲ್ ಸರಿಸಿ"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ಟೈಲ್ ಸೇರಿಸಿ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ಇಲ್ಲಿಗೆ ಸರಿಸಿ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ಸ್ಥಾನಕ್ಕೆ ಸೇರಿಸಿ"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ಸ್ಥಾನ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‍ಗಳ ಎಡಿಟರ್."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> ಅಧಿಸೂಚನೆ: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"ವರ್ಧನೆಯ ವಿಂಡೋ"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"ವರ್ಧನೆಯ ವಿಂಡೋ ನಿಯಂತ್ರಣಗಳು"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ಝೂಮ್ ಇನ್ ಮಾಡಿ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ಝೂಮ್ ಔಟ್ ಮಾಡಿ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ಮೇಲೆಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ಕೆಳಗೆ ಸರಿಸಿ"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ಎಡಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ಬಲಕ್ಕೆ ಸರಿಸಿ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ಝೂಮ್ ಮಾಡುವ ಸ್ವಿಚ್"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ಸ್ಕ್ರೀನ್‌ನ ಸಂಪೂರ್ಣ ಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ಸ್ಕ್ರೀನ್‌ನ ಅರ್ಧಭಾಗವನ್ನು ಝೂಮ್ ಮಾಡಿ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ಸ್ವಿಚ್"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳು"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ನಿಮ್ಮ ಸಂಪರ್ಕಿತ ಸಾಧನಗಳಿಗೆ ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಿ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳನ್ನು ಸೆಟಪ್ ಮಾಡಿ"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಿ"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ಬಿಲ್ಡ್ ಸಂಖ್ಯೆಯನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ ನಕಲಿಸಲಾಗಿದೆ."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 6a755e52f058..fc533be79c2c 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -371,7 +371,7 @@
<string name="quick_settings_time_label" msgid="3352680970557509303">"시간"</string>
<string name="quick_settings_user_label" msgid="1253515509432672496">"나"</string>
<string name="quick_settings_user_title" msgid="8673045967216204537">"사용자"</string>
- <string name="quick_settings_user_new_user" msgid="3347905871336069666">"새 사용자"</string>
+ <string name="quick_settings_user_new_user" msgid="3347905871336069666">"신규 사용자"</string>
<string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string>
<string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"연결되어 있지 않음"</string>
<string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"네트워크가 연결되지 않음"</string>
@@ -473,7 +473,7 @@
<string name="accessibility_multi_user_switch_inactive" msgid="383168614528618402">"현재 사용자: <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string>
<string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"프로필 표시"</string>
<string name="user_add_user" msgid="4336657383006913022">"사용자 추가"</string>
- <string name="user_new_user_name" msgid="2019166282704195789">"새 사용자"</string>
+ <string name="user_new_user_name" msgid="2019166282704195789">"신규 사용자"</string>
<string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"게스트를 삭제하시겠습니까?"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
<string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"삭제"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"우선순위가 낮은 알림 아이콘 표시"</string>
<string name="other" msgid="429768510980739978">"기타"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"타일 삭제"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"끝에 타일 추가"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"타일 이동"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"타일 추가"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> 위치로 이동"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> 위치에 추가"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> 위치"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"빠른 설정 편집기"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> 알림: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"설정 열기"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"설정"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"확대 창"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"확대 창 컨트롤"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"확대"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"축소"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"위로 이동"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"아래로 이동"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"왼쪽으로 이동"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"오른쪽으로 이동"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"확대 전환"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"전체 화면 확대"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"화면 일부 확대"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"전환"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"기기 컨트롤"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"연결된 기기의 컨트롤을 추가하세요."</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"기기 컨트롤 설정"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(연결 끊김)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"연결할 수 없습니다. 다시 시도하세요."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"새 기기와 페어링"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"빌드 번호"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"빌드 번호가 클립보드에 복사되었습니다."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index a740d07f7eac..a4e47b39d454 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -862,7 +862,7 @@
<string name="left_icon" msgid="5036278531966897006">"¨Солго¨ сүрөтчөсү"</string>
<string name="right_icon" msgid="1103955040645237425">"¨Оңго¨ сүрөтчөсү"</string>
<string name="drag_to_add_tiles" msgid="8933270127508303672">"Керектүү элементтерди сүйрөп келиңиз"</string>
- <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Элементтердин иретин өзгөртүү үчүн, кармап туруп, сүйрөңүз"</string>
+ <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Элементтердин иретин өзгөртүү үчүн кармап туруп, сүйрөңүз"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Алып салуу үчүн бул жерге сүйрөңүз"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Сизге жок дегенде <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> мозаика керек"</string>
<string name="qs_edit" msgid="5583565172803472437">"Түзөтүү"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Анча маанилүү эмес билдирменин сүрөтчөлөрүн көрсөтүү"</string>
<string name="other" msgid="429768510980739978">"Башка"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ыкчам баскычты өчүрүү"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ыкчам баскычты аягына кошуу"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Ыкчам баскычты жылдыруу"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ыкчам баскыч кошуу"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Төмөнкүгө жылдыруу: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>-позицияга кошуу"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>-позиция"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ыкчам жөндөөлөр түзөткүчү."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> эскертмеси: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Жөндөөлөрдү ачуу."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Жөндөөлөр"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Чоңойтуу терезеси"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Чоңойтуу терезесин башкаруу каражаттары"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Жакындатуу"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Алыстатуу"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Жогору жылдыруу"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Төмөн жылдыруу"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Солго жылдыруу"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Оңго жылдыруу"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Чоңойтуу режимине которулуу"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Толук экранды чоңойтуу"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Экрандын бир бөлүгүн чоңойтуу"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Которулуу"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Түзмөктү башкаруу элементтери"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Байланышкан түзмөктөрүңүздү башкаруу элементтерин кошосуз"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Түзмөктү башкаруу элементтерин жөндөө"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ажыратылды)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Байланышпай койду. Кайталоо."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Жаңы түзмөктү жупташтыруу"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Курама номери"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Курама номери алмашуу буферине көчүрүлдү."</string>
</resources>
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 8237919990de..95ff59bdf1df 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -16,10 +16,17 @@
<resources>
<!-- Height of volume bar -->
- <dimen name="volume_dialog_row_height">200dp</dimen>
- <dimen name="volume_dialog_panel_transparent_padding">17dp</dimen>
+ <dimen name="volume_dialog_row_height">190dp</dimen>
+ <dimen name="volume_dialog_row_width">48dp</dimen>
+ <dimen name="volume_dialog_panel_transparent_padding">24dp</dimen>
<dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
- <dimen name="tv_volume_dialog_corner_radius">40dp</dimen>
- <dimen name="tv_volume_dialog_row_padding">5dp</dimen>
- <dimen name="tv_volume_number_text_size">16dp</dimen>
+ <dimen name="tv_volume_dialog_corner_radius">36dp</dimen>
+ <dimen name="tv_volume_dialog_row_padding">6dp</dimen>
+ <dimen name="tv_volume_number_text_size">16sp</dimen>
+ <dimen name="tv_volume_seek_bar_width">4dp</dimen>
+ <dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen>
+ <dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen>
+ <dimen name="tv_volume_icons_size">20dp</dimen>
+ <dimen name="tv_volume_seek_bar_thumb_shadow_radius">4.0</dimen>
+ <dimen name="tv_volume_seek_bar_thumb_shadow_dy">4.0</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 1fb2ca0b0bd2..78fdf0964707 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ສະແດງໄອຄອນການແຈ້ງເຕືອນຄວາມສຳຄັນຕ່ຳ"</string>
<string name="other" msgid="429768510980739978">"ອື່ນໆ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ລຶບແຜ່ນອອກ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ເພີ່ມແຜ່ນໃສ່ທ້າຍ"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ຍ້າຍແຜ່ນ"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ເພີ່ມແຜ່ນ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ຍ້າຍໄປ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"ເພີ່ມໃສ່ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ຕຳແໜ່ງ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ຕົວແກ້ໄຂການຕັ້ງຄ່າດ່ວນ"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"ການແຈ້ງເຕືອນ <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ເປີດການຕັ້ງຄ່າ."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ການຕັ້ງຄ່າ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"ໜ້າຈໍການຂະຫຍາຍ"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"ການຄວບຄຸມໜ້າຈໍການຂະຫຍາຍ"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ຊູມເຂົ້າ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ຊູມອອກ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ຍ້າຍຂຶ້ນ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ຍ້າຍລົງ"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ຍ້າຍໄປຊ້າຍ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ຍ້າຍໄປຂວາ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ສະຫຼັບການຂະຫຍາຍ"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ຂະຫຍາຍທັງໜ້າຈໍ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ຂະຫຍາຍບາງສ່ວນຂອງໜ້າຈໍ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ສະຫຼັບ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ການຄວບຄຸມອຸປະກອນ"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ເພີ່ມການຄວບຄຸມສຳລັບອຸປະກອນທີ່ເຊື່ອມຕໍ່ແລ້ວຂອງທ່ານ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ຕັ້ງຄ່າການຄວບຄຸມອຸປະກອນ"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ຕັດການເຊື່ອມຕໍ່ແລ້ວ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້. ລອງໃໝ່."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ຈັບຄູ່ອຸປະກອນໃໝ່"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ໝາຍເລກສ້າງ"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ສຳເນົາໝາຍເລກສ້າງໄປໃສ່ຄລິບບອດແລ້ວ."</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index bd24722436dd..6db8969e0238 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Rodyti mažo prioriteto pranešimų piktogramas"</string>
<string name="other" msgid="429768510980739978">"Kita"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"pašalintumėte išklotinės elementą"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"pridėtumėte išklotinės elementą gale"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Perkelti išklotinės elementą"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridėti išklotinės elementą"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Perkelkite į <xliff:g id="POSITION">%1$d</xliff:g> poziciją"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridėkite <xliff:g id="POSITION">%1$d</xliff:g> pozicijoje"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> pozicija"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Sparčiųjų nustatymų redagavimo priemonė."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"„<xliff:g id="ID_1">%1$s</xliff:g>“ pranešimas: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Atidaryti nustatymus."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Nustatymai"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Didinimo langas"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Didinimo lango valdikliai"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Artinti"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Tolinti"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Perkelti aukštyn"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Perkelti žemyn"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Perkelti kairėn"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Perkelti dešinėn"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Didinimo jungiklis"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Didinti visą ekraną"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Didinti ekrano dalį"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Perjungti"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Įrenginio valdikliai"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Pridėkite prijungtų įrenginių valdiklių"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Įrenginio valdiklių nustatymas"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"„<xliff:g id="DEVICE_NAME">%1$s</xliff:g>“ (atjungta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepavyko prijungti. Bandykite dar kartą."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Naujo įrenginio susiejimas"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijos numeris"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Versijos numeris nukopijuotas į iškarpinę."</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e8d9a4d8c797..c7807deb3a5c 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Rādīt zemas prioritātes paziņojumu ikonas"</string>
<string name="other" msgid="429768510980739978">"Citi"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"noņemt elementu"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"pievienot elementu beigās"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Pārvietot elementu"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pievienot elementu"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pārvietot uz pozīciju numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pievienot elementu pozīcijā numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozīcija numur <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Ātro iestatījumu redaktors."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> paziņojums: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Atvērt iestatījumus."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Iestatījumi"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Palielināšanas logs"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Palielināšanas loga vadīklas"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Tuvināt"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Tālināt"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Pārvietot uz augšu"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pārvietot uz leju"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Pārvietot pa kreisi"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Pārvietot pa labi"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Palielinājuma slēdzis"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Palielināt visu ekrānu"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Palielināt ekrāna daļu"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Pārslēgt"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ierīču vadīklas"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Pievienojiet vadīklas pievienotajām ierīcēm"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Ierīču vadīklu iestatīšana"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (savienojums pārtraukts)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nevarēja izveidot savienojumu. Mēģiniet vēlreiz."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Savienošana pārī ar jaunu ierīci"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versijas numurs"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Versijas numurs ir kopēts starpliktuvē."</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 164cb96bf046..203f6c24609f 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажувај икони за известувања со низок приоритет"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"отстранување на плочката"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"додавање на плочката на крај"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Преместување на плочката"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додавање плочка"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместување на <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додавање на позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиција <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уредник за брзи поставки."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Известување од <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Отворете ги поставките."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Поставки"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Прозорец за зголемување"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Контроли на прозорец за зголемување"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Зумирај"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Одзумирај"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Премести нагоре"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Премести надолу"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Премести налево"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Премести надесно"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Прекинувач за зголемување"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Зголеми цел екран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Зголеми дел од екранот"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Префрли"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроли за уредите"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Додајте контроли за поврзаните уреди"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Поставете ги контролите за уредите"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (исклучен)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не можеше да се поврзе. Обидете се повторно."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Спарете нов уред"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број на верзија"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Бројот на верзијата е копиран во привремената меморија."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index ba379d3b7116..1076eace0223 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -387,7 +387,7 @@
<string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്‌തിട്ടില്ല"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
<string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="2325362583903258677">"യാന്ത്രികം"</string>
- <string name="quick_settings_inversion_label" msgid="5078769633069667698">"നിറം മാറ്റുക"</string>
+ <string name="quick_settings_inversion_label" msgid="5078769633069667698">"നെഗറ്റീവ് ലുക്ക്"</string>
<string name="quick_settings_color_space_label" msgid="537528291083575559">"വർണ്ണം ശരിയാക്കൽ മോഡ്"</string>
<string name="quick_settings_more_settings" msgid="2878235926753776694">"കൂടുതൽ ക്രമീകരണങ്ങൾ"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"പൂർത്തിയാക്കി"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"പ്രാധാന്യം കുറഞ്ഞ അറിയിപ്പ് ചിഹ്‌നങ്ങൾ"</string>
<string name="other" msgid="429768510980739978">"മറ്റുള്ളവ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ടൈൽ നീക്കം ചെയ്യുക"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ടൈൽ, അവസാന ഭാഗത്ത് ചേർക്കുക"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ടൈൽ നീക്കുക"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ടൈൽ ചേർക്കുക"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> എന്നതിലേക്ക് നീക്കുക"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> എന്ന സ്ഥാനത്തേക്ക് ചേർക്കുക"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"സ്ഥാനം <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ദ്രുത ക്രമീകരണ എഡിറ്റർ."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> അറിയിപ്പ്: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ക്രമീകരണം തുറക്കുക."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ക്രമീകരണം"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"മാഗ്നിഫിക്കേഷൻ വിൻഡോ നിയന്ത്രണങ്ങൾ"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"സൂം ഇൻ ചെയ്യുക"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"സൂം ഔട്ട് ചെയ്യുക"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"മുകളിലേക്ക് നീക്കുക"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"താഴേക്ക് നീക്കുക"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ഇടത്തേക്ക് നീക്കുക"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"വലത്തേക്ക് നീക്കുക"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"മാഗ്നിഫിക്കേഷൻ മോഡ് മാറുക"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"മുഴുവൻ സ്‌ക്രീനും മാഗ്നിഫൈ ചെയ്യുക"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"സ്‌ക്രീനിന്റെ ഭാഗം മാഗ്നിഫൈ ചെയ്യുക"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"മാറുക"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"കണക്റ്റ് ചെയ്ത ഉപകരണങ്ങൾക്ക് നിയന്ത്രണങ്ങൾ ചേർക്കുക"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ഉപകരണ നിയന്ത്രണങ്ങൾ സജ്ജീകരിക്കുക"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (വിച്ഛേദിച്ചു)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"കണക്റ്റ് ചെയ്യാനായില്ല. വീണ്ടും ശ്രമിക്കുക."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"പുതിയ ഉപകരണവുമായി ജോടിയാക്കുക"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ബിൽഡ് നമ്പർ"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ക്ലിപ്പ്ബോർഡിലേക്ക് ബിൽഡ് നമ്പർ പകർത്തി."</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 56c5ef27baec..c1ec99ee1da5 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -549,29 +549,29 @@
<string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Таны байгууллага таны ажлын профайлд сертификатын зөвшөөрөл суулгасан байна. Таны аюулгүй сүлжээний ачааллыг өөрчлөх эсвэл хянах боломжтой."</string>
<string name="monitoring_description_ca_certificate" msgid="448923057059097497">"Сертификатын зөвшөөрлийг энэ төхөөрөмжид суулгасан байна. Таны аюулгүй сүлжээний ачааллыг өөрчлөх эсвэл хянах боломжтой."</string>
<string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна."</string>
- <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"Та имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
- <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"Та имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP_0">%1$s</xliff:g>, <xliff:g id="VPN_APP_1">%2$s</xliff:g>-д холбогдсон байна."</string>
- <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"Таны ажлын профайл <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна. Энэ нь таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
- <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Таны хувийн профайлыг имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбосон байна."</string>
+ <string name="monitoring_description_named_vpn" msgid="5749932930634037027">"Та имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="monitoring_description_two_named_vpns" msgid="3516830755681229463">"Та имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP_0">%1$s</xliff:g>, <xliff:g id="VPN_APP_1">%2$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="monitoring_description_managed_profile_named_vpn" msgid="368812367182387320">"Таны ажлын профайл <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна. Энэ нь таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+ <string name="monitoring_description_personal_profile_named_vpn" msgid="8179722332380953673">"Таны хувийн профайлыг имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбосон байна."</string>
<string name="monitoring_description_do_header_generic" msgid="6130190408164834986">"Таны төхөөрөмжийг <xliff:g id="DEVICE_OWNER_APP">%1$s</xliff:g> удирддаг."</string>
<string name="monitoring_description_do_header_with_name" msgid="2696255132542779511">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> таны төхөөрөмжийг удирдахын тулд <xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>-г ашигладаг."</string>
<string name="monitoring_description_do_body" msgid="7700878065625769970">"Таны админ тохиргоо, байгууллагын хандалт, апп, төхөөрөмжтэй холбоотой өгөгдөл болон таны төхөөрөмжийн байршлын мэдээллийг хянах, удирдах боломжтой."</string>
<string name="monitoring_description_do_learn_more_separator" msgid="1467280496376492558">" "</string>
<string name="monitoring_description_do_learn_more" msgid="645149183455573790">"Дэлгэрэнгүй үзэх"</string>
- <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"Таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="monitoring_description_do_body_vpn" msgid="7699280130070502303">"Таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="VPN_APP">%1$s</xliff:g>-д холбогдсон байна."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
<string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"VPN тохиргоог нээх"</string>
<string name="monitoring_description_ca_cert_settings_separator" msgid="7107390013344435439">" "</string>
<string name="monitoring_description_ca_cert_settings" msgid="8329781950135541003">"Итгэмжлэгдсэн мандат үнэмлэхийг нээх"</string>
<string name="monitoring_description_network_logging" msgid="577305979174002252">"Таны админ төхөөрөмжийн ачааллыг хянадаг сүлжээний логийг асаасан байна.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу."</string>
- <string name="monitoring_description_vpn" msgid="1685428000684586870">"Та апп-д VPN холболт хийхийг зөвшөөрсөн байна.\n\nЭнэхүү апп нь таны имэйл, апп, вэбсайт зэрэг төхөөрөмж болон сүлжээний үйл ажиллагааг хянах боломжтой."</string>
- <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг.\n\nТаны админ имэйл, апп болон вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу.\n\nТа сүлжээний үйл ажиллагааг хянах боломжтой VPN-д холбогдсон байна."</string>
+ <string name="monitoring_description_vpn" msgid="1685428000684586870">"Та апп-д VPN холболт хийхийг зөвшөөрсөн байна.\n\nЭнэхүү апп нь таны имэйл, апп, вебсайт зэрэг төхөөрөмж болон сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+ <string name="monitoring_description_vpn_profile_owned" msgid="4964237035412372751">"<xliff:g id="ORGANIZATION">%1$s</xliff:g> таны ажлын профайлыг удирддаг.\n\nТаны админ имэйл, апп болон веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой.\n\nДэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу.\n\nТа сүлжээний үйл ажиллагааг хянах боломжтой VPN-д холбогдсон байна."</string>
<string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
- <string name="monitoring_description_app" msgid="376868879287922929">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
- <string name="monitoring_description_app_personal" msgid="1970094872688265987">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вэбсайт зэрэг сүлжээний хувийн үйл ажиллагааг хянах боломжтой."</string>
- <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"Та имэйл, апп, вэб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
- <string name="monitoring_description_app_work" msgid="3713084153786663662">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-д холбогдсон. \n\nДэлгэрэнгүй мэдээллийг авахын тулд админтай холбогдоно уу."</string>
- <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, вэб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон. \n\nМөн таны сүлжээний хувийн үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="monitoring_description_app" msgid="376868879287922929">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вебсайт зэрэг сүлжээний үйл ажиллагааг хянах боломжтой."</string>
+ <string name="monitoring_description_app_personal" msgid="1970094872688265987">"Та <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон бөгөөд энэ нь таны имэйл, апп, вебсайт зэрэг сүлжээний хувийн үйл ажиллагааг хянах боломжтой."</string>
+ <string name="branded_monitoring_description_app_personal" msgid="1703511985892688885">"Та имэйл, апп, веб хуудас зэрэг хувийн сүлжээнийхээ үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%1$s</xliff:g>-д холбогдсон байна."</string>
+ <string name="monitoring_description_app_work" msgid="3713084153786663662">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, веб хуудас зэрэг ажлын сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION">%2$s</xliff:g>-д холбогдсон. \n\nДэлгэрэнгүй мэдээллийг авахын тулд админтай холбогдоно уу."</string>
+ <string name="monitoring_description_app_personal_work" msgid="6175816356939166101">"Таны ажлын профайлыг <xliff:g id="ORGANIZATION">%1$s</xliff:g> удирддаг. Энэ нь таны имэйл, апп, веб хуудас зэрэг сүлжээний үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>-тай холбогдсон. \n\nМөн таны сүлжээний хувийн үйл ажиллагааг хянах боломжтой <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>-д холбогдсон байна."</string>
<string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent-р түгжээгүй байлгасан"</string>
<string name="keyguard_indication_trust_disabled" msgid="6820793704816727918">"Таныг гараар нээх хүртэл төхөөрөмж түгжээтэй байх болно"</string>
<string name="keyguard_indication_trust_unlocked_plugged_in" msgid="2323452175329362855">"<xliff:g id="KEYGUARD_INDICATION">%1$s</xliff:g>\n<xliff:g id="POWER_INDICATION">%2$s</xliff:g>"</string>
@@ -653,8 +653,8 @@
<string name="status_bar_alarm" msgid="87160847643623352">"Сэрүүлэг"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string>
- <string name="add_tile" msgid="6239678623873086686">"Вэбсайтын цонх нэмэх"</string>
- <string name="broadcast_tile" msgid="5224010633596487481">"Вэбсайтын цонх дамжуулах"</string>
+ <string name="add_tile" msgid="6239678623873086686">"Вебсайтын цонх нэмэх"</string>
+ <string name="broadcast_tile" msgid="5224010633596487481">"Вебсайтын цонх дамжуулах"</string>
<string name="zen_alarm_warning_indef" msgid="5252866591716504287">"Та өмнө нь унтраагаагүй тохиолдолд <xliff:g id="WHEN">%1$s</xliff:g>-т сэрүүлгээ сонсохгүй"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string>
<string name="alarm_template" msgid="2234991538018805736">"<xliff:g id="WHEN">%1$s</xliff:g> цагт"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Бага ач холбогдолтой мэдэгдлийн дүрс тэмдгийг харуулах"</string>
<string name="other" msgid="429768510980739978">"Бусад"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"хавтанг хасна уу"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"дуусгахын тулд хавтан нэмэх"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Хавтанг зөөх"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Хавтан нэмэх"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> руу зөөнө үү"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> байрлалд нэмнэ үү"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> байрлал"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Түргэн тохиргоо засварлагч."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> мэдэгдэл: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Тохиргоог нээнэ үү."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Тохиргоо"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Томруулалтын цонх"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Томруулалтын цонхны хяналт"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Томруулах"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Жижигрүүлэх"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Дээш зөөх"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Доош зөөх"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Зүүн тийш зөөх"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Баруун тийш зөөх"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Томруулах сэлгэлт"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Дэлгэцийг бүхэлд нь томруулах"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Дэлгэцийн нэг хэсгийг томруулах"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Сэлгэх"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Төхөөрөмжийн хяналт"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Холбогдсон төхөөрөмжүүд дээрээ хяналт нэмэх"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Төхөөрөмжийн хяналтыг тохируулах"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (салсан)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Холбогдож чадсангүй. Дахин оролдоно уу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Шинэ төхөөрөмж хослуулах"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Хийгдсэн дугаар"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Хийгдсэн дугаарыг түр санах ойд хуулсан."</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 728065fbd5cc..40a51c77d4df 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कमी प्राधान्य सूचना आयकन दर्शवा"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल काढून टाका"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"टाइल शेवटच्या स्थानावर जोडा"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल हलवा"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल जोडा"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> यावर हलवा"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> स्थानावर जोडा"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थान <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिंग्ज संपादक."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> सूचना: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"सेटिंग्ज उघडा."</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"सेटिंग्ज"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"मॅग्निफिकेशन विंडो"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"मॅग्निफिकेशन विंडो नियंत्रणे"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"झूम इन करा"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"झूम आउट करा"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"वर हलवा"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"खाली हलवा"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"डावीकडे हलवा"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"उजवीकडे हलवा"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"डिव्हाइस नियंत्रणे"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"तुमच्या कनेक्ट केलेल्या डिव्हाइससाठी नियंत्रणे जोडा"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"डिव्हाइस नियंत्रणे सेट करा"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट केले)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट करू शकलो नाही. पुन्हा प्रयत्न करा."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नवीन डिव्हाइससोबत पेअर करा"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नंबर"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नंबर क्लिपबोर्डवर कॉपी केला."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 62d845c3f72a..ac22f4c37b56 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Tunjukkan ikon pemberitahuan keutamaan rendah"</string>
<string name="other" msgid="429768510980739978">"Lain-lain"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"alih keluar jubin"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"tambahkan jubin pada bahagian hujung"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Alihkan jubin"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan jubin"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Alih ke <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan pada kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor tetapan pantas."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Pemberitahuan <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Buka tetapan."</string>
@@ -975,7 +968,7 @@
<string name="auto_saver_enabled_text" msgid="7889491183116752719">"Penjimat Bateri akan dihidupkan secara automatik setelah kuasa bateri kurang daripada <xliff:g id="PERCENTAGE">%d</xliff:g>%%."</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"Tetapan"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"OK"</string>
- <string name="heap_dump_tile_name" msgid="2464189856478823046">"Longgok Tmbunn SysUI"</string>
+ <string name="heap_dump_tile_name" msgid="2464189856478823046">"DumpSys"</string>
<string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"<xliff:g id="APP">%1$s</xliff:g> sedang menggunakan <xliff:g id="TYPES_LIST">%2$s</xliff:g> anda."</string>
<string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikasi sedang menggunakan <xliff:g id="TYPES_LIST">%s</xliff:g> anda."</string>
<string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Tetapan"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Tetingkap Pembesaran"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kawalan Tetingkap Pembesaran"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zum masuk"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zum keluar"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Alih ke atas"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Alih ke bawah"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Alih ke kiri"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Alih ke kanan"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Suis pembesaran"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Besarkan seluruh skrin"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Besarkan sebahagian skrin"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Tukar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kawalan peranti"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Tambah kawalan untuk peranti yang disambungkan"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Sediakan kawalan peranti"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (diputuskan sambungan)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Tidak boleh menyambung. Cuba lagi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Gandingkan peranti baharu"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nombor binaan"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Nombor binaan disalin ke papan keratan."</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index e72f16925eab..c92900cd825d 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"အရေးမကြီးသော အကြောင်းကြားချက် သင်္ကေတများ ပြရန်"</string>
<string name="other" msgid="429768510980739978">"အခြား"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"အကွက်ငယ်ကို ဖယ်ရှားရန်"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"အဆုံးတွင် အကွက်ငယ်ထည့်ရန်"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"အကွက်ငယ်ကို ရွှေ့ရန်"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"အကွက်ငယ်ကို ထည့်ရန်"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> သို့ ရွှေ့ရန်"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထားသို့ ပေါင်းထည့်ရန်"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> အနေအထား"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"မြန်ဆန်သည့် ဆက်တင်တည်းဖြတ်စနစ်"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> အကြောင်းကြားချက် − <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ဆက်တင်များကို ဖွင့်ပါ။"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ဆက်တင်များ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"ဝင်းဒိုး ချဲ့ခြင်း"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"ဝင်းဒိုး ထိန်းချုပ်မှုများ ချဲ့ခြင်း"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ဇူးမ်ဆွဲရန်"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ဇူးမ်ဖြုတ်ရန်"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"အပေါ်သို့ရွှေ့ရန်"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"အောက်သို့ရွှေ့ရန်"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ဘယ်ဘက်သို့ရွှေ့ရန်"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ညာဘက်သို့ရွှေ့ရန်"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ချဲ့ရန် ခလုတ်"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ဖန်သားပြင် တစ်ခုလုံးကို ချဲ့ပါ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်းကို ချဲ့ပါ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ခလုတ်"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"စက်ထိန်းစနစ်"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ချိတ်ဆက်စက်များအတွက် ထိန်းချုပ်မှုများထည့်ပါ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"စက်ထိန်းစနစ် ထည့်သွင်းခြင်း"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ချိတ်ဆက်မထားပါ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ချိတ်ဆက်၍ မရပါ။ ထပ်စမ်းကြည့်ပါ။"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"စက်အသစ် တွဲချိတ်ရန်"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"တည်ဆောက်မှုနံပါတ်"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"တည်ဆောက်မှုနံပါတ်ကို ကလစ်ဘုတ်သို့ မိတ္တူကူးပြီးပါပြီ။"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 41aabcb89b65..2920af3da628 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Vis ikoner for varsler med lav prioritet"</string>
<string name="other" msgid="429768510980739978">"Annet"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"fjerne infobrikken"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"legge til en infobrikke på slutten"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flytt infobrikken"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Legg til en infobrikke"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytt til <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Legg til posisjonen <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisjon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigeringsvindu for hurtiginnstillinger."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-varsel: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Åpne innstillingene."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Innstillinger"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Forstørringsvindu"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontroller for forstørringsvindu"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom inn"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom ut"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Flytt opp"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flytt ned"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flytt til venstre"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flytt til høyre"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Forstørringsbryter"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Forstørr hele skjermen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Forstørr en del av skjermen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Bytt"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyring"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Legg til kontroller for de tilkoblede enhetene dine"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurer enhetsstyring"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frakoblet)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kunne ikke koble til. Prøv på nytt."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Koble til en ny enhet"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delversjonsnummer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Delversjonsnummeret er kopiert til utklippstavlen."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 0cf09d2bdd0d..6b77bb2ebeb0 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -339,7 +339,7 @@
<string name="dessert_case" msgid="9104973640704357717">"Dessert Case"</string>
<string name="start_dreams" msgid="9131802557946276718">"स्क्रिन सेभर"</string>
<string name="ethernet_label" msgid="2203544727007463351">"Ethernet"</string>
- <string name="quick_settings_header_onboarding_text" msgid="1918085351115504765">"थप विकल्पहरूका लागि आइकनहरूमा छोइराख्नुहोस्"</string>
+ <string name="quick_settings_header_onboarding_text" msgid="1918085351115504765">"थप विकल्पहरूका लागि आइकनहरूमा टच एण्ड होल्ड गर्नुहोस्"</string>
<string name="quick_settings_dnd_label" msgid="7728690179108024338">"बाधा नपुऱ्याउनुहोस्"</string>
<string name="quick_settings_dnd_priority_label" msgid="6251076422352664571">"प्राथमिकता मात्र"</string>
<string name="quick_settings_dnd_alarms_label" msgid="1241780970469630835">"अलार्महरू मात्र"</string>
@@ -591,16 +591,16 @@
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"असक्षम पार्नुहोस्"</string>
<string name="accessibility_output_chooser" msgid="7807898688967194183">"आउटपुट यन्त्र बदल्नुहोस्"</string>
<string name="screen_pinning_title" msgid="9058007390337841305">"एप पिन गरिएको छ"</string>
- <string name="screen_pinning_description" msgid="8699395373875667743">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र परिदृश्य बटनलाई छोइराख्नुहोस्।"</string>
- <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्।"</string>
+ <string name="screen_pinning_description" msgid="8699395373875667743">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र परिदृश्य बटनलाई टच एण्ड होल्ड गर्नुहोस्।"</string>
+ <string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न पछाडि र गृह नामक बटनहरूलाई टच एण्ड होल्ड गर्नुहोस्।"</string>
<string name="screen_pinning_description_gestural" msgid="7246323931831232068">"तपाईंले यो एप अनपिन नगरेसम्म यो एप यहाँ देखिइरहने छ। अनपिन गर्न माथितिर स्वाइप गरी होल्ड गर्नुहोस्।"</string>
- <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न परिदृश्य बटनलाई छोइराख्नुहोस्।"</string>
- <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न गृह नामक बटनलाई छोइराख्नुहोस्।"</string>
+ <string name="screen_pinning_description_accessible" msgid="7386449191953535332">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न परिदृश्य बटनलाई टच एण्ड होल्ड गर्नुहोस्।"</string>
+ <string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न गृह नामक बटनलाई टच एण्ड होल्ड गर्नुहोस्।"</string>
<string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"स्क्रिनमा व्यक्तिगत डेटा (जस्तै सम्पर्क ठेगाना र इमेलको सामग्री) देखिन सक्छ।"</string>
<string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"पिन गरिएको एपले अन्य एप खोल्न सक्छ।"</string>
- <string name="screen_pinning_toast" msgid="8177286912533744328">"यो एप अनपनि गर्न पछाडि र विवरण नामक बटनहरूलाई छोइराख्नुहोस्"</string>
- <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"यो एप अनपनि गर्न पछाडि र होम बटनलाई छोइराख्नुहोस्"</string>
- <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"यो एप अनपिन गर्न माथितिर स्वाइप गरी स्क्रिनमा छोइराख्नुहोस्"</string>
+ <string name="screen_pinning_toast" msgid="8177286912533744328">"यो एप अनपनि गर्न पछाडि र विवरण नामक बटनहरूलाई टच एण्ड होल्ड गर्नुहोस्"</string>
+ <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"यो एप अनपनि गर्न पछाडि र होम बटनलाई टच एण्ड होल्ड गर्नुहोस्"</string>
+ <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"यो एप अनपिन गर्न माथितिर स्वाइप गरी स्क्रिनमा टच एण्ड होल्ड गर्नुहोस्"</string>
<string name="screen_pinning_positive" msgid="3285785989665266984">"बुझेँ"</string>
<string name="screen_pinning_negative" msgid="6882816864569211666">"धन्यवाद पर्दैन"</string>
<string name="screen_pinning_start" msgid="7483998671383371313">"एप पिन गरियो"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"कम प्राथमिकताका सूचना आइकनहरू देखाउनुहोस्"</string>
<string name="other" msgid="429768510980739978">"अन्य"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"टाइल हटाउनुहोस्"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"टाइल अन्त्यमा हाल्नुहोस्"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"टाइल सार्नुहोस्"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"टाइल हाल्नुहोस्"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"टाइल सारेर <xliff:g id="POSITION">%1$d</xliff:g> मा लैजानुहोस्"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"टाइल यो अवस्था <xliff:g id="POSITION">%1$d</xliff:g> मा हाल्नुहोस्"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"स्थिति <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"द्रुत सेटिङ सम्पादक।"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> को सूचना: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"सेटिङहरूलाई खोल्नुहोस्।"</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"सेटिङ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"म्याग्निफिकेसन विन्डो"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"म्याग्निफिकेसन विन्डोका नियन्त्रणहरू"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"जुम इन गर्नुहोस्"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"जुम आउट गर्नुहोस्"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"माथि सार्नुहोस्"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"तल सार्नुहोस्"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"बायाँ सार्नुहोस्"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"दायाँ सार्नुहोस्"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"यन्त्र नियन्त्रण गर्ने विजेटहरू"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"आफ्ना जोडिएका यन्त्रहरूका लागि नियन्त्रण सुविधाहरू थप्नुहोस्"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"यन्त्र नियन्त्रण गर्ने विजेटहरू सेटअप गर्नुहोस्"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (डिस्कनेक्ट गरिएको)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"कनेक्ट गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"नयाँ यन्त्रको जोडा बनाउनुहोस्"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"बिल्ड नम्बर"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"बिल्ड नम्बर कपी गरी क्लिपबोर्डमा सारियो।"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 49e097e5a047..b90cd3000dc7 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pictogrammen voor meldingen met lage prioriteit weergeven"</string>
<string name="other" msgid="429768510980739978">"Overig"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"tegel verwijderen"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"tegel toevoegen aan einde"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Tegel verplaatsen"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tegel toevoegen"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Verplaatsen naar <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Toevoegen aan positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Positie <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor voor \'Snelle instellingen\'."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-melding: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Instellingen openen."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Instellingen"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Vergrotingsvenster"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Bediening van vergrotingsvenster"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Inzoomen"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Uitzoomen"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Omhoog verplaatsen"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Omlaag verplaatsen"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Naar links verplaatsen"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Naar rechts verplaatsen"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Vergrotingsschakelaar"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Hele scherm vergroten"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Deel van het scherm vergroten"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Schakelen"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Apparaatbediening"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Bedieningselementen voor je gekoppelde apparaten toevoegen"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Apparaatbediening instellen"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (verbinding verbroken)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Kan geen verbinding maken. Probeer het nog eens."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Nieuw apparaat koppelen"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Build-nummer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Build-nummer naar klembord gekopieerd."</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index c3105ff55bc7..fd7692aaac68 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -861,7 +861,7 @@
<string name="right_keycode" msgid="2480715509844798438">"ଡାହାଣ କୀ\'କୋଡ୍‍"</string>
<string name="left_icon" msgid="5036278531966897006">"ବାମ ଆଇକନ୍‍"</string>
<string name="right_icon" msgid="1103955040645237425">"ଡାହାଣ ଆଇକନ୍"</string>
- <string name="drag_to_add_tiles" msgid="8933270127508303672">"ଟାଇଲ୍ ଯୋଡ଼ିବା ପାଇଁ ଦାବିଧରି ଟାଣନ୍ତୁ"</string>
+ <string name="drag_to_add_tiles" msgid="8933270127508303672">"ଟାଇଲ୍ ଯୋଗ କରିବା ପାଇଁ ଦାବିଧରି ଟାଣନ୍ତୁ"</string>
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"ଟାଇଲ୍‍ ପୁଣି ସଜାଇବାକୁ ଦାବିଧରି ଟାଣନ୍ତୁ"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"ବାହାର କରିବାକୁ ଏଠାକୁ ଡ୍ରାଗ୍‍ କରନ୍ତୁ"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"ଆପଣଙ୍କର ଅତିକମ୍‌ରେ <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g>ଟି ଟାଇଲ୍ ଆବଶ୍ୟକ"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"କମ୍‍-ଅଗ୍ରାଧିକାର ବିଜ୍ଞପ୍ତି ଆଇକନ୍‍ ଦେଖାନ୍ତୁ"</string>
<string name="other" msgid="429768510980739978">"ଅନ୍ୟାନ୍ୟ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ଟାଇଲ୍ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ଶେଷରେ ଟାଇଲ୍ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ଟାଇଲ୍ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ଟାଇଲ୍ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>କୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ଅବସ୍ଥିତିରେ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ଅବସ୍ଥିତି <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ଦ୍ରୁତ ସେଟିଙ୍ଗ ଏଡିଟର୍।"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> ବିଜ୍ଞପ୍ତି: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ସେଟିଂସ୍ ଖୋଲନ୍ତୁ।"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ସେଟିଂସ୍"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ୱିଣ୍ଡୋ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ଜୁମ୍ ଇନ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ଜୁମ୍ ଆଉଟ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ଉପରକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ତଳକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"ମ୍ୟାଗ୍ନିଫିକେସନ୍ ସ୍ୱିଚ୍"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ସ୍କ୍ରିନର ଅଂଶ ମାଗ୍ନିଫାଏ କରନ୍ତୁ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ସ୍ୱିଚ୍ କରନ୍ତୁ"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ଆପଣଙ୍କ ସଂଯୁକ୍ତ ଡିଭାଇସଗୁଡ଼ିକ ପାଇଁ ନିୟନ୍ତ୍ରଣ ଯୋଗ କରନ୍ତୁ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ନୂଆ ଡିଭାଇସକୁ ପେୟାର୍ କରନ୍ତୁ"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ବିଲ୍ଡ ନମ୍ୱର"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"କ୍ଲିପବୋର୍ଡକୁ କପି କରାଯାଇଥିବା ବିଲ୍ଡ ନମ୍ୱର।"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index b12dfb3954ee..19715c56c793 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"ਘੱਟ ਤਰਜੀਹ ਵਾਲੇ ਸੂਚਨਾ ਪ੍ਰਤੀਕਾਂ ਨੂੰ ਦਿਖਾਓ"</string>
<string name="other" msgid="429768510980739978">"ਹੋਰ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ਟਾਇਲ ਹਟਾਓ"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ਟਾਇਲ ਨੂੰ ਅੰਤ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ਟਾਇਲ ਨੂੰ ਲਿਜਾਓ"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ਟਾਇਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> \'ਤੇ ਲਿਜਾਓ"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ਸਥਾਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ਸਥਾਨ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ ਸੰਪਾਦਕ।"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> ਸੂਚਨਾ: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ।"</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ਸੈਟਿੰਗਾਂ"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"ਵੱਡਦਰਸ਼ੀਕਰਨ Window"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"ਵੱਡਦਰਸ਼ੀਕਰਨ Window ਦੇ ਕੰਟਰੋਲ"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ਜ਼ੂਮ ਵਧਾਓ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ਜ਼ੂਮ ਘਟਾਓ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ਉੱਪਰ ਲਿਜਾਓ"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ਹੇਠਾਂ ਲਿਜਾਓ"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ਖੱਬੇ ਲਿਜਾਓ"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ਸੱਜੇ ਲਿਜਾਓ"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"ਡੀਵਾਈਸ ਕੰਟਰੋਲ"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ਆਪਣੇ ਕਨੈਕਟ ਕੀਤੇ ਡੀਵਾਈਸਾਂ ਲਈ ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ਡੀਵਾਈਸ ਕੰਟਰੋਲਾਂ ਦਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ਡਿਸਕਨੈਕਟ ਹੋਇਆ)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"ਨਵਾਂ ਡੀਵਾਈਸ ਜੋੜਾਬੱਧ ਕਰੋ"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"ਬਿਲਡ ਨੰਬਰ"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"ਬਿਲਡ ਨੰਬਰ ਨੂੰ ਕਲਿੱਪਬੋਰਡ \'ਤੇ ਕਾਪੀ ਕੀਤਾ ਗਿਆ।"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index d195977f460b..19cd0e9e7c6c 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokazuj ikony powiadomień o niskim priorytecie"</string>
<string name="other" msgid="429768510980739978">"Inne"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"usunąć kartę"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"dodać kartę na końcu"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Przenieś kartę"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodaj kartę"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Przenieś do pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodaj w pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozycja <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Edytor szybkich ustawień."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Powiadomienie z aplikacji <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otwórz ustawienia."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ustawienia"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Okno powiększenia"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Elementy sterujące okna powiększenia"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Powiększ"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Pomniejsz"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Przesuń w górę"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Przesuń w dół"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Przesuń w lewo"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Przesuń w prawo"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Przełączanie powiększenia"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Powiększ cały ekran"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Powiększ część ekranu"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Przełącz"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Sterowanie urządzeniami"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodaj elementy sterujące połączonymi urządzeniami"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurowanie sterowania urządzeniami"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (rozłączono)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nie udało się połączyć. Spróbuj ponownie."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sparuj nowe urządzenie"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numer kompilacji"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Numer kompilacji został skopiowany do schowka."</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 6f96f37bc302..f8cf8810c9ff 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adicionar o bloco ao final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificação do <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir configurações."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Configurações"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Controles da janela de ampliação"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Aumentar zoom"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Diminuir zoom"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover para cima"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover para baixo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover para a esquerda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover para a direita"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Chave de ampliação"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar toda a tela"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Trocar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Adiciona controles aos dispositivos conectados"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar controles do dispositivo"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 93999f6b7b49..0b1c5e31d3bf 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -861,7 +861,7 @@
<string name="right_keycode" msgid="2480715509844798438">"Código de tecla direito"</string>
<string name="left_icon" msgid="5036278531966897006">"Ícone esquerdo"</string>
<string name="right_icon" msgid="1103955040645237425">"Ícone direito"</string>
- <string name="drag_to_add_tiles" msgid="8933270127508303672">"Toque sem soltar e arraste para adicionar mosaicos."</string>
+ <string name="drag_to_add_tiles" msgid="8933270127508303672">"Tocar sem soltar e arrastar para adicionar mosaicos"</string>
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Tocar sem soltar e arrastar para reorganizar os mosaicos"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrastar para aqui para remover"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Necessita de, pelo menos, <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> cartões"</string>
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de prioridade baixa"</string>
<string name="other" msgid="429768510980739978">"Outro"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o cartão"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adicionar o cartão ao final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover cartão"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar cartão"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mova para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicione à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de definições rápidas."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificação do <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir as definições."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Definições"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Controlos da janela de ampliação"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Aumentar zoom"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Diminuir zoom"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover para cima"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover para baixo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover para a esquerda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover para a direita"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Interruptor de ampliação"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar o ecrã inteiro"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte do ecrã"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Mudar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controlos de dispositivos"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Adicione controlos para os dispositivos associados."</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configure os controlos de dispositivos"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desligado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível ligar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Sincronize o novo dispositivo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da compilação"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Número da compilação copiado para a área de transferência."</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 6f96f37bc302..f8cf8810c9ff 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Mostrar ícones de notificações de baixa prioridade"</string>
<string name="other" msgid="429768510980739978">"Outros"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"remover o bloco"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adicionar o bloco ao final"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mover bloco"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor de configurações rápidas."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificação do <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Abrir configurações."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Configurações"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Janela de ampliação"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Controles da janela de ampliação"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Aumentar zoom"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Diminuir zoom"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Mover para cima"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Mover para baixo"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Mover para a esquerda"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Mover para a direita"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Chave de ampliação"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ampliar toda a tela"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ampliar parte da tela"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Trocar"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Adiciona controles aos dispositivos conectados"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurar controles do dispositivo"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (desconectado)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Não foi possível conectar. Tente novamente."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parear novo dispositivo"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Número da versão"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Número da versão copiado para a área de transferência."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 9810cc5540cb..35021611a06c 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Afișați pictogramele de notificare cu prioritate redusă"</string>
<string name="other" msgid="429768510980739978">"Altele"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"eliminați cardul"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"adăugați cardul la sfârșit"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Mutați cardul"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adăugați un card"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mutați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adăugați pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editorul pentru setări rapide."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notificare <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Deschideți setările."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Setări"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Fereastra de mărire"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Comenzi pentru fereastra de mărire"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Măriți"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Micșorați"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Deplasați în sus"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Deplasați în jos"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Deplasați spre stânga"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Deplasați spre dreapta"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Comutator de mărire"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Măriți întregul ecran"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Măriți o parte a ecranului"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Comutator"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Adăugați comenzi pentru dispozitivele conectate"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Configurați comenzile dispozitivelor"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (s-a deconectat)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nu s-a putut conecta. Reîncercați."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Asociați un nou dispozitiv"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numărul versiunii"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Numărul versiunii s-a copiat în clipboard."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 9f43b5ac7f16..7f257a1c6b06 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показывать значки уведомлений с низким приоритетом"</string>
<string name="other" msgid="429768510980739978">"Другое"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"удалить панель"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"добавить панель в конец"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Переместить панель"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Добавить панель"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Переместить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Добавить на позицию <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиция <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор быстрых настроек."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Уведомление <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Открыть настройки."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Настройки"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Окно увеличения"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Настройки окна увеличения"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Увеличить"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Уменьшить"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Переместить вверх"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Переместить вниз"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Переместить влево"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Переместить вправо"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Переключатель режима увеличения"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увеличить весь экран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увеличить часть экрана"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Переключить"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Управление устройствами"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Добавьте виджеты для управления устройствами."</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Настройте виджеты управления устройствами"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (отключено)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не удалось подключиться. Повторите попытку."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Подключить новое устройство"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер сборки"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Номер сборки скопирован в буфер обмена."</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 700f5101475c..ac743d318579 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"අඩු ප්‍රමුඛතා දැනුම්දීම් අයිකන පෙන්වන්න"</string>
<string name="other" msgid="429768510980739978">"වෙනත්"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ටයිල් ඉවත් කරන්න"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"අගට ටයිල් එක් කරන්න"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ටයිල් ගෙන යන්න"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ටයිල් එක් කරන්න"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> වෙත ගෙන යන්න"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> ස්ථානයට එක් කරන්න"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ස්ථානය <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ඉක්මන් සැකසුම් සංස්කාරකය."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> දැනුම්දීම: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"සැකසීම් විවෘත කරන්න."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"සැකසීම්"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"විශාලන කවුළුව"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"විශාලනය කිරීමේ කවුළු පාලන"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"විශාලනය වැඩි කරන්න"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"විශාලනය අඩු කරන්න"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ඉහළට ගෙන යන්න"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"පහළට ගෙන යන්න"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"වමට ගෙන යන්න"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"දකුණට ගෙන යන්න"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"විශාලන ස්විචය"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"සම්පූර්ණ තිරය විශාලනය කරන්න"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"තිරයේ කොටසක් විශාලනය කරන්න"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ස්විචය"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"උපාංග පාලන"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"ඔබේ සම්බන්ධිත උපාංග සඳහා පාලන එක් කරන්න"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"උපාංග පාලන පිහිටුවන්න"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (විසන්ධි විය)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"සම්බන්ධ වීමට නොහැකි විය. නැවත උත්සාහ කරන්න."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"නව උපාංගය යුගල කරන්න"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"නිමැවුම් අංකය"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"නිමැවුම් අංකය පසුරු පුවරුවට පිටපත් කරන ලදි."</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 62df35aeee02..d29d1e99d635 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Zobraziť ikony upozornení s nízkou prioritou"</string>
<string name="other" msgid="429768510980739978">"Ďalšie"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstrániť kartu"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"pridať kartu na koniec"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Presunúť kartu"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridať kartu"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Presunúť na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridať na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozícia"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor rýchlych nastavení"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Upozornenie <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Otvoriť nastavenia"</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Nastavenia"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Okno priblíženia"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Ovládacie prvky okna priblíženia"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Priblížiť"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Oddialiť"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Posunúť nahor"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Posunúť nadol"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Posunúť doľava"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Posunúť doprava"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Prepínač zväčenia"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Zväčšiť celú obrazovku"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Zväčšiť časť obrazovky"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Prepnúť"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Ovládanie zariadení"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Pridajte si ovládače pripojených zariadení"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavenie ovládania zariadení"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (odpojené)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nepodarilo sa pripojiť. Skúste to znova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Spárovať nové zariadenie"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Číslo zostavy"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Číslo zostavy bolo skopírované do schránky."</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 25bdd656b56c..6cb0ccd2df9c 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Pokaži ikone obvestil z nizko stopnjo prednosti"</string>
<string name="other" msgid="429768510980739978">"Drugo"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"odstranitev ploščice"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"dodajanje ploščice na konec"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Premik ploščice"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajanje ploščice"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premik na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Urejevalnik hitrih nastavitev."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Obvestilo za <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Odpri nastavitve."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Nastavitve"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Povečevalno okno"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrolniki povečevalnega okna"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Povečaj"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Pomanjšaj"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Premakni navzgor"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Premakni navzdol"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Premakni levo"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Premakni desno"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Stikalo za povečavo"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Povečava celotnega zaslona"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Povečava dela zaslona"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Stikalo"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrolniki naprave"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Dodajte kontrolnike za povezane naprave"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Nastavitev kontrolnikov naprave"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (povezava prekinjena)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Povezave ni bilo mogoče vzpostaviti. Poskusite znova."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Seznanitev nove naprave"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Delovna različica"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Delovna različica je bila kopirana v odložišče."</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index abcde4286190..154d53dd6d6f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -246,7 +246,7 @@
<string name="accessibility_remove_notification" msgid="1641455251495815527">"Pastro njoftimin."</string>
<string name="accessibility_gps_enabled" msgid="4061313248217660858">"GPS-ja është e aktivizuar."</string>
<string name="accessibility_gps_acquiring" msgid="896207402196024040">"Po siguron GPS-në."</string>
- <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teleprinteri është i aktivizuar."</string>
+ <string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletajpi është i aktivizuar."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zile me dridhje."</string>
<string name="accessibility_ringer_silent" msgid="8994620163934249882">"Zilja është heshtur."</string>
<!-- no translation found for accessibility_casting (8708751252897282313) -->
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Shfaq ikonat e njoftimeve me përparësi të ulët"</string>
<string name="other" msgid="429768510980739978">"Të tjera"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"hiq pllakëzën"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"shto pllakëzën në fund"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Zhvendos pllakëzën"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Shto pllakëzën"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Zhvendos te <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redaktori i cilësimeve të shpejta."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Njoftim nga <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Hap cilësimet."</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Cilësimet"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Dritarja e zmadhimit"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kontrollet e dritares së zmadhimit"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zmadho"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zvogëlo"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Lëvize lart"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Lëvize poshtë"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Lëvize majtas"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Lëvize djathtas"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"Kontrollet e pajisjes"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Shto kontrolle për pajisjet e tua të lidhura"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfiguro kontrollet e pajisjes"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (e shkëputur)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Nuk mund të lidhej. Provo sërish."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Çifto pajisjen e re"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numri i ndërtimit"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Numri i ndërtimit u kopjua te kujtesa e fragmenteve"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index e117602afa9a..687a1bd93e4c 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -884,20 +884,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Прикажи иконе обавештења ниског приоритета"</string>
<string name="other" msgid="429768510980739978">"Друго"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"уклонили плочицу"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"додали плочицу на крај"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Преместите плочицу"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додајте плочицу"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Преместите на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додајте на <xliff:g id="POSITION">%1$d</xliff:g>. позицију"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. позиција"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Уређивач за Брза подешавања."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Обавештења за <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Отвори Подешавања."</string>
@@ -1024,6 +1017,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Подешавања"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Прозор за увећање"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Контроле прозора за увећање"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Увећајте"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Умањите"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Померите нагоре"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Померите надоле"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Померите налево"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Померите надесно"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Прелазак на други режим увећања"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Увећајте цео екран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Увећајте део екрана"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Пређи"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Контроле уређаја"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Додајте контроле за повезане уређаје"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Подесите контроле уређаја"</string>
@@ -1088,4 +1091,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (веза је прекинута)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Повезивање није успело. Пробајте поново."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Упари нови уређај"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Број верзије"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Број верзије је копиран у привремену меморију."</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 8559b0b0de2b..3dc89d883e5d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Visa ikoner för aviseringar med låg prioritet"</string>
<string name="other" msgid="429768510980739978">"Annat"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ta bort ruta"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"lägg till ruta i slutet"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Flytta ruta"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lägg till ruta"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytta till <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lägg till på position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Redigerare för snabbinställningar."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>-avisering: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Öppna inställningarna."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Inställningar"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Förstoringsfönster"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Inställningar för förstoringsfönster"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zooma in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zooma ut"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Flytta uppåt"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Flytta nedåt"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Flytta åt vänster"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Flytta åt höger"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Förstoringsreglage"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Förstora hela skärmen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Förstora en del av skärmen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Reglage"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyrning"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Lägg till snabbkontroller för anslutna enheter"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Konfigurera enhetsstyrning"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (frånkopplad)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Det gick inte att ansluta. Försök igen."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Parkoppla en ny enhet"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Versionsnummer"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Versionsnumret har kopierats till urklipp."</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 76b86eee8289..76c1cd4d3e4d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Onyesha aikoni za arifa zisizo muhimu"</string>
<string name="other" msgid="429768510980739978">"Nyingine"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ondoa kigae"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ongeza kigae mwishoni"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hamisha kigae"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ongeza kigae"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Hamishia kwenye <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ongeza kwenye nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Kihariri cha Mipangilio ya haraka."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Arifa kutoka <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Fungua mipangilio."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Mipangilio"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Dirisha la Ukuzaji"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Vidhibiti vya Dirisha la Ukuzaji"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Vuta karibu"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Sogeza mbali"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Sogeza juu"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Sogeza chini"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sogeza kushoto"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sogeza kulia"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Swichi ya ukuzaji"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Kuza skrini yote"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Kuza sehemu ya skrini"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Swichi"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Vidhibiti vya vifaa"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Weka vidhibiti vya vifaa ulivyounganisha"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Weka mipangilio ya vidhibiti vya vifaa"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (hakijaunganishwa)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Imeshindwa kuunganisha. Jaribu tena."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Oanisha kifaa kipya"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nambari ya muundo"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Nambari ya muundo imewekwa kwenye ubao wa kunakili."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9d5c54fc7890..7e4ed357d541 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"குறைந்த முன்னுரிமை உள்ள அறிவிப்பு ஐகான்களைக் காட்டு"</string>
<string name="other" msgid="429768510980739978">"மற்றவை"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"கட்டத்தை அகற்றும்"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"கடைசியில் கட்டத்தைச் சேர்க்கும்"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"கட்டத்தை நகர்த்து"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"கட்டத்தைச் சேர்"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>க்கு நகர்த்தும்"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g>ல் சேர்க்கும்"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"இடம்: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"விரைவு அமைப்புகள் திருத்தி."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> அறிவிப்பு: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"அமைப்புகளைத் திற."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"அமைப்புகள்"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"பெரிதாக்கல் சாளரம்"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"பெரிதாக்கல் சாளரக் கட்டுப்பாடுகள்"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"பெரிதாக்கு"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"சிறிதாக்கு"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"மேலே நகர்த்து"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"கீழே நகர்த்து"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"இடப்புறம் நகர்த்து"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"வலப்புறம் நகர்த்து"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"பெரிதாக்கல் ஸ்விட்ச்"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"முழுத்திரையைப் பெரிதாக்கும்"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"திரையின் ஒரு பகுதியைப் பெரிதாக்கும்"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"ஸ்விட்ச்"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"சாதனக் கட்டுப்பாடுகள்"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"இணைக்கப்பட்ட சாதனங்களில் கட்டுப்பாடுகளைச் சேர்க்கலாம்"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"சாதனக் கட்டுப்பாடுகளை அமைத்தல்"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (இணைப்பு துண்டிக்கப்பட்டது)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"இணைக்க முடியவில்லை. மீண்டும் முயலவும்."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"புதிய சாதனத்தை இணைத்தல்"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"பதிப்பு எண்"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"பதிப்பு எண் கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது."</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 925c958199ed..f26c8b4de480 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"తక్కువ ప్రాధాన్యత నోటిఫికేషన్ చిహ్నాలను చూపించు"</string>
<string name="other" msgid="429768510980739978">"ఇతరం"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"టైల్‌ను తీసివేయండి"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ముగించడానికి టైల్‌ను జోడించండి"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"టైల్‌ను తరలించండి"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"టైల్‌ను జోడించండి"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g>కు తరలించండి"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> స్థానానికి జోడించండి"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"స్థానం <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"శీఘ్ర సెట్టింగ్‌ల ఎడిటర్."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> నోటిఫికేషన్: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"సెట్టింగ్‌లను తెరవండి."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"సెట్టింగ్‌లు"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"మాగ్నిఫికేషన్ విండో"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"మాగ్నిఫికేషన్ నియంత్రణల విండో"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"దగ్గరగా జూమ్ చేయండి"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"దూరంగా జూమ్ చేయండి"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"పైకి పంపండి"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"కిందకి పంపండి"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ఎడమవైపుగా జరపండి"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"కుడివైపుగా జరపండి"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"మాగ్నిఫికేషన్ స్విచ్"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"స్క్రీన్ మొత్తాన్ని మాగ్నిఫై చేయండి"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"స్క్రీన్‌లో భాగాన్ని మాగ్నిఫై చేయండి"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"స్విచ్ చేయి"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"పరికరం నియంత్రణలు"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"మీ కనెక్ట్ అయిన పరికరాలకు నియంత్రణలను జోడించండి"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"పరికరం నియంత్రణలను సెటప్ చేయడం"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (డిస్‌కనెక్ట్ అయ్యింది)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"కనెక్ట్ చేయడం సాధ్యపడలేదు. మళ్లీ ట్రై చేయండి."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"కొత్త పరికరాన్ని పెయిర్ చేయండి"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"బిల్డ్ నంబర్"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"బిల్డ్ నంబర్, క్లిప్‌బోర్డ్‌కు కాపీ చేయబడింది."</string>
</resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 981a95312736..015ac90e2521 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -45,4 +45,13 @@
<!-- Show a separate icon for low and high volume on the volume dialog -->
<bool name="config_showLowMediaVolumeIcon">true</bool>
+
+ <!-- Change the volume row tint when it is inactive, i.e. when it is being dismissed -->
+ <bool name="config_changeVolumeRowTintWhenInactive">false</bool>
+
+ <!-- The duraction of the show animation for the volume dialog in milliseconds -->
+ <integer name="config_dialogShowAnimationDurationMs">600</integer>
+
+ <!-- The duraction of the hide animation for the volume dialog in milliseconds -->
+ <integer name="config_dialogHideAnimationDurationMs">400</integer>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index c613e9c0d7c0..9c61e5e45681 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"แสดงไอคอนการแจ้งเตือนลำดับความสำคัญต่ำ"</string>
<string name="other" msgid="429768510980739978">"อื่นๆ"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"นำชิ้นส่วนออก"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"เพิ่มชิ้นส่วนต่อท้าย"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ย้ายชิ้นส่วน"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"เพิ่มชิ้นส่วน"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"ย้ายไปที่ <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"เพิ่มไปยังตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"ตำแหน่ง <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"ตัวแก้ไขการตั้งค่าด่วน"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> การแจ้งเตือน: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"เปิดการตั้งค่า"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"การตั้งค่า"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"หน้าต่างการขยาย"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"การควบคุมหน้าต่างการขยาย"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"ซูมเข้า"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"ซูมออก"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"ย้ายขึ้น"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"ย้ายลง"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"ย้ายไปทางซ้าย"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"ย้ายไปทางขวา"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"เปลี่ยนการขยาย"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"ขยายทั้งหน้าจอ"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"ขยายบางส่วนของหน้าจอ"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"เปลี่ยน"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"ระบบควบคุมอุปกรณ์"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"เพิ่มตัวควบคุมของอุปกรณ์ที่เชื่อมต่อ"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"ตั้งค่าระบบควบคุมอุปกรณ์"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (ยกเลิกการเชื่อมต่อแล้ว)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"เชื่อมต่อไม่ได้ ลองใหม่"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"จับคู่อุปกรณ์ใหม่"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"หมายเลขบิวด์"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"คัดลอกหมายเลขบิวด์ไปยังคลิปบอร์ดแล้ว"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 9411957e56f2..be7bd06187c9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Ipakita ang mga icon ng notification na may mababang priority"</string>
<string name="other" msgid="429768510980739978">"Iba pa"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"alisin ang tile"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"idagdag ang tile sa dulo"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Ilipat ang tile"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Magdagdag ng tile"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Ilipat sa <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Idagdag sa posisyong <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Editor ng Mga mabilisang setting."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Notification sa <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Buksan ang mga setting."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Mga Setting"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Window ng Pag-magnify"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Mga Kontrol sa Pag-magnify ng Window"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Mag-zoom in"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Mag-zoom out"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Itaas"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Ibaba"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Ilipat pakaliwa"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Ilipat pakanan"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Switch ng pag-magnify"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"I-magnify ang buong screen"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"I-magnify ang isang bahagi ng screen"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Mga kontrol ng device"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Magdagdag ng kontrol para sa mga nakakonektang device"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"I-set up ang mga kontrol ng device"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (nakadiskonekta)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Hindi makakonekta. Subukan ulit."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Magpares ng bagong device"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Numero ng build"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Nakopya sa clipboard ang numero ng build."</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 4d46f93e2d25..5d48836533ed 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Düşük öncelikli bildirim simgelerini göster"</string>
<string name="other" msgid="429768510980739978">"Diğer"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"Karoyu kaldırmak için"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"Sona karo eklemek için"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Karoyu taşı"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Karo ekle"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna taşı"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna ekle"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Konum: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Hızlı ayar düzenleyicisi."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> bildirimi: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Ayarları aç."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Ayarlar"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Büyütme Penceresi"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Büyütme Penceresi Kontrolleri"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Yakınlaştır"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Uzaklaştır"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Yukarı taşı"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Aşağı taşı"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Sola taşı"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Sağa taşı"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Büyütme moduna geçin"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Ekranın tamamını büyütün"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekranın bir parçasını büyütün"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Geç"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Cihaz denetimleri"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Bağlı cihazlarınız için denetimler ekleyin"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Cihaz denetimlerini kur"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (bağlı değil)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Bağlanılamadı. Tekrar deneyin."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yeni cihaz eşle"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Derleme numarası"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Derleme numarası panoya kopyalandı."</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 399b53a91de6..3b5c44b0afdc 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -889,20 +889,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Показувати значки сповіщень із низьким пріоритетом"</string>
<string name="other" msgid="429768510980739978">"Інше"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"вилучити опцію"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"додати опцію в кінець"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Перемістити опцію"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Додати опцію"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перемістити на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Додати на позицію <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Позиція <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Редактор швидких налаштувань."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Сповіщення <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Відкрити налаштування."</string>
@@ -1029,6 +1022,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Налаштування"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Вікно збільшення"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Елементи керування вікна збільшення"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Наблизити"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Віддалити"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Перемістити вгору"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Перемістити вниз"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Перемістити ліворуч"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Перемістити праворуч"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Перемикач режиму збільшення"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Збільшити весь екран"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Збільшити частину екрана"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Перемкнути"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Керування пристроями"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Додайте елементи керування для підключених пристроїв"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Налаштувати елементи керування пристроями"</string>
@@ -1094,4 +1097,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (відключено)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Не вдалося підключитися. Повторіть спробу."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Підключити новий пристрій"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Номер складання"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Номер складання скопійовано в буфер обміну."</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 09e500d227a6..ffba2bdf6afa 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"کم ترجیحی اطلاع کے آئیکنز دکھائیں"</string>
<string name="other" msgid="429768510980739978">"دیگر"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"ٹائل ہٹائیں"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"ختم کرنے کے لیے ٹائل شامل کریں"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"ٹائل منتقل کریں"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"ٹائل شامل کریں"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> میں منتقل کریں"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g> میں شامل کریں"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"پوزیشن <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"فوری ترتیبات کا ایڈیٹر۔"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> اطلاع: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"ترتیبات کھولیں۔"</string>
@@ -1019,6 +1012,20 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"ترتیبات"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"میگنیفکیشن ونڈو"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"میگنیفکیشن ونڈو کنٹرولز"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"زوم ان کریں"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"زوم آؤٹ کریں"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"اوپر منتقل کریں"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"نیچے منتقل کریں"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"بائیں منتقل کریں"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"دائیں منتقل کریں"</string>
+ <!-- no translation found for magnification_mode_switch_description (2698364322069934733) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_full_screen (2882507327576770574) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_state_window (8597100249594076965) -->
+ <skip />
+ <!-- no translation found for magnification_mode_switch_click_label (2786203505805898199) -->
+ <skip />
<string name="quick_controls_title" msgid="6839108006171302273">"آلہ کے کنٹرولز"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"اپنے منسلک آلات کے لیے کنٹرولز شامل کریں"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"آلہ کے کنٹرولز سیٹ اپ کریں"</string>
@@ -1082,4 +1089,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (غیر منسلک ہو گیا)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"منسلک نہیں ہو سکا۔ پھر کوشش کریں۔"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"نئے آلہ کا جوڑا بنائیں"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"بلڈ نمبر"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"بلڈ نمبر کلپ بورڈ میں کاپی ہو گیا۔"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 61b91c768582..228b80d20027 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Muhim boʻlmagan bildirishnoma ikonkalarini koʻrsatish"</string>
<string name="other" msgid="429768510980739978">"Boshqa"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"katakchani olib tashlash"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"oxiriga katakcha kiritish"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Katakchani boshqa joyga olish"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Katakcha kiritish"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Bu joyga olish: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bu joyga kiritish: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Joylashuv: <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Tezkor sozlamalar muharriri"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> bildirishnomasi: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Sozlamalarni ochish."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Sozlamalar"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Kattalashtirish oynasi"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Kattalashtirish oynasi sozlamalari"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Yaqinlashtirish"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Uzoqlashtirish"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Tepaga siljitish"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Pastga siljitish"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Chapga siljitish"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Oʻngga siljitish"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Kattalashtirishni almashtirish"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Butun ekranni kattalashtirish"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Ekran qismini kattalashtirish"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Almashtirish"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Qurilmalarni boshqarish"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Ulangan qurilmalar uchun boshqaruv elementlari"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Qurilma boshqaruv elementlarini sozlash"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (uzilgan)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ulanmadi. Qayta urining."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Yangi qurilmani ulash"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Nashr raqami"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Nashr raqami vaqtinchalik xotiraga nusxalandi."</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index dc2a5b826439..849223651fd7 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Hiển thị biểu tượng thông báo có mức ưu tiên thấp"</string>
<string name="other" msgid="429768510980739978">"Khác"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"xóa ô"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"thêm ô vào cuối"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Di chuyển ô"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Thêm ô"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Di chuyển tới <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Thêm vào vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Trình chỉnh sửa cài đặt nhanh."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"Thông báo của <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Mở phần cài đặt."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Cài đặt"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Cửa sổ phóng to"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Các tùy chọn điều khiển cửa sổ phóng to"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Phóng to"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Thu nhỏ"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Di chuyển lên"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Di chuyển xuống"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Di chuyển sang trái"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Di chuyển sang phải"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Nút chuyển phóng to"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Phóng to toàn bộ màn hình"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Phóng to một phần màn hình"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Chuyển"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Điều khiển thiết bị"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Thêm các tùy chọn điều khiển cho các thiết bị đã kết nối của bạn"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Thiết lập các tùy chọn điều khiển thiết bị"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (đã ngắt kết nối)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Không thể kết nối. Hãy thử lại."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Ghép nối thiết bị mới"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Số bản dựng"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Đã sao chép số bản dựng vào khay nhớ tạm."</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 71a7d8e7165d..72e7640f6f54 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"显示低优先级的通知图标"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除图块"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"将图块添加到末尾"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移动图块"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"添加图块"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移至 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"添加到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快捷设置编辑器。"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>通知:<xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"打开设置。"</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"设置"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"放大窗口"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"放大窗口控件"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"放大"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"缩小"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"上移"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"下移"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"左移"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"右移"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"切换放大模式"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整个屏幕"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分屏幕"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切换"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"设备控制器"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"为您所连接的设备添加控件"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"设置设备控件"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>(已断开连接)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"无法连接。请重试。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"与新设备配对"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本号"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"已将版本号复制到剪贴板。"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 1d763d010984..c3dc8d2b17c6 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"顯示低優先順序通知圖示"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除圖塊"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"將圖塊加到最尾"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移動圖塊"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"加圖塊"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移去 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"加去位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯工具。"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> 通知:<xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"開啟設定。"</string>
@@ -975,7 +968,7 @@
<string name="auto_saver_enabled_text" msgid="7889491183116752719">"省電模式將會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"設定"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"知道了"</string>
- <string name="heap_dump_tile_name" msgid="2464189856478823046">"轉儲 SysUI 堆"</string>
+ <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string>
<string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"「<xliff:g id="APP">%1$s</xliff:g>」正在使用<xliff:g id="TYPES_LIST">%2$s</xliff:g>。"</string>
<string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string>
<string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"設定"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"放大視窗"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"放大視窗控制項"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"放大"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"縮細"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"向上移"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"向下移"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"向左移"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"向右移"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"放大開關"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整個螢幕畫面"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大部分螢幕畫面"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"為連接的裝置新增控制選項"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"設定裝置控制"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"版本號碼已複製到剪貼簿。"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 303ddd925a86..02fc24f41abb 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"顯示低優先順序通知圖示"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除圖塊"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"將圖塊加到結尾處"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移動圖塊"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"新增圖塊"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移至 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"新增到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快速設定編輯器。"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> 通知:<xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"開啟設定。"</string>
@@ -975,7 +968,7 @@
<string name="auto_saver_enabled_text" msgid="7889491183116752719">"省電模式會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string>
<string name="open_saver_setting_action" msgid="2111461909782935190">"設定"</string>
<string name="auto_saver_okay_action" msgid="7815925750741935386">"我知道了"</string>
- <string name="heap_dump_tile_name" msgid="2464189856478823046">"傾印 SysUI 記憶體快照"</string>
+ <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string>
<string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"「<xliff:g id="APP">%1$s</xliff:g>」正在使用<xliff:g id="TYPES_LIST">%2$s</xliff:g>。"</string>
<string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string>
<string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"設定"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"放大視窗"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"放大視窗控制項"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"放大"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"縮小"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"向上移"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"向下移"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"向左移"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"向右移"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"切換放大模式"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"放大整個螢幕畫面"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"放大局部螢幕畫面"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"切換"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"新增已連結裝置的控制項"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"設定裝置控制"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (已中斷連線)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"無法連線,請再試一次。"</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"配對新裝置"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"版本號碼"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"已將版本號碼複製到剪貼簿。"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index d8eb7ade9600..8eb86bcb6b75 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -879,20 +879,13 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"Bonisa izithonjana zesaziso zokubaluleka okuncane"</string>
<string name="other" msgid="429768510980739978">"Okunye"</string>
- <!-- no translation found for accessibility_qs_edit_remove_tile_action (775511891457193480) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_action (5051211910345301833) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_move (2009373939914517817) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_start_add (7560798153975555772) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_move_to_position (5198161544045930556) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_tile_add_to_position (9029163095148274690) -->
- <skip />
- <!-- no translation found for accessibility_qs_edit_position (4509277359815711830) -->
- <skip />
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"susa ithayela"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"engeza ithayela ekugcineni"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"Hambisa ithayela"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Engeza ithayela"</string>
+ <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Hambisa ku-<xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engeza kusikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string>
+ <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Isikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"Isihleli sezilungiselelo ezisheshayo."</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g> isaziso: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"Vula izilungiselelo."</string>
@@ -1019,6 +1012,16 @@
<string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Amasethingi"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"Iwindi Lesikhulisi"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"Izilawuli Zewindi Lesikhulisi"</string>
+ <string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Sondeza"</string>
+ <string name="accessibility_control_zoom_out" msgid="69578832020304084">"Hlehlisa"</string>
+ <string name="accessibility_control_move_up" msgid="6622825494014720136">"Khuphula"</string>
+ <string name="accessibility_control_move_down" msgid="5390922476900974512">"Yehlisa"</string>
+ <string name="accessibility_control_move_left" msgid="8156206978511401995">"Yisa kwesokunxele"</string>
+ <string name="accessibility_control_move_right" msgid="8926821093629582888">"Yisa kwesokudla"</string>
+ <string name="magnification_mode_switch_description" msgid="2698364322069934733">"Iswishi yokukhulisa"</string>
+ <string name="magnification_mode_switch_state_full_screen" msgid="2882507327576770574">"Khulisa sonke isikrini"</string>
+ <string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Khulisa ingxenye eyesikrini"</string>
+ <string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Iswishi"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Izilawuli zezinsiza"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"Engeza izilawuli zedivayisi yakho exhunyiwe"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"Setha izilawuli zezinsiza"</string>
@@ -1082,4 +1085,6 @@
<string name="media_output_dialog_disconnected" msgid="1834473104836986046">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> (inqamukile)"</string>
<string name="media_output_dialog_connect_failed" msgid="3225190634236259010">"Ayikwazanga ukuxhumeka. Zama futhi."</string>
<string name="media_output_dialog_pairing_new" msgid="9099497976087485862">"Bhanqa idivayisi entsha"</string>
+ <string name="build_number_clip_data_label" msgid="3623176728412560914">"Yakha inombolo"</string>
+ <string name="build_number_copy_toast" msgid="877720921605503046">"Yakha inombolo ekopishelwe kubhodi yokunamathisela."</string>
</resources>
diff --git a/packages/SystemUI/res/values/colors_tv.xml b/packages/SystemUI/res/values/colors_tv.xml
index cb49918e4e3f..9b0ae1d61de6 100644
--- a/packages/SystemUI/res/values/colors_tv.xml
+++ b/packages/SystemUI/res/values/colors_tv.xml
@@ -22,13 +22,15 @@
<color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
<color name="recents_tv_text_shadow_color">#7F000000</color>
- <!-- Background color for audio recording indicator (G800) -->
- <color name="tv_audio_recording_indicator_background">#FF3C4043</color>
<color name="tv_audio_recording_indicator_icon_background">#CC000000</color>
<color name="tv_audio_recording_indicator_stroke">#33FFFFFF</color>
<color name="red">#FFCC0000</color>
<color name="tv_volume_dialog_background">#E61F232B</color>
<color name="tv_volume_dialog_circle">#08FFFFFF</color>
+ <color name="tv_volume_dialog_seek_thumb_focus_ring">#1AFFFFFF</color>
+ <color name="tv_volume_dialog_seek_thumb_shadow">#40000000</color>
+ <color name="tv_volume_dialog_seek_bar_background">#A03C4043</color>
+ <color name="tv_volume_dialog_seek_bar_fill">#FFF8F9FA</color>
<color name="tv_volume_dialog_accent">#FFDADCE0</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 95db8f52ab0a..b96e7273dcd8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -566,4 +566,13 @@
<!-- Show a separate icon for low and high volume on the volume dialog -->
<bool name="config_showLowMediaVolumeIcon">false</bool>
+
+ <!-- Change the volume row tint when it is inactive, i.e. when it is being dismissed -->
+ <bool name="config_changeVolumeRowTintWhenInactive">true</bool>
+
+ <!-- The duraction of the show animation for the volume dialog in milliseconds -->
+ <integer name="config_dialogShowAnimationDurationMs">300</integer>
+
+ <!-- The duraction of the hide animation for the volume dialog in milliseconds -->
+ <integer name="config_dialogHideAnimationDurationMs">250</integer>
</resources>
diff --git a/packages/SystemUI/res/values/config_tv.xml b/packages/SystemUI/res/values/config_tv.xml
index 7451ba8e88a4..2e776749582c 100644
--- a/packages/SystemUI/res/values/config_tv.xml
+++ b/packages/SystemUI/res/values/config_tv.xml
@@ -15,10 +15,6 @@
-->
<resources>
- <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
- when the PIP menu is shown in center. -->
- <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string>
-
<!-- Whether to enable microphone disclosure indicator
(com.android.systemui.statusbar.tv.micdisclosure.AudioRecordingDisclosureBar). -->
<bool name="audio_recording_disclosure_enabled">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 382712086e16..9d1654172af6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -226,6 +226,8 @@
<dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
<dimen name="conversation_onboarding_bullet_gap_width">6dp</dimen>
+ <dimen name="notification_guts_header_top_padding">11dp</dimen>
+
<!-- The height of the header in inline settings -->
<dimen name="notification_guts_header_height">24dp</dimen>
@@ -482,20 +484,20 @@
<!-- The size of the gesture span needed to activate the "pull" notification expansion -->
<dimen name="pull_span_min">25dp</dimen>
- <dimen name="qs_tile_height">106dp</dimen>
+ <dimen name="qs_tile_height">96dp</dimen>
<!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 -->
<dimen name="qs_tile_layout_margin_side">18dp</dimen>
<dimen name="qs_tile_margin_horizontal">18dp</dimen>
<dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
- <dimen name="qs_tile_margin_vertical">24dp</dimen>
+ <dimen name="qs_tile_margin_vertical">2dp</dimen>
<dimen name="qs_tile_margin_top_bottom">12dp</dimen>
<dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
<!-- The height of the qs customize header. Should be
- (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+ (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) -
(Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
-->
- <dimen name="qs_customize_header_min_height">46dp</dimen>
- <dimen name="qs_tile_margin_top">18dp</dimen>
+ <dimen name="qs_customize_header_min_height">28dp</dimen>
+ <dimen name="qs_tile_margin_top">0dp</dimen>
<dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
<dimen name="qs_tile_background_size">44dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
@@ -504,6 +506,10 @@
<dimen name="qs_header_tile_margin_bottom">18dp</dimen>
<dimen name="qs_page_indicator_width">16dp</dimen>
<dimen name="qs_page_indicator_height">8dp</dimen>
+ <!-- The size of a single dot in relation to the whole animation.
+ Scaled @dimen/qs_page_indicator-width by .4f.
+ -->
+ <dimen name="qs_page_indicator_dot_width">6.4dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
@@ -968,8 +974,6 @@
<!-- The start margin of quick scrub onboarding toast. -->
<dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
- <dimen name="floating_dismiss_bottom_margin">50dp</dimen>
-
<dimen name="default_gear_space">18dp</dimen>
<dimen name="cell_overlay_padding">18dp</dimen>
@@ -1213,7 +1217,7 @@
<!-- Interior padding of the message bubble -->
<dimen name="bubble_message_padding">4dp</dimen>
<!-- Offset between bubbles in their stacked position. -->
- <dimen name="bubble_stack_offset">5dp</dimen>
+ <dimen name="bubble_stack_offset">10dp</dimen>
<!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
<dimen name="bubble_stack_offscreen">9dp</dimen>
<!-- How far down the screen the stack starts. -->
@@ -1226,8 +1230,6 @@
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<dimen name="bubble_manage_menu_elevation">4dp</dimen>
- <dimen name="dismiss_target_x_size">24dp</dimen>
-
<!-- Bubbles user education views -->
<dimen name="bubbles_manage_education_width">160dp</dimen>
<!-- The inset from the top bound of the manage button to place the user education. -->
@@ -1368,9 +1370,11 @@
<dimen name="config_rounded_mask_size_bottom">@*android:dimen/rounded_corner_radius_bottom</dimen>
<!-- Output switcher panel related dimensions -->
- <dimen name="media_output_dialog_padding_top">11dp</dimen>
+ <dimen name="media_output_dialog_list_margin">12dp</dimen>
<dimen name="media_output_dialog_list_max_height">364dp</dimen>
<dimen name="media_output_dialog_header_album_icon_size">52dp</dimen>
<dimen name="media_output_dialog_header_back_icon_size">36dp</dimen>
+ <dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
+ <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d815681c8736..f2bb4907ee8f 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -175,5 +175,13 @@
<!-- Accessibility actions for PIP -->
<item type="id" name="action_pip_resize" />
+
+ <!-- Accessibility actions for window magnification. -->
+ <item type="id" name="accessibility_action_zoom_in"/>
+ <item type="id" name="accessibility_action_zoom_out"/>
+ <item type="id" name="accessibility_action_move_left"/>
+ <item type="id" name="accessibility_action_move_right"/>
+ <item type="id" name="accessibility_action_move_up"/>
+ <item type="id" name="accessibility_action_move_down"/>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f69314977a21..d1fb6cd4f209 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -218,7 +218,7 @@
<!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] -->
<string name="screenshot_saving_ticker">Saving screenshot\u2026</string>
- <!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] -->
+ <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
<string name="screenshot_saving_title">Saving screenshot\u2026</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saved_title">Screenshot saved</string>
@@ -2666,6 +2666,26 @@
<string name="magnification_window_title">Magnification Window</string>
<!-- Title for Magnification Controls Window [CHAR LIMIT=NONE] -->
<string name="magnification_controls_title">Magnification Window Controls</string>
+ <!-- Action in accessibility menu to zoom in content of the magnification window. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_zoom_in">Zoom in</string>
+ <!-- Action in accessibility menu to zoom out content of the magnification window. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_zoom_out">Zoom out</string>
+ <!-- Action in accessibility menu to move the magnification window up. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_move_up">Move up</string>
+ <!-- Action in accessibility menu to move the magnification window down. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_move_down">Move down</string>
+ <!-- Action in accessibility menu to move the magnification window left. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_move_left">Move left</string>
+ <!-- Action in accessibility menu to move the magnification window right. [CHAR LIMIT=30] -->
+ <string name="accessibility_control_move_right">Move right</string>
+ <!-- Content description for magnification mode switch. [CHAR LIMIT=NONE] -->
+ <string name="magnification_mode_switch_description">Magnification switch</string>
+ <!-- A11y state description for magnification mode switch that device is in full-screen mode. [CHAR LIMIT=NONE] -->
+ <string name="magnification_mode_switch_state_full_screen">Magnify entire screen</string>
+ <!-- A11y state description for magnification mode switch that device is in window mode. [CHAR LIMIT=NONE] -->
+ <string name="magnification_mode_switch_state_window">Magnify part of screen</string>
+ <!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
+ <string name="magnification_mode_switch_click_label">Switch</string>
<!-- Device Controls strings -->
<!-- Device Controls empty state, title [CHAR LIMIT=30] -->
@@ -2817,4 +2837,15 @@
<string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string>
<!-- Title for pairing item [CHAR LIMIT=60] -->
<string name="media_output_dialog_pairing_new">Pair new device</string>
+
+ <!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
+ <string name="build_number_clip_data_label">Build number</string>
+ <!-- Text to display when copying the build number off QS [CHAR LIMIT=NONE]-->
+ <string name="build_number_copy_toast">Build number copied to clipboard.</string>
+
+ <!-- Status for last interaction [CHAR LIMIT=120] -->
+ <string name="last_interaction_status" translatable="false">You last chatted <xliff:g id="duration" example="5 hours">%1$s</xliff:g> ago</string>
+ <!-- Status for conversation without interaction data [CHAR LIMIT=120] -->
+ <string name="basic_status" translatable="false">Open conversation</string>
+
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2b0a963bff18..283918912c76 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -637,6 +637,13 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
+ <style name="Theme.CreateUser" parent="@style/Theme.SystemUI">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">#33000000</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+
<style name="TextAppearance.Control">
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
</style>
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index fa6468fefe04..b67378e638e1 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -18,4 +18,5 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="leak" path="leak/"/>
<external-path name="screenrecord" path="."/>
+ <cache-path name="multi_user" path="multi_user/" />
</paths> \ No newline at end of file
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 68f4b746caa2..606fd2c1848e 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -37,8 +37,6 @@ android_library {
static_libs: [
"PluginCoreLib",
],
-
-
java_version: "1.8",
min_sdk_version: "26",
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 8ed79290c8d4..e4427f49e030 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -16,6 +16,9 @@
package com.android.systemui.shared.recents;
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -44,11 +47,6 @@ interface ISystemUiProxy {
void startScreenPinning(int taskId) = 1;
/**
- * Notifies SystemUI that split screen has been invoked.
- */
- void onSplitScreenInvoked() = 5;
-
- /**
* Notifies SystemUI that Overview is shown.
*/
void onOverviewShown(boolean fromHome) = 6;
@@ -171,4 +169,27 @@ interface ISystemUiProxy {
* Notifies to expand notification panel.
*/
void expandNotificationPanel() = 29;
+
+ /**
+ * Notifies that Activity is about to be swiped to home with entering PiP transition and
+ * queries the destination bounds for PiP depends on Launcher's rotation and shelf height.
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param activityInfo ActivityInfo tied to the Activity
+ * @param pictureInPictureParams PictureInPictureParams tied to the Activity
+ * @param launcherRotation Launcher rotation to calculate the PiP destination bounds
+ * @param shelfHeight Shelf height of launcher to calculate the PiP destination bounds
+ * @return destination bounds the PiP window should land into
+ */
+ Rect startSwipePipToHome(in ComponentName componentName, in ActivityInfo activityInfo,
+ in PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) = 30;
+
+ /**
+ * Notifies the swiping Activity to PiP onto home transition is finished
+ *
+ * @param componentName ComponentName represents the Activity
+ * @param destinationBounds the destination bounds the PiP window lands into
+ */
+ void stopSwipePipToHome(in ComponentName componentName, in Rect destinationBounds) = 31;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index cffc10f65f1e..b81ffb766440 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -34,7 +34,6 @@ import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
-import android.app.IAssistDataReceiver;
import android.app.WindowConfiguration;
import android.content.ContentResolver;
import android.content.Context;
@@ -43,12 +42,10 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -84,13 +81,11 @@ public class ActivityManagerWrapper {
private final PackageManager mPackageManager;
private final BackgroundExecutor mBackgroundExecutor;
- private final TaskStackChangeListeners mTaskStackChangeListeners;
private ActivityManagerWrapper() {
final Context context = AppGlobals.getInitialApplication();
mPackageManager = context.getPackageManager();
mBackgroundExecutor = BackgroundExecutor.get();
- mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
}
public static ActivityManagerWrapper getInstance() {
@@ -228,21 +223,10 @@ public class ActivityManagerWrapper {
/**
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
- public void startRecentsActivity(Intent intent, final AssistDataReceiver assistDataReceiver,
+ public void startRecentsActivity(Intent intent, long eventTime,
final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
try {
- IAssistDataReceiver receiver = null;
- if (assistDataReceiver != null) {
- receiver = new IAssistDataReceiver.Stub() {
- public void onHandleAssistData(Bundle resultData) {
- assistDataReceiver.onHandleAssistData(resultData);
- }
- public void onHandleAssistScreenshot(Bitmap screenshot) {
- assistDataReceiver.onHandleAssistScreenshot(screenshot);
- }
- };
- }
IRecentsAnimationRunner runner = null;
if (animationHandler != null) {
runner = new IRecentsAnimationRunner.Stub() {
@@ -272,7 +256,7 @@ public class ActivityManagerWrapper {
}
};
}
- ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);
+ ActivityTaskManager.getService().startRecentsActivity(intent, eventTime, runner);
if (resultCallback != null) {
resultCallbackHandler.post(new Runnable() {
@Override
@@ -296,9 +280,9 @@ public class ActivityManagerWrapper {
/**
* Cancels the remote recents animation started from {@link #startRecentsActivity}.
*/
- public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
+ public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
try {
- ActivityTaskManager.getService().cancelRecentsAnimation(restoreHomeStackPosition);
+ ActivityTaskManager.getService().cancelRecentsAnimation(restoreHomeRootTaskPosition);
} catch (RemoteException e) {
Log.e(TAG, "Failed to cancel recents animation", e);
}
@@ -373,36 +357,17 @@ public class ActivityManagerWrapper {
}
/**
- * Moves an already resumed task to the side of the screen to initiate split screen.
- */
- public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
- Rect initialBounds) {
- try {
- return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(taskId,
- true /* onTop */);
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * Registers a task stack listener with the system.
- * This should be called on the main thread.
+ * @deprecated use {@link TaskStackChangeListeners#registerTaskStackListener}
*/
public void registerTaskStackListener(TaskStackChangeListener listener) {
- synchronized (mTaskStackChangeListeners) {
- mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener);
- }
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(listener);
}
/**
- * Unregisters a task stack listener with the system.
- * This should be called on the main thread.
+ * @deprecated use {@link TaskStackChangeListeners#unregisterTaskStackListener}
*/
public void unregisterTaskStackListener(TaskStackChangeListener listener) {
- synchronized (mTaskStackChangeListeners) {
- mTaskStackChangeListeners.removeListener(listener);
- }
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(listener);
}
/**
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index 345a649a036f..0db4faf9e493 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -86,4 +86,12 @@ public abstract class ActivityOptionsCompat {
opts.setFreezeRecentTasksReordering();
return opts;
}
+
+ /**
+ * Sets the launch event time from launcher.
+ */
+ public static ActivityOptions setLauncherSourceInfo(ActivityOptions opts, long uptimeMillis) {
+ opts.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER, uptimeMillis);
+ return opts;
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
new file mode 100644
index 000000000000..27cb4f60bd9c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -0,0 +1,71 @@
+/*
+ * 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.shared.system;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.internal.jank.InteractionJankMonitor;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public final class InteractionJankMonitorWrapper {
+ // Launcher journeys.
+ public static final int CUJ_APP_LAUNCH_FROM_RECENTS =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS;
+ public static final int CUJ_APP_LAUNCH_FROM_ICON =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON;
+ public static final int CUJ_APP_CLOSE_TO_HOME =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME;
+ public static final int CUJ_APP_CLOSE_TO_PIP =
+ InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP;
+ public static final int CUJ_QUICK_SWITCH =
+ InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH;
+
+ @IntDef({
+ CUJ_APP_LAUNCH_FROM_RECENTS,
+ CUJ_APP_LAUNCH_FROM_ICON,
+ CUJ_APP_CLOSE_TO_HOME,
+ CUJ_APP_CLOSE_TO_PIP,
+ CUJ_QUICK_SWITCH,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CujType {
+ }
+
+ public static void init(@NonNull View view) {
+ InteractionJankMonitor.getInstance().init(view);
+ }
+
+ public static boolean begin(@CujType int cujType) {
+ return InteractionJankMonitor.getInstance().begin(cujType);
+ }
+
+ public static boolean begin(@CujType int cujType, long timeout) {
+ return InteractionJankMonitor.getInstance().begin(cujType, timeout);
+ }
+
+ public static boolean end(@CujType int cujType) {
+ return InteractionJankMonitor.getInstance().end(cujType);
+ }
+
+ public static boolean cancel(@CujType int cujType) {
+ return InteractionJankMonitor.getInstance().cancel(cujType);
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index aed7c216433b..a1c1f93638a8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -16,6 +16,7 @@
package com.android.systemui.shared.system;
+import android.app.PictureInPictureParams;
import android.app.WindowConfiguration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -49,6 +50,7 @@ public class RemoteAnimationTargetCompat {
public final Rect screenSpaceBounds;
public final boolean isNotInRecents;
public final Rect contentInsets;
+ public final PictureInPictureParams pictureInPictureParams;
private final SurfaceControl mStartLeash;
@@ -66,6 +68,7 @@ public class RemoteAnimationTargetCompat {
isNotInRecents = app.isNotInRecents;
contentInsets = app.contentInsets;
activityType = app.windowConfiguration.getActivityType();
+ pictureInPictureParams = app.pictureInPictureParams;
mStartLeash = app.startLeash;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 2985a61dec9e..86129e0e204e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -47,6 +47,7 @@ public class SyncRtSurfaceTransactionApplierCompat {
public static final int FLAG_BACKGROUND_BLUR_RADIUS = 1 << 5;
public static final int FLAG_VISIBILITY = 1 << 6;
public static final int FLAG_RELATIVE_LAYER = 1 << 7;
+ public static final int FLAG_SHADOW_RADIUS = 1 << 8;
private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
@@ -196,6 +197,7 @@ public class SyncRtSurfaceTransactionApplierCompat {
SurfaceControl relativeTo;
int relativeLayer;
boolean visible;
+ float shadowRadius;
/**
* @param surface The surface to modify.
@@ -274,6 +276,16 @@ public class SyncRtSurfaceTransactionApplierCompat {
}
/**
+ * @param radius the Radius for the shadows to apply to the surface.
+ * @return this Builder
+ */
+ public Builder withShadowRadius(float radius) {
+ this.shadowRadius = radius;
+ flags |= FLAG_SHADOW_RADIUS;
+ return this;
+ }
+
+ /**
* @param radius the Radius for blur to apply to the background surfaces.
* @return this Builder
*/
@@ -298,31 +310,14 @@ public class SyncRtSurfaceTransactionApplierCompat {
*/
public SurfaceParams build() {
return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
- relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible);
+ relativeTo, relativeLayer, cornerRadius, backgroundBlurRadius, visible,
+ shadowRadius);
}
}
- /**
- * Constructs surface parameters to be applied when the current view state gets pushed to
- * RenderThread.
- *
- * @param surface The surface to modify.
- * @param alpha Alpha to apply.
- * @param matrix Matrix to apply.
- * @param windowCrop Crop to apply, only applied if not {@code null}
- */
- public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
- Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
- float cornerRadius) {
- this(surface.mSurfaceControl,
- FLAG_ALL & ~(FLAG_VISIBILITY | FLAG_BACKGROUND_BLUR_RADIUS), alpha,
- matrix, windowCrop, layer, relativeTo, relativeLayer, cornerRadius,
- 0 /* backgroundBlurRadius */, true);
- }
-
private SurfaceParams(SurfaceControl surface, int flags, float alpha, Matrix matrix,
Rect windowCrop, int layer, SurfaceControl relativeTo, int relativeLayer,
- float cornerRadius, int backgroundBlurRadius, boolean visible) {
+ float cornerRadius, int backgroundBlurRadius, boolean visible, float shadowRadius) {
this.flags = flags;
this.surface = surface;
this.alpha = alpha;
@@ -334,6 +329,7 @@ public class SyncRtSurfaceTransactionApplierCompat {
this.cornerRadius = cornerRadius;
this.backgroundBlurRadius = backgroundBlurRadius;
this.visible = visible;
+ this.shadowRadius = shadowRadius;
}
private final int flags;
@@ -349,6 +345,7 @@ public class SyncRtSurfaceTransactionApplierCompat {
public final SurfaceControl relativeTo;
public final int relativeLayer;
public final boolean visible;
+ public final float shadowRadius;
public void applyTo(SurfaceControl.Transaction t) {
if ((flags & FLAG_MATRIX) != 0) {
@@ -379,6 +376,9 @@ public class SyncRtSurfaceTransactionApplierCompat {
if ((flags & FLAG_RELATIVE_LAYER) != 0) {
t.setRelativeLayer(surface, relativeTo, relativeLayer);
}
+ if ((flags & FLAG_SHADOW_RADIUS) != 0) {
+ t.setShadowRadius(surface, shadowRadius);
+ }
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index f214648d89f1..765cd3de4222 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -19,14 +19,12 @@ package com.android.systemui.shared.system;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
import android.os.Trace;
import android.util.Log;
@@ -42,208 +40,40 @@ import java.util.List;
public class TaskStackChangeListeners extends TaskStackListener {
private static final String TAG = TaskStackChangeListeners.class.getSimpleName();
+ private static final TaskStackChangeListeners INSTANCE = new TaskStackChangeListeners();
- /**
- * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
- */
- private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
- private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
-
- private final Handler mHandler;
- private boolean mRegistered;
+ private final Impl mImpl;
- public TaskStackChangeListeners(Looper looper) {
- mHandler = new H(looper);
+ private TaskStackChangeListeners() {
+ mImpl = new Impl(Looper.getMainLooper());
}
- public void addListener(IActivityManager am, TaskStackChangeListener listener) {
- synchronized (mTaskStackListeners) {
- mTaskStackListeners.add(listener);
- }
- if (!mRegistered) {
- // Register mTaskStackListener to IActivityManager only once if needed.
- try {
- ActivityTaskManager.getService().registerTaskStackListener(this);
- mRegistered = true;
- } catch (Exception e) {
- Log.w(TAG, "Failed to call registerTaskStackListener", e);
- }
- }
+ public static TaskStackChangeListeners getInstance() {
+ return INSTANCE;
}
- public void removeListener(TaskStackChangeListener listener) {
- boolean isEmpty;
- synchronized (mTaskStackListeners) {
- mTaskStackListeners.remove(listener);
- isEmpty = mTaskStackListeners.isEmpty();
- }
- if (isEmpty && mRegistered) {
- // Unregister mTaskStackListener once we have no more listeners
- try {
- ActivityTaskManager.getService().unregisterTaskStackListener(this);
- mRegistered = false;
- } catch (Exception e) {
- Log.w(TAG, "Failed to call unregisterTaskStackListener", e);
- }
+ /**
+ * Registers a task stack listener with the system.
+ * This should be called on the main thread.
+ */
+ public void registerTaskStackListener(TaskStackChangeListener listener) {
+ synchronized (mImpl) {
+ mImpl.addListener(listener);
}
}
- @Override
- public void onTaskStackChanged() throws RemoteException {
- // Call the task changed callback for the non-ui thread listeners first. Copy to a set of
- // temp listeners so that we don't lock on mTaskStackListeners while calling all the
- // callbacks. This call is always on the same binder thread, so we can just synchronize
- // on the copying of the listener list.
- synchronized (mTaskStackListeners) {
- mTmpListeners.addAll(mTaskStackListeners);
- }
- for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
- mTmpListeners.get(i).onTaskStackChangedBackground();
+ /**
+ * Unregisters a task stack listener with the system.
+ * This should be called on the main thread.
+ */
+ public void unregisterTaskStackListener(TaskStackChangeListener listener) {
+ synchronized (mImpl) {
+ mImpl.removeListener(listener);
}
- mTmpListeners.clear();
-
- mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
- mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
- }
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
- throws RemoteException {
- mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
- new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
- }
-
- @Override
- public void onActivityUnpinned() throws RemoteException {
- mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
- }
-
- @Override
- public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
- boolean clearedTask, boolean wasVisible) throws RemoteException {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = task;
- args.argi1 = homeTaskVisible ? 1 : 0;
- args.argi2 = clearedTask ? 1 : 0;
- args.argi3 = wasVisible ? 1 : 0;
- mHandler.removeMessages(H.ON_ACTIVITY_RESTART_ATTEMPT);
- mHandler.obtainMessage(H.ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
- }
-
- @Override
- public void onActivityForcedResizable(String packageName, int taskId, int reason)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
- .sendToTarget();
- }
-
- @Override
- public void onActivityDismissingDockedStack() throws RemoteException {
- mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo,
- int requestedDisplayId) throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, requestedDisplayId,
- 0 /* unused */,
- taskInfo).sendToTarget();
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo,
- int requestedDisplayId) throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
- requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget();
- }
-
- @Override
- public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
- }
-
- @Override
- public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
- }
-
- @Override
- public void onTaskRemoved(int taskId) throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_REMOVED, taskId, 0).sendToTarget();
}
- @Override
- public void onTaskMovedToFront(RunningTaskInfo taskInfo)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
- }
-
- @Override
- public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) throws RemoteException {
- mHandler.obtainMessage(H.ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
- }
-
- @Override
- public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
- requestedOrientation).sendToTarget();
- }
-
- @Override
- public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
- throws RemoteException {
- mHandler.obtainMessage(H.ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, 0 /* unused */,
- activityToken).sendToTarget();
- }
-
- @Override
- public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
- mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_DRAWN, displayId,
- 0 /* unused */).sendToTarget();
- }
-
- @Override
- public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
- mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
- 0 /* unused */).sendToTarget();
- }
-
- @Override
- public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
- }
-
- @Override
- public void onRecentTaskListUpdated() throws RemoteException {
- mHandler.obtainMessage(H.ON_TASK_LIST_UPDATED).sendToTarget();
- }
-
- @Override
- public void onRecentTaskListFrozenChanged(boolean frozen) {
- mHandler.obtainMessage(H.ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */)
- .sendToTarget();
- }
-
- @Override
- public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) {
- mHandler.obtainMessage(H.ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
- }
-
- @Override
- public void onActivityRotation(int displayId) {
- mHandler.obtainMessage(H.ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
- .sendToTarget();
- }
+ private static class Impl extends TaskStackListener implements Handler.Callback {
- private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
private static final int ON_ACTIVITY_PINNED = 3;
@@ -268,13 +98,205 @@ public class TaskStackChangeListeners extends TaskStackListener {
private static final int ON_TASK_DESCRIPTION_CHANGED = 24;
private static final int ON_ACTIVITY_ROTATION = 25;
+ /**
+ * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+ */
+ private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
+ private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
+
+ private final Handler mHandler;
+ private boolean mRegistered;
+
+ Impl(Looper looper) {
+ mHandler = new Handler(looper, this);
+ }
+
+ public void addListener(TaskStackChangeListener listener) {
+ synchronized (mTaskStackListeners) {
+ mTaskStackListeners.add(listener);
+ }
+ if (!mRegistered) {
+ // Register mTaskStackListener to IActivityManager only once if needed.
+ try {
+ ActivityTaskManager.getService().registerTaskStackListener(this);
+ mRegistered = true;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to call registerTaskStackListener", e);
+ }
+ }
+ }
+
+ public void removeListener(TaskStackChangeListener listener) {
+ boolean isEmpty;
+ synchronized (mTaskStackListeners) {
+ mTaskStackListeners.remove(listener);
+ isEmpty = mTaskStackListeners.isEmpty();
+ }
+ if (isEmpty && mRegistered) {
+ // Unregister mTaskStackListener once we have no more listeners
+ try {
+ ActivityTaskManager.getService().unregisterTaskStackListener(this);
+ mRegistered = false;
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to call unregisterTaskStackListener", e);
+ }
+ }
+ }
- public H(Looper looper) {
- super(looper);
+ @Override
+ public void onTaskStackChanged() {
+ // Call the task changed callback for the non-ui thread listeners first. Copy to a set
+ // of temp listeners so that we don't lock on mTaskStackListeners while calling all the
+ // callbacks. This call is always on the same binder thread, so we can just synchronize
+ // on the copying of the listener list.
+ synchronized (mTaskStackListeners) {
+ mTmpListeners.addAll(mTaskStackListeners);
+ }
+ for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
+ mTmpListeners.get(i).onTaskStackChangedBackground();
+ }
+ mTmpListeners.clear();
+
+ mHandler.removeMessages(ON_TASK_STACK_CHANGED);
+ mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED);
+ }
+
+ @Override
+ public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+ mHandler.removeMessages(ON_ACTIVITY_PINNED);
+ mHandler.obtainMessage(ON_ACTIVITY_PINNED,
+ new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ mHandler.removeMessages(ON_ACTIVITY_UNPINNED);
+ mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED);
+ }
+
+ @Override
+ public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
+ boolean clearedTask, boolean wasVisible) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = task;
+ args.argi1 = homeTaskVisible ? 1 : 0;
+ args.argi2 = clearedTask ? 1 : 0;
+ args.argi3 = wasVisible ? 1 : 0;
+ mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
+ mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
+ }
+
+ @Override
+ public void onActivityForcedResizable(String packageName, int taskId, int reason) {
+ mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onActivityDismissingDockedStack() {
+ mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+ }
+
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo,
+ int requestedDisplayId) {
+ mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED,
+ requestedDisplayId,
+ 0 /* unused */,
+ taskInfo).sendToTarget();
+ }
+
+ @Override
+ public void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo,
+ int requestedDisplayId) {
+ mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED,
+ requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget();
+ }
+
+ @Override
+ public void onTaskProfileLocked(int taskId, int userId) {
+ mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+ }
+
+ @Override
+ public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+ mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget();
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget();
+ }
+
+ @Override
+ public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
+ mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget();
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+ mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+ mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
+ requestedOrientation).sendToTarget();
+ }
+
+ @Override
+ public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
+ mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
+ 0 /* unused */,
+ activityToken).sendToTarget();
+ }
+
+ @Override
+ public void onSingleTaskDisplayDrawn(int displayId) {
+ mHandler.obtainMessage(ON_SINGLE_TASK_DISPLAY_DRAWN, displayId,
+ 0 /* unused */).sendToTarget();
+ }
+
+ @Override
+ public void onSingleTaskDisplayEmpty(int displayId) {
+ mHandler.obtainMessage(ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+ 0 /* unused */).sendToTarget();
+ }
+
+ @Override
+ public void onTaskDisplayChanged(int taskId, int newDisplayId) {
+ mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
+ }
+
+ @Override
+ public void onRecentTaskListUpdated() {
+ mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget();
+ }
+
+ @Override
+ public void onRecentTaskListFrozenChanged(boolean frozen) {
+ mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) {
+ mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget();
+ }
+
+ @Override
+ public void onActivityRotation(int displayId) {
+ mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */)
+ .sendToTarget();
}
@Override
- public void handleMessage(Message msg) {
+ public boolean handleMessage(Message msg) {
synchronized (mTaskStackListeners) {
switch (msg.what) {
case ON_TASK_STACK_CHANGED: {
@@ -298,7 +320,8 @@ public class TaskStackChangeListeners extends TaskStackListener {
final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onActivityPinned(
- info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
+ info.mPackageName, info.mUserId, info.mTaskId,
+ info.mStackId);
}
break;
}
@@ -404,8 +427,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
}
case ON_SINGLE_TASK_DISPLAY_EMPTY: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
- msg.arg1);
+ mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(msg.arg1);
}
break;
}
@@ -423,7 +445,8 @@ public class TaskStackChangeListeners extends TaskStackListener {
}
case ON_TASK_LIST_FROZEN_UNFROZEN: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onRecentTaskListFrozenChanged(msg.arg1 != 0);
+ mTaskStackListeners.get(i).onRecentTaskListFrozenChanged(
+ msg.arg1 != 0);
}
break;
}
@@ -445,6 +468,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
if (msg.obj instanceof SomeArgs) {
((SomeArgs) msg.obj).recycle();
}
+ return true;
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 067ac9ec7b1e..5122f6cf31a1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,13 +27,13 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
import com.android.systemui.shared.recents.view.RecentsTransition;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
public class WindowManagerWrapper {
@@ -75,26 +75,32 @@ public class WindowManagerWrapper {
WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
public static final int WINDOWING_MODE_MULTI_WINDOW =
WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
- public static final int WINDOWING_MODE_PINNED = WindowConfiguration.WINDOWING_MODE_PINNED;
+
public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY =
WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY =
WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
public static final int WINDOWING_MODE_FREEFORM = WindowConfiguration.WINDOWING_MODE_FREEFORM;
- private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
+ public static final int ITYPE_EXTRA_NAVIGATION_BAR = InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
- /**
- * Forwarder to which we can add multiple pinned stack listeners. Each listener will receive
- * updates from the window manager service.
- */
- private PinnedStackListenerForwarder mPinnedStackListenerForwarder =
- new PinnedStackListenerForwarder();
+ private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
public static WindowManagerWrapper getInstance() {
return sInstance;
}
+
+ /**
+ * Sets {@param providesInsetsTypes} as the inset types provided by {@param params}.
+ * @param params The window layout params.
+ * @param providesInsetsTypes The inset types we would like this layout params to provide.
+ */
+ public void setProvidesInsetsTypes(WindowManager.LayoutParams params,
+ int[] providesInsetsTypes) {
+ params.providesInsetsTypes = providesInsetsTypes;
+ }
+
/**
* @return the stable insets for the primary display.
*/
@@ -188,23 +194,6 @@ public class WindowManagerWrapper {
}
/**
- * Adds a pinned stack listener, which will receive updates from the window manager service
- * along with any other pinned stack listeners that were added via this method.
- */
- public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
- mPinnedStackListenerForwarder.addListener(listener);
- WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
- DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
- }
-
- /**
- * Removes a pinned stack listener.
- */
- public void removePinnedStackListener(PinnedStackListener listener) {
- mPinnedStackListenerForwarder.removeListener(listener);
- }
-
- /**
* Mirrors a specified display. The SurfaceControl returned is the root of the mirrored
* hierarchy.
*
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a65accf1ff15..4ffd22c73116 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -107,7 +107,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
if (shouldLockout(deadline)) {
handleAttemptLockout(deadline);
} else {
- mView.resetState();
+ resetState();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5ad8cad8195a..272954df6dd6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -104,6 +104,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
private boolean mSupportsDarkText;
private int[] mColorPalette;
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,6 +130,35 @@ public class KeyguardClockSwitch extends RelativeLayout {
return mClockPlugin != null;
}
+ /**
+ * Update lock screen mode for testing different layouts
+ */
+ public void updateLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ RelativeLayout.LayoutParams statusAreaLP = (RelativeLayout.LayoutParams)
+ mKeyguardStatusArea.getLayoutParams();
+ RelativeLayout.LayoutParams clockLP = (RelativeLayout.LayoutParams)
+ mSmallClockFrame.getLayoutParams();
+
+ if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ statusAreaLP.removeRule(RelativeLayout.BELOW);
+ statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.clock_view);
+ statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
+
+ clockLP.addRule(RelativeLayout.ALIGN_PARENT_END);
+ clockLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
+ statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
+ statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
+
+ clockLP.removeRule(RelativeLayout.ALIGN_PARENT_END);
+ clockLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ requestLayout();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -363,6 +394,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
* these cases.
*/
void setKeyguardShowingHeader(boolean hasHeader) {
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ hasHeader = false;
+ }
+
if (mShowingHeader == hasHeader) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 9ffa658da0e8..351369c51364 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -163,16 +163,18 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
@Inject
public KeyguardHostViewController(KeyguardHostView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardSecurityContainerController keyguardSecurityContainerController,
AudioManager audioManager,
TelephonyManager telephonyManager,
- ViewMediatorCallback viewMediatorCallback) {
+ ViewMediatorCallback viewMediatorCallback,
+ KeyguardSecurityContainerController.Factory
+ keyguardSecurityContainerControllerFactory) {
super(view);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardSecurityContainerController = keyguardSecurityContainerController;
mAudioManager = audioManager;
mTelephonyManager = telephonyManager;
mViewMediatorCallback = viewMediatorCallback;
+ mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create(
+ mSecurityCallback);
}
/** Initialize the Controller. */
@@ -188,7 +190,6 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
mView.setOnKeyListener(mOnKeyListener);
- mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 64676e55b038..1c23605a8516 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -68,8 +68,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final UiEventLogger mUiEventLogger;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+ private final SecurityCallback mSecurityCallback;
- private SecurityCallback mSecurityCallback;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
@@ -145,8 +145,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
};
- @Inject
- KeyguardSecurityContainerController(KeyguardSecurityContainer view,
+ private KeyguardSecurityContainerController(KeyguardSecurityContainer view,
AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
LockPatternUtils lockPatternUtils,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -154,6 +153,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
+ SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -162,6 +162,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
+ mSecurityCallback = securityCallback;
mSecurityViewFlipperController = securityViewFlipperController;
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
@@ -269,10 +270,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
- public void setSecurityCallback(SecurityCallback securityCallback) {
- mSecurityCallback = securityCallback;
- }
-
/**
* Shows the next security screen if there is one.
* @param authenticated true if the user entered the correct authentication
@@ -450,4 +447,49 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mCurrentSecurityMode = securityMode;
return getCurrentSecurityController();
}
+
+ static class Factory {
+
+ private final KeyguardSecurityContainer mView;
+ private final AdminSecondaryLockScreenController.Factory
+ mAdminSecondaryLockScreenControllerFactory;
+ private final LockPatternUtils mLockPatternUtils;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final KeyguardSecurityModel mKeyguardSecurityModel;
+ private final MetricsLogger mMetricsLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
+
+ @Inject
+ Factory(KeyguardSecurityContainer view,
+ AdminSecondaryLockScreenController.Factory
+ adminSecondaryLockScreenControllerFactory,
+ LockPatternUtils lockPatternUtils,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardSecurityModel keyguardSecurityModel,
+ MetricsLogger metricsLogger,
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController,
+ KeyguardSecurityViewFlipperController securityViewFlipperController) {
+ mView = view;
+ mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
+ mLockPatternUtils = lockPatternUtils;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardSecurityModel = keyguardSecurityModel;
+ mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
+ mSecurityViewFlipperController = securityViewFlipperController;
+ }
+
+ public KeyguardSecurityContainerController create(
+ SecurityCallback securityCallback) {
+ return new KeyguardSecurityContainerController(mView,
+ mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
+ mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
+ mKeyguardStateController, securityCallback, mSecurityViewFlipperController);
+ }
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 6e111745627f..9ef2def04ec1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -79,6 +79,11 @@ public class KeyguardStatusView extends GridLayout implements
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
+ public void onLockScreenModeChanged(int mode) {
+ updateLockScreenMode(mode);
+ }
+
+ @Override
public void onTimeChanged() {
refreshTime();
}
@@ -255,6 +260,10 @@ public class KeyguardStatusView extends GridLayout implements
mClockView.refresh();
}
+ private void updateLockScreenMode(int mode) {
+ mClockView.updateLockScreenMode(mode);
+ }
+
private void updateTimeZone(TimeZone timeZone) {
mClockView.onTimeZoneChanged(timeZone);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a8dd03f668e2..bb8a99bb8cd8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -56,7 +56,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -100,8 +100,8 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.Assert;
@@ -181,6 +181,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_USER_STOPPED = 340;
private static final int MSG_USER_REMOVED = 341;
private static final int MSG_KEYGUARD_GOING_AWAY = 342;
+ private static final int MSG_LOCK_SCREEN_MODE = 343;
+
+ public static final int LOCK_SCREEN_MODE_NORMAL = 0;
+ public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1;
/** Biometric authentication state: Not listening. */
private static final int BIOMETRIC_STATE_STOPPED = 0;
@@ -264,6 +268,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
mCallbacks = Lists.newArrayList();
private ContentObserver mDeviceProvisionedObserver;
+ private ContentObserver mLockScreenModeObserver;
private boolean mSwitchingUser;
@@ -287,6 +292,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
+ private int mLockScreenMode;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1337,7 +1343,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private CancellationSignal mFaceCancelSignal;
private FingerprintManager mFpm;
private FaceManager mFaceManager;
- private List<FaceSensorProperties> mFaceSensorProperties;
+ private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
private boolean mFingerprintLockedOut;
private TelephonyManager mTelephonyManager;
@@ -1695,6 +1701,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_KEYGUARD_GOING_AWAY:
handleKeyguardGoingAway((boolean) msg.obj);
break;
+ case MSG_LOCK_SCREEN_MODE:
+ handleLockScreenMode();
+ break;
default:
super.handleMessage(msg);
break;
@@ -1779,7 +1788,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
- mFaceSensorProperties = mFaceManager.getSensorProperties();
+ mFaceSensorProperties = mFaceManager.getSensorPropertiesInternal();
}
if (mFpm != null || mFaceManager != null) {
@@ -1797,7 +1806,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mIsAutomotive = isAutomotive();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
mIsPrimaryUser = mUserManager.isPrimaryUser();
int user = ActivityManager.getCurrentUser();
@@ -1829,6 +1838,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
}
+
+ updateLockScreenMode();
+ mLockScreenModeObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateLockScreenMode();
+ mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE);
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.SHOW_NEW_LOCKSCREEN),
+ false, mLockScreenModeObserver);
+ }
+
+ private void updateLockScreenMode() {
+ mLockScreenMode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_LOCKSCREEN, 0);
}
private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@@ -2351,6 +2377,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
/**
+ * Handle {@link #MSG_LOCK_SCREEN_MODE}
+ */
+ private void handleLockScreenMode() {
+ Assert.isMainThread();
+ if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")");
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onLockScreenModeChanged(mLockScreenMode);
+ }
+ }
+ }
+
+ /**
* Handle (@line #MSG_TIMEZONE_UPDATE}
*/
private void handleTimeZoneUpdate(String timeZone) {
@@ -2679,6 +2719,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onClockVisibilityChanged();
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
+ callback.onLockScreenModeChanged(mLockScreenMode);
+
for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
final SimData state = data.getValue();
callback.onSimStateChanged(state.subId, state.slotId, state.simState);
@@ -3023,13 +3065,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
}
+ if (mLockScreenModeObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(mLockScreenModeObserver);
+ }
+
try {
ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
} catch (RemoteException e) {
Log.d(TAG, "RemoteException onDestroy. cannot unregister userSwitchObserver");
}
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 37c66639876e..9c2b14945cc2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -325,4 +325,9 @@ public class KeyguardUpdateMonitorCallback {
*/
public void onSecondaryLockscreenRequirementChanged(int userId) { }
+ /**
+ * Called to switch lock screen layout/clock layouts
+ */
+ public void onLockScreenModeChanged(int mode) { }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 832edf719dbb..9f28e0936d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -37,7 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -47,6 +47,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -126,6 +127,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.SystemWindows;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -167,6 +169,15 @@ public class Dependency {
* Generic handler on the main thread.
*/
private static final String MAIN_HANDLER_NAME = "main_handler";
+ /**
+ * Generic executor on the main thread.
+ */
+ private static final String MAIN_EXECUTOR_NAME = "main_executor";
+
+ /**
+ * Generic executor on a background thread.
+ */
+ private static final String BACKGROUND_EXECUTOR_NAME = "background_executor";
/**
* An email address to send memory leak reports to by default.
@@ -199,6 +210,17 @@ public class Dependency {
new DependencyKey<>(MAIN_HANDLER_NAME);
/**
+ * Generic executor on the main thread.
+ */
+ public static final DependencyKey<Executor> MAIN_EXECUTOR =
+ new DependencyKey<>(MAIN_EXECUTOR_NAME);
+ /**
+ * Generic executor on a background thread.
+ */
+ public static final DependencyKey<Executor> BACKGROUND_EXECUTOR =
+ new DependencyKey<>(BACKGROUND_EXECUTOR_NAME);
+
+ /**
* An email address to send memory leak reports to by default.
*/
public static final DependencyKey<String> LEAK_REPORT_EMAIL =
@@ -288,7 +310,7 @@ public class Dependency {
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
- @Inject Lazy<BubbleController> mBubbleController;
+ @Inject Lazy<Bubbles> mBubbles;
@Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@@ -301,6 +323,8 @@ public class Dependency {
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Nullable
@Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
+ @Inject @Main Lazy<Executor> mMainExecutor;
+ @Inject @Background Lazy<Executor> mBackgroundExecutor;
@Inject Lazy<ClockManager> mClockManager;
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@@ -321,6 +345,7 @@ public class Dependency {
@Inject Lazy<DisplayImeController> mDisplayImeController;
@Inject Lazy<RecordingController> mRecordingController;
@Inject Lazy<ProtoTracer> mProtoTracer;
+ @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
@Inject
public Dependency() {
@@ -336,6 +361,8 @@ public class Dependency {
mProviders.put(BG_LOOPER, mBgLooper::get);
mProviders.put(MAIN_LOOPER, mMainLooper::get);
mProviders.put(MAIN_HANDLER, mMainHandler::get);
+ mProviders.put(MAIN_EXECUTOR, mMainExecutor::get);
+ mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get);
mProviders.put(ActivityStarter.class, mActivityStarter::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
@@ -483,7 +510,7 @@ public class Dependency {
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,
mRemoteInputQuickSettingsDisabler::get);
- mProviders.put(BubbleController.class, mBubbleController::get);
+ mProviders.put(Bubbles.class, mBubbles::get);
mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
@@ -516,6 +543,8 @@ public class Dependency {
mProviders.put(RecordingController.class, mRecordingController::get);
+ mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get);
+
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f4c865e1d131..f210d508907c 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -72,11 +72,11 @@ public final class Prefs {
Key.QS_HAS_TURNED_OFF_MOBILE_DATA,
Key.TOUCHED_RINGER_TOGGLE,
Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
- Key.HAS_SEEN_BUBBLES_EDUCATION,
- Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
- Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
+ Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
+ Key.HAS_SEEN_PRIORITY_ONBOARDING
})
+ // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key {
@Deprecated
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
@@ -121,8 +121,6 @@ public final class Prefs {
String QS_HAS_TURNED_OFF_MOBILE_DATA = "QsHasTurnedOffMobileData";
String TOUCHED_RINGER_TOGGLE = "TouchedRingerToggle";
String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
- String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding";
- String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
/** Tracks whether the user has seen the onboarding screen for priority conversations */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index fbfabd1e4ae7..a4648ee75485 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -86,6 +86,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -125,6 +126,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final TunerService mTunerService;
private DisplayManager.DisplayListener mDisplayListener;
private CameraAvailabilityListener mCameraListener;
+ private final UserTracker mUserTracker;
//TODO: These are piecemeal being updated to Points for now to support non-square rounded
// corners. for now it is only supposed when reading the intrinsic size from the drawables with
@@ -202,11 +204,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
public ScreenDecorations(Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
- TunerService tunerService) {
+ TunerService tunerService,
+ UserTracker userTracker) {
super(context);
mMainHandler = handler;
mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
+ mUserTracker = userTracker;
}
@Override
@@ -310,7 +314,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
// Watch color inversion and invert the overlay as needed.
if (mColorInversionSetting == null) {
mColorInversionSetting = new SecureSetting(mContext, mHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ mUserTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateColorInversion(value);
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index 34efa35c37c5..02f34ac3dec0 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -42,8 +42,8 @@ import android.widget.PopupWindow;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import java.lang.ref.WeakReference;
@@ -66,11 +66,11 @@ public class SizeCompatModeActivityController extends SystemUI implements Comman
@VisibleForTesting
@Inject
- SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,
+ SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
CommandQueue commandQueue) {
super(context);
mCommandQueue = commandQueue;
- am.registerTaskStackListener(new TaskStackChangeListener() {
+ listeners.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
// Note the callback already runs on main thread.
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 2365f128532e..47adffc216a5 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -111,9 +111,7 @@ public class SlicePermissionActivity extends Activity implements OnClickListener
final String providerPkg = getIntent().getStringExtra("provider_pkg");
if (providerPkg == null || mProviderPkg.equals(providerPkg)) return;
final String callingPkg = getCallingPkg();
- EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg), String.format(
- "pkg %s (disguised as %s) attempted to request permission to show %s slices in %s",
- callingPkg, providerPkg, mProviderPkg, mCallingPkg));
+ EventLog.writeEvent(0x534e4554, "159145361", getUid(callingPkg));
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 68e404e36bba..69a0d65a6963 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -16,11 +16,14 @@
package com.android.systemui.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.graphics.PointF;
+import android.os.Bundle;
import android.provider.Settings;
import android.util.MathUtils;
import android.view.Gravity;
@@ -28,6 +31,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +46,15 @@ import com.android.systemui.R;
*/
class MagnificationModeSwitch {
- private static final int DURATION_MS = 5000;
- private static final int START_DELAY_MS = 3000;
- private final Runnable mAnimationTask;
+ @VisibleForTesting
+ static final long FADING_ANIMATION_DURATION_MS = 300;
+ private static final int DEFAULT_FADE_OUT_ANIMATION_DELAY_MS = 3000;
+ // The button visible duration starting from the last showButton() called.
+ private int mVisibleDuration = DEFAULT_FADE_OUT_ANIMATION_DELAY_MS;
+ private final Runnable mFadeInAnimationTask;
+ private final Runnable mFadeOutAnimationTask;
+ @VisibleForTesting
+ boolean mIsFadeOutAnimating = false;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -71,16 +82,53 @@ class MagnificationModeSwitch {
applyResourcesValues();
mImageView.setImageResource(getIconResId(mMagnificationMode));
mImageView.setOnTouchListener(this::onTouch);
+ mImageView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setStateDescription(formatStateDescription());
+ info.setContentDescription(mContext.getResources().getString(
+ R.string.magnification_mode_switch_description));
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ handleSingleTap();
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ });
- mAnimationTask = () -> {
+ mFadeInAnimationTask = () -> {
+ mImageView.animate()
+ .alpha(1f)
+ .setDuration(FADING_ANIMATION_DURATION_MS)
+ .start();
+ };
+ mFadeOutAnimationTask = () -> {
mImageView.animate()
.alpha(0f)
- .setDuration(DURATION_MS)
+ .setDuration(FADING_ANIMATION_DURATION_MS)
.withEndAction(() -> removeButton())
.start();
+ mIsFadeOutAnimating = true;
};
}
+ private CharSequence formatStateDescription() {
+ final int stringId = mMagnificationMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ ? R.string.magnification_mode_switch_state_window
+ : R.string.magnification_mode_switch_state_full_screen;
+ return mContext.getResources().getString(stringId);
+ }
+
private void applyResourcesValues() {
final int padding = mContext.getResources().getDimensionPixelSize(
R.dimen.magnification_switch_button_padding);
@@ -93,7 +141,6 @@ class MagnificationModeSwitch {
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
- mImageView.setAlpha(1.0f);
mImageView.animate().cancel();
mLastDown.set(event.getRawX(), event.getRawY());
mLastDrag.set(event.getRawX(), event.getRawY());
@@ -134,9 +181,13 @@ class MagnificationModeSwitch {
if (!mIsVisible) {
return;
}
+ // Reset button status.
+ mImageView.removeCallbacks(mFadeInAnimationTask);
+ mImageView.removeCallbacks(mFadeOutAnimationTask);
mImageView.animate().cancel();
+ mIsFadeOutAnimating = false;
+ mImageView.setAlpha(0f);
mWindowManager.removeView(mImageView);
- // Reset button status.
mIsVisible = false;
mParams.x = 0;
mParams.y = 0;
@@ -150,14 +201,15 @@ class MagnificationModeSwitch {
if (!mIsVisible) {
mWindowManager.addView(mImageView, mParams);
mIsVisible = true;
+ mImageView.postOnAnimation(mFadeInAnimationTask);
}
- mImageView.setAlpha(1.0f);
- // TODO(b/143852371): use accessibility timeout as a delay.
- // Dismiss the magnification switch button after the button is displayed for a period of
- // time.
- mImageView.animate().cancel();
- mImageView.removeCallbacks(mAnimationTask);
- mImageView.postDelayed(mAnimationTask, START_DELAY_MS);
+ if (mIsFadeOutAnimating) {
+ mImageView.animate().cancel();
+ mImageView.setAlpha(1f);
+ }
+ // Refresh the time slot of the fade-out task whenever this method is called.
+ mImageView.removeCallbacks(mFadeOutAnimationTask);
+ mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mVisibleDuration);
}
void onConfigurationChanged(int configDiff) {
@@ -187,6 +239,7 @@ class MagnificationModeSwitch {
imageView.setClickable(true);
imageView.setFocusable(true);
imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ imageView.setAlpha(0f);
return imageView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 911bf9ef757b..a705ec784c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -37,6 +37,7 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import javax.inject.Inject;
@@ -66,7 +67,8 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
@Inject
public WindowMagnification(Context context, @Main Handler mainHandler,
- CommandQueue commandQueue, ModeSwitchesController modeSwitchesController) {
+ CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
+ NavigationModeController navigationModeController) {
super(context);
mHandler = mainHandler;
mLastConfiguration = new Configuration(context.getResources().getConfiguration());
@@ -77,6 +79,9 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
final WindowMagnificationController controller = new WindowMagnificationController(mContext,
mHandler, new SfVsyncFrameCallbackProvider(), null,
new SurfaceControl.Transaction(), this);
+ final int navBarMode = navigationModeController.addListener(
+ controller::onNavigationModeChanged);
+ controller.onNavigationModeChanged(navBarMode);
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 714095631fdb..c3474bb7ca57 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -17,6 +17,8 @@
package com.android.systemui.accessibility;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,9 +32,11 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Range;
import android.view.Choreographer;
import android.view.Display;
import android.view.Gravity;
@@ -47,12 +51,17 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.R;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.text.NumberFormat;
+import java.util.Locale;
+
/**
* Class to handle adding and removing a window magnification.
*/
@@ -60,6 +69,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
MirrorWindowControl.MirrorWindowDelegate {
private static final String TAG = "WindowMagnificationController";
+ // Delay to avoid updating state description too frequently.
+ private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100;
+ // It should be consistent with the value defined in WindowMagnificationGestureHandler.
+ private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
+ private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
private final Context mContext;
private final Resources mResources;
private final Handler mHandler;
@@ -95,6 +109,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final View.OnLayoutChangeListener mMirrorViewLayoutChangeListener;
private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener;
private final Runnable mMirrorViewRunnable;
+ private final Runnable mUpdateStateDescriptionRunnable;
private View mMirrorView;
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
@@ -104,8 +119,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
+ private int mNavBarMode;
+ private int mNavGestureHeight;
+
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
private Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback;
+ private Locale mLocale;
+ private NumberFormat mPercentFormat;
@Nullable
private MirrorWindowControl mMirrorWindowControl;
@@ -164,6 +184,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
}
};
+ mUpdateStateDescriptionRunnable = () -> {
+ if (isWindowVisible()) {
+ mMirrorView.setStateDescription(formatStateDescription(mScale));
+ }
+ };
}
private void updateDimensions() {
@@ -175,6 +200,19 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
R.dimen.magnification_drag_view_size);
mOuterBorderSize = mResources.getDimensionPixelSize(
R.dimen.magnification_outer_border_margin);
+ updateNavigationBarDimensions();
+ }
+
+ private void updateNavigationBarDimensions() {
+ if (!supportsSwipeUpGesture()) {
+ mNavGestureHeight = 0;
+ return;
+ }
+ mNavGestureHeight = (mDisplaySize.x > mDisplaySize.y)
+ ? mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape)
+ : mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
}
/**
@@ -219,6 +257,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
+ /** Handles MirrorWindow position when the navigation bar mode changed. */
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ updateNavigationBarDimensions();
+ updateMirrorViewLayout();
+ }
+
/** Handles MirrorWindow position when the device rotation changed. */
private void onRotate() {
final Display display = mContext.getDisplay();
@@ -226,6 +271,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
display.getRealSize(mDisplaySize);
setMagnificationFrameBoundary();
mRotation = display.getRotation();
+ updateNavigationBarDimensions();
if (!isWindowVisible()) {
return;
@@ -292,12 +338,13 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener);
+ mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate());
+
mWm.addView(mMirrorView, params);
SurfaceHolder holder = mMirrorSurfaceView.getHolder();
holder.addCallback(this);
holder.setFormat(PixelFormat.RGBA_8888);
-
addDragTouchListeners();
}
@@ -380,15 +427,23 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* moved close to the screen edges.
*/
private void updateMirrorViewLayout() {
+ if (!isWindowVisible()) {
+ return;
+ }
+ final int maxMirrorViewX = mDisplaySize.x - mMirrorView.getWidth();
+ final int maxMirrorViewY = mDisplaySize.y - mMirrorView.getHeight() - mNavGestureHeight;
WindowManager.LayoutParams params =
(WindowManager.LayoutParams) mMirrorView.getLayoutParams();
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
+ // If nav bar mode supports swipe-up gesture, the Y position of mirror view should not
+ // overlap nav bar window to prevent window-dragging obscured.
+ if (supportsSwipeUpGesture()) {
+ params.y = Math.min(params.y, maxMirrorViewY);
+ }
// Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView
// able to move close to the screen edges.
- final int maxMirrorViewX = mDisplaySize.x - mMirrorView.getWidth();
- final int maxMirrorViewY = mDisplaySize.y - mMirrorView.getHeight();
final float translationX;
final float translationY;
if (params.x < 0) {
@@ -526,6 +581,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
final float offsetY = Float.isNaN(centerY) ? 0
: centerY - mMagnificationFrame.exactCenterY();
mScale = Float.isNaN(scale) ? mScale : scale;
+
setMagnificationFrameBoundary();
updateMagnificationFramePosition((int) offsetX, (int) offsetY);
if (!isWindowVisible()) {
@@ -546,6 +602,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return;
}
enableWindowMagnification(scale, Float.NaN, Float.NaN);
+ mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
+ mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
}
/**
@@ -596,4 +654,82 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private boolean isWindowVisible() {
return mMirrorView != null;
}
+
+ private boolean supportsSwipeUpGesture() {
+ return mNavBarMode == NAV_BAR_MODE_2BUTTON || mNavBarMode == NAV_BAR_MODE_GESTURAL;
+ }
+
+ private CharSequence formatStateDescription(float scale) {
+ // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed
+ // non-null, so the first time this is called we will always get the appropriate
+ // NumberFormat, then never regenerate it unless the locale changes on the fly.
+ final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+ if (!curLocale.equals(mLocale)) {
+ mLocale = curLocale;
+ mPercentFormat = NumberFormat.getPercentInstance(curLocale);
+ }
+ return mPercentFormat.format(scale);
+ }
+
+ private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(
+ new AccessibilityAction(R.id.accessibility_action_zoom_in,
+ mContext.getString(R.string.accessibility_control_zoom_in)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out,
+ mContext.getString(R.string.accessibility_control_zoom_out)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)));
+
+ info.setContentDescription(mContext.getString(R.string.magnification_window_title));
+ info.setStateDescription(formatStateDescription(getScale()));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (performA11yAction(action)) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private boolean performA11yAction(int action) {
+ if (action == R.id.accessibility_action_zoom_in) {
+ final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
+ setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ return true;
+ }
+ if (action == R.id.accessibility_action_zoom_out) {
+ final float scale = mScale - A11Y_CHANGE_SCALE_DIFFERENCE;
+ setScale(A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_up) {
+ move(0, -mSourceBounds.height());
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_down) {
+ move(0, mSourceBounds.height());
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_left) {
+ move(-mSourceBounds.width(), 0);
+ return true;
+ }
+ if (action == R.id.accessibility_action_move_right) {
+ move(mSourceBounds.width(), 0);
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 1b2e4c6a595e..c1c2de166627 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -26,6 +26,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
@@ -45,6 +47,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import java.io.PrintWriter;
@@ -66,8 +69,10 @@ import dagger.Lazy;
@SysUISingleton
final class AssistHandleReminderExpBehavior implements BehaviorController {
- private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
- private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count";
+ private static final Uri LEARNING_TIME_ELAPSED_URI =
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS);
+ private static final Uri LEARNING_EVENT_COUNT_URI =
+ Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT);
private static final String LEARNED_HINT_LAST_SHOWN_KEY =
"reminder_exp_learned_hint_last_shown";
private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(10);
@@ -167,6 +172,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
private final DeviceConfigHelper mDeviceConfigHelper;
private final Lazy<StatusBarStateController> mStatusBarStateController;
private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
+ private final Lazy<TaskStackChangeListeners> mTaskStackChangeListeners;
private final Lazy<OverviewProxyService> mOverviewProxyService;
private final Lazy<SysUiState> mSysUiFlagContainer;
private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
@@ -181,6 +187,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
private boolean mIsNavBarHidden;
private boolean mIsLauncherShowing;
private int mConsecutiveTaskSwitches;
+ @Nullable private ContentObserver mSettingObserver;
/** Whether user has learned the gesture. */
private boolean mIsLearned;
@@ -202,6 +209,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
DeviceConfigHelper deviceConfigHelper,
Lazy<StatusBarStateController> statusBarStateController,
Lazy<ActivityManagerWrapper> activityManagerWrapper,
+ Lazy<TaskStackChangeListeners> taskStackChangeListeners,
Lazy<OverviewProxyService> overviewProxyService,
Lazy<SysUiState> sysUiFlagContainer,
Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
@@ -213,6 +221,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mDeviceConfigHelper = deviceConfigHelper;
mStatusBarStateController = statusBarStateController;
mActivityManagerWrapper = activityManagerWrapper;
+ mTaskStackChangeListeners = taskStackChangeListeners;
mOverviewProxyService = overviewProxyService;
mSysUiFlagContainer = sysUiFlagContainer;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -240,7 +249,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
ActivityManager.RunningTaskInfo runningTaskInfo =
mActivityManagerWrapper.get().getRunningTask();
mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
- mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener);
+ mTaskStackChangeListeners.get().registerTaskStackListener(mTaskStackChangeListener);
mOverviewProxyService.get().addCallback(mOverviewProxyListener);
mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
@@ -248,9 +257,22 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
mLearningTimeElapsed = Settings.Secure.getLong(
- context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0);
+ context.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ /* default = */ 0);
mLearningCount = Settings.Secure.getInt(
- context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0);
+ context.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ /* default = */ 0);
+ mSettingObserver = new SettingsObserver(context, mHandler);
+ context.getContentResolver().registerContentObserver(
+ LEARNING_TIME_ELAPSED_URI,
+ /* notifyForDescendants = */ true,
+ mSettingObserver);
+ context.getContentResolver().registerContentObserver(
+ LEARNING_EVENT_COUNT_URI,
+ /* notifyForDescendants = */ true,
+ mSettingObserver);
mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
mLastLearningTimestamp = mClock.currentTimeMillis();
@@ -264,13 +286,25 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
if (mContext != null) {
mBroadcastDispatcher.get().unregisterReceiver(mDefaultHomeBroadcastReceiver);
mBootCompleteCache.get().removeListener(mBootCompleteListener);
- Settings.Secure.putLong(mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, 0);
- Settings.Secure.putInt(mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, 0);
+ mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+ mSettingObserver = null;
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ Long.toString(0L),
+ /* overrideableByRestore = */ true);
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ Integer.toString(0),
+ /* overrideableByRestore = */ true);
Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
mContext = null;
}
mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
- mActivityManagerWrapper.get().unregisterTaskStackListener(mTaskStackChangeListener);
+ mTaskStackChangeListeners.get().unregisterTaskStackListener(mTaskStackChangeListener);
mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
@@ -282,8 +316,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
return;
}
- Settings.Secure.putLong(
- mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount);
+ // putString in order to use overrideableByRestore
+ Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ Integer.toString(++mLearningCount),
+ /* overrideableByRestore = */ true);
}
@Override
@@ -460,8 +498,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
mIsLearned =
mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
- mHandler.post(() -> Settings.Secure.putLong(
- mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
+ // putString in order to use overrideableByRestore
+ mHandler.post(() -> Settings.Secure.putString(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ Long.toString(mLearningTimeElapsed),
+ /* overrideableByRestore = */ true));
}
private void resetConsecutiveTaskSwitches() {
@@ -589,4 +631,32 @@ final class AssistHandleReminderExpBehavior implements BehaviorController {
+ "="
+ getShowWhenTaught());
}
+
+ private final class SettingsObserver extends ContentObserver {
+
+ private final Context mContext;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (LEARNING_TIME_ELAPSED_URI.equals(uri)) {
+ mLastLearningTimestamp = mClock.currentTimeMillis();
+ mLearningTimeElapsed = Settings.Secure.getLong(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS,
+ /* default = */ 0);
+ } else if (LEARNING_EVENT_COUNT_URI.equals(uri)) {
+ mLearningCount = Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT,
+ /* default = */ 0);
+ }
+
+ super.onChange(selfChange, uri);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index 50d559b7aeab..61951cc0b5e9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -35,6 +35,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -82,7 +83,6 @@ public final class PhoneStateMonitor {
mStatusBarOptionalLazy = statusBarOptionalLazy;
mStatusBarStateController = Dependency.get(StatusBarStateController.class);
- ActivityManagerWrapper activityManagerWrapper = ActivityManagerWrapper.getInstance();
mDefaultHome = getCurrentDefaultHome();
bootCompleteCache.addListener(() -> mDefaultHome = getCurrentDefaultHome());
IntentFilter intentFilter = new IntentFilter();
@@ -95,12 +95,13 @@ public final class PhoneStateMonitor {
mDefaultHome = getCurrentDefaultHome();
}
}, intentFilter);
- mLauncherShowing = isLauncherShowing(activityManagerWrapper.getRunningTask());
- activityManagerWrapper.registerTaskStackListener(new TaskStackChangeListener() {
- @Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- mLauncherShowing = isLauncherShowing(taskInfo);
- }
+ mLauncherShowing = isLauncherShowing(ActivityManagerWrapper.getInstance().getRunningTask());
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(
+ new TaskStackChangeListener() {
+ @Override
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ mLauncherShowing = isLauncherShowing(taskInfo);
+ }
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index bde9a6e7c714..b1ae56a584d2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -36,7 +36,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -306,10 +306,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
- final List<FingerprintSensorProperties> fingerprintSensorProperties =
- mFingerprintManager.getSensorProperties();
- for (FingerprintSensorProperties props : fingerprintSensorProperties) {
- if (props.sensorType == FingerprintSensorProperties.TYPE_UDFPS) {
+ final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
+ mFingerprintManager.getSensorPropertiesInternal();
+ for (FingerprintSensorPropertiesInternal props : fingerprintSensorProperties) {
+ if (props.isAnyUdfpsType()) {
mUdfpsController = mUdfpsControllerFactory.get();
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index d79c96ea4774..e3b00495f3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.SuppressLint;
@@ -25,6 +26,7 @@ import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.PowerManager;
import android.os.UserHandle;
@@ -41,6 +43,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.BrightnessSynchronizer;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
@@ -56,7 +59,15 @@ import javax.inject.Inject;
/**
* Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
* and coordinates triggering of the high-brightness mode (HBM).
+ *
+ * Note that the current architecture is designed so that a single {@link UdfpsController}
+ * controls/manages all UDFPS sensors. In other words, a single controller is registered with
+ * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
+ * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@code sensorId} parameters.
*/
+@SuppressWarnings("deprecation")
class UdfpsController implements DozeReceiver {
private static final String TAG = "UdfpsController";
// Gamma approximation for the sRGB color space.
@@ -64,6 +75,10 @@ class UdfpsController implements DozeReceiver {
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
private final FingerprintManager mFingerprintManager;
+ // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
+ // sensors, this, in addition to a lot of the code here, will be updated.
+ @VisibleForTesting
+ final int mUdfpsSensorId;
private final WindowManager mWindowManager;
private final SystemSettings mSystemSettings;
private final DelayableExecutor mFgExecutor;
@@ -103,17 +118,17 @@ class UdfpsController implements DozeReceiver {
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
- public void showUdfpsOverlay() {
+ public void showUdfpsOverlay(int sensorId) {
UdfpsController.this.setShowOverlay(true);
}
@Override
- public void hideUdfpsOverlay() {
+ public void hideUdfpsOverlay(int sensorId) {
UdfpsController.this.setShowOverlay(false);
}
@Override
- public void setDebugMessage(String message) {
+ public void setDebugMessage(int sensorId, String message) {
mView.setDebugMessage(message);
}
}
@@ -165,6 +180,18 @@ class UdfpsController implements DozeReceiver {
mFgExecutor = fgExecutor;
mLayoutParams = createLayoutParams(context);
+ int udfpsSensorId = -1;
+ for (FingerprintSensorPropertiesInternal props :
+ mFingerprintManager.getSensorPropertiesInternal()) {
+ if (props.isAnyUdfpsType()) {
+ udfpsSensorId = props.sensorId;
+ break;
+ }
+ }
+ // At least one UDFPS sensor exists
+ checkArgument(udfpsSensorId != -1);
+ mUdfpsSensorId = udfpsSensorId;
+
mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
@@ -347,7 +374,7 @@ class UdfpsController implements DozeReceiver {
fw.write(mHbmEnableCommand);
fw.close();
}
- mFingerprintManager.onFingerDown(x, y, minor, major);
+ mFingerprintManager.onPointerDown(mUdfpsSensorId, x, y, minor, major);
} catch (IOException e) {
mView.hideScrimAndDot();
Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
@@ -355,7 +382,7 @@ class UdfpsController implements DozeReceiver {
}
private void onFingerUp() {
- mFingerprintManager.onFingerUp();
+ mFingerprintManager.onPointerUp(mUdfpsSensorId);
// Hiding the scrim before disabling HBM results in less noticeable flicker.
mView.hideScrimAndDot();
if (mHbmSupported) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index fbb47e2086b3..57d8dc70cd88 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -15,8 +15,8 @@
*/
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.AsyncTask.Status.FINISHED;
-import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
@@ -25,6 +25,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -35,16 +36,18 @@ import android.graphics.Bitmap;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
import java.util.Objects;
/**
@@ -169,11 +172,10 @@ class Bubble implements BubbleViewProvider {
}
@VisibleForTesting(visibility = PRIVATE)
- Bubble(@NonNull final NotificationEntry e,
+ Bubble(@NonNull final BubbleEntry entry,
@Nullable final BubbleController.NotificationSuppressionChangedListener listener,
final BubbleController.PendingIntentCanceledListener intentCancelListener) {
- Objects.requireNonNull(e);
- mKey = e.getKey();
+ mKey = entry.getKey();
mSuppressionListener = listener;
mIntentCancelListener = intent -> {
if (mIntent != null) {
@@ -181,7 +183,7 @@ class Bubble implements BubbleViewProvider {
}
intentCancelListener.onPendingIntentCanceled(this);
};
- setEntry(e);
+ setEntry(entry);
}
@Override
@@ -254,7 +256,8 @@ class Bubble implements BubbleViewProvider {
}
/**
- * Cleanup expanded view for bubbles going into overflow.
+ * Call this to clean up the task for the bubble. Ensure this is always called when done with
+ * the bubble.
*/
void cleanupExpandedView() {
if (mExpandedView != null) {
@@ -268,8 +271,7 @@ class Bubble implements BubbleViewProvider {
}
/**
- * Call when the views should be removed, ensure this is called to clean up ActivityView
- * content.
+ * Call when all the views should be removed/cleaned up.
*/
void cleanupViews() {
cleanupExpandedView();
@@ -294,6 +296,15 @@ class Bubble implements BubbleViewProvider {
}
/**
+ * Sets whether this bubble is considered visually interruptive. This method is purely for
+ * testing.
+ */
+ @VisibleForTesting
+ void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
+ mIsVisuallyInterruptive = visuallyInterruptive;
+ }
+
+ /**
* Starts a task to inflate & load any necessary information to display a bubble.
*
* @param callback the callback to notify one the bubble is ready to be displayed.
@@ -379,30 +390,28 @@ class Bubble implements BubbleViewProvider {
/**
* Sets the entry associated with this bubble.
*/
- void setEntry(@NonNull final NotificationEntry entry) {
+ void setEntry(@NonNull final BubbleEntry entry) {
Objects.requireNonNull(entry);
- Objects.requireNonNull(entry.getSbn());
- mLastUpdated = entry.getSbn().getPostTime();
- mIsBubble = entry.getSbn().getNotification().isBubbleNotification();
- mPackageName = entry.getSbn().getPackageName();
- mUser = entry.getSbn().getUser();
+ mLastUpdated = entry.getStatusBarNotification().getPostTime();
+ mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
+ mPackageName = entry.getStatusBarNotification().getPackageName();
+ mUser = entry.getStatusBarNotification().getUser();
mTitle = getTitle(entry);
- mIsClearable = entry.isClearable();
- mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
- mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
- mShouldSuppressPeek = entry.shouldSuppressPeek();
- mChannelId = entry.getSbn().getNotification().getChannelId();
- mNotificationId = entry.getSbn().getId();
- mAppUid = entry.getSbn().getUid();
- mInstanceId = entry.getSbn().getInstanceId();
- mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry);
- mShortcutInfo = (entry.getRanking() != null ? entry.getRanking().getShortcutInfo() : null);
- mMetadataShortcutId = (entry.getBubbleMetadata() != null
- ? entry.getBubbleMetadata().getShortcutId() : null);
+ mChannelId = entry.getStatusBarNotification().getNotification().getChannelId();
+ mNotificationId = entry.getStatusBarNotification().getId();
+ mAppUid = entry.getStatusBarNotification().getUid();
+ mInstanceId = entry.getStatusBarNotification().getInstanceId();
+ mFlyoutMessage = extractFlyoutMessage(entry);
if (entry.getRanking() != null) {
+ mShortcutInfo = entry.getRanking().getShortcutInfo();
mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
+ if (entry.getRanking().getChannel() != null) {
+ mIsImportantConversation =
+ entry.getRanking().getChannel().isImportantConversation();
+ }
}
if (entry.getBubbleMetadata() != null) {
+ mMetadataShortcutId = entry.getBubbleMetadata().getShortcutId();
mFlags = entry.getBubbleMetadata().getFlags();
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
@@ -419,12 +428,16 @@ class Bubble implements BubbleViewProvider {
} else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
// Was an intent bubble now it's a shortcut bubble... still unregister the listener
mIntent.unregisterCancelListener(mIntentCancelListener);
+ mIntentActive = false;
mIntent = null;
}
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
- mIsImportantConversation =
- entry.getChannel() != null && entry.getChannel().isImportantConversation();
+
+ mIsClearable = entry.isClearable();
+ mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
+ mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
+ mShouldSuppressPeek = entry.shouldSuppressPeek();
}
@Nullable
@@ -455,14 +468,6 @@ class Bubble implements BubbleViewProvider {
return mIntentActive;
}
- /**
- * @return the display id of the virtual display on which bubble contents is drawn.
- */
- @Override
- public int getDisplayId() {
- return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
- }
-
public InstanceId getInstanceId() {
return mInstanceId;
}
@@ -477,6 +482,14 @@ class Bubble implements BubbleViewProvider {
}
/**
+ * @return the task id of the task in which bubble contents is drawn.
+ */
+ @Override
+ public int getTaskId() {
+ return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID;
+ }
+
+ /**
* Should be invoked whenever a Bubble is accessed (selected while expanded).
*/
void markAsAccessedAt(long lastAccessedMillis) {
@@ -725,9 +738,75 @@ class Bubble implements BubbleViewProvider {
}
@Nullable
- private static String getTitle(@NonNull final NotificationEntry e) {
- final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence(
- Notification.EXTRA_TITLE);
+ private static String getTitle(@NonNull final BubbleEntry e) {
+ final CharSequence titleCharSeq = e.getStatusBarNotification()
+ .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE);
return titleCharSeq == null ? null : titleCharSeq.toString();
}
+
+ /**
+ * Returns our best guess for the most relevant text summary of the latest update to this
+ * notification, based on its type. Returns null if there should not be an update message.
+ */
+ @NonNull
+ static Bubble.FlyoutMessage extractFlyoutMessage(BubbleEntry entry) {
+ Objects.requireNonNull(entry);
+ final Notification underlyingNotif = entry.getStatusBarNotification().getNotification();
+ final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+ Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
+ bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
+ Notification.EXTRA_IS_GROUP_CONVERSATION);
+ try {
+ if (Notification.BigTextStyle.class.equals(style)) {
+ // Return the big text, it is big so probably important. If it's not there use the
+ // normal text.
+ CharSequence bigText =
+ underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+ bubbleMessage.message = !TextUtils.isEmpty(bigText)
+ ? bigText
+ : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ return bubbleMessage;
+ } else if (Notification.MessagingStyle.class.equals(style)) {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) underlyingNotif.extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ if (latestMessage != null) {
+ bubbleMessage.message = latestMessage.getText();
+ Person sender = latestMessage.getSenderPerson();
+ bubbleMessage.senderName = sender != null ? sender.getName() : null;
+ bubbleMessage.senderAvatar = null;
+ bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
+ return bubbleMessage;
+ }
+ } else if (Notification.InboxStyle.class.equals(style)) {
+ CharSequence[] lines =
+ underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+ // Return the last line since it should be the most recent.
+ if (lines != null && lines.length > 0) {
+ bubbleMessage.message = lines[lines.length - 1];
+ return bubbleMessage;
+ }
+ } else if (Notification.MediaStyle.class.equals(style)) {
+ // Return nothing, media updates aren't typically useful as a text update.
+ return bubbleMessage;
+ } else {
+ // Default to text extra.
+ bubbleMessage.message =
+ underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+ return bubbleMessage;
+ }
+ } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+ // No use crashing, we'll just return null and the caller will assume there's no update
+ // message.
+ e.printStackTrace();
+ }
+
+ return bubbleMessage;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index b6106025a17a..3f94b00d3c60 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -27,13 +28,10 @@ import static android.service.notification.NotificationListenerService.REASON_CL
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -47,6 +45,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -71,7 +70,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseSetArray;
-import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -81,17 +79,18 @@ import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.bubbles.dagger.BubbleModule;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.PinnedStackListenerForwarder;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
@@ -114,7 +113,10 @@ import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -130,7 +132,8 @@ import java.util.Objects;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationController.ConfigurationListener, Dumpable {
+public class BubbleController implements Bubbles, ConfigurationController.ConfigurationListener,
+ Dumpable {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -168,8 +171,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private final ShadeController mShadeController;
private final FloatingContentCoordinator mFloatingContentCoordinator;
private final BubbleDataRepository mDataRepository;
- private BubbleLogger mLogger = new BubbleLoggerImpl();
-
+ private BubbleLogger mLogger;
+ private final Handler mMainHandler;
private BubbleData mBubbleData;
private ScrimView mBubbleScrim;
@Nullable private BubbleStackView mStackView;
@@ -231,11 +234,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
*/
private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+ /**
+ * Last known font scale, used to detect font size changes in {@link #onConfigChanged}.
+ */
+ private float mFontScale = 0;
+
/** Last known direction, used to detect layout direction changes @link #onConfigChanged}. */
private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
private boolean mInflateSynchronously;
+ private MultiWindowTaskListener mTaskListener;
+
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
@@ -345,7 +355,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
/**
* Injected constructor. See {@link BubbleModule}.
*/
- public BubbleController(Context context,
+ public static BubbleController create(Context context,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
+ ConfigurationController configurationController,
+ NotificationInterruptStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManagerLegacy groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ FeatureFlags featureFlags,
+ DumpManager dumpManager,
+ FloatingContentCoordinator floatingContentCoordinator,
+ SysUiState sysUiState,
+ INotificationManager notificationManager,
+ @Nullable IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ @Main Handler mainHandler,
+ ShellTaskOrganizer organizer) {
+ BubbleLogger logger = new BubbleLogger(uiEventLogger);
+ return new BubbleController(context, notificationShadeWindowController,
+ statusBarStateController, shadeController, new BubbleData(context, logger),
+ synchronizer, configurationController, interruptionStateProvider, zenModeController,
+ notifUserManager, groupManager, entryManager, notifPipeline, featureFlags,
+ dumpManager, floatingContentCoordinator,
+ new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager,
+ statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger,
+ mainHandler, organizer);
+ }
+
+ /**
+ * Testing constructor.
+ */
+ @VisibleForTesting
+ BubbleController(Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
ShadeController shadeController,
@@ -366,7 +415,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
INotificationManager notificationManager,
@Nullable IStatusBarService statusBarService,
WindowManager windowManager,
- LauncherApps launcherApps) {
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ BubbleLogger bubbleLogger,
+ Handler mainHandler,
+ ShellTaskOrganizer organizer) {
dumpManager.registerDumpable(TAG, this);
mContext = context;
mShadeController = shadeController;
@@ -376,6 +429,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mFloatingContentCoordinator = floatingContentCoordinator;
mDataRepository = dataRepository;
mINotificationManager = notificationManager;
+ mLogger = bubbleLogger;
+ mMainHandler = mainHandler;
mZenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
@@ -414,7 +469,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (bubble.getBubbleIntent() == null) {
return;
}
- if (bubble.isIntentActive()) {
+ if (bubble.isIntentActive()
+ || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
bubble.setPendingIntentCanceled();
return;
}
@@ -438,10 +494,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
statusBarStateController.addCallback(mStatusBarStateListener);
mTaskStackListener = new BubbleTaskStackListener();
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
try {
- WindowManagerWrapper.getInstance().addPinnedStackListener(new BubblesImeListener());
+ windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
} catch (RemoteException e) {
e.printStackTrace();
}
@@ -473,6 +529,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
});
mBubbleIconFactory = new BubbleIconFactory(context);
+ mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer);
launcherApps.registerCallback(new LauncherApps.Callback() {
@Override
@@ -519,6 +576,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
/**
* See {@link NotifCallback}.
*/
+ @Override
public void addNotifCallback(NotifCallback callback) {
mCallbacks.add(callback);
}
@@ -534,6 +592,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
+ private void onBubbleExpandChanged(boolean shouldExpand) {
+ mSysUiState
+ .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
private void setupNEM() {
mNotificationEntryManager.addNotificationEntryListener(
new NotificationEntryListener() {
@@ -700,6 +764,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* since we want the scrim's appearance and behavior to be identical to that of the notification
* shade scrim.
*/
+ @Override
public ScrimView getScrimForBubble() {
return mBubbleScrim;
}
@@ -708,6 +773,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
*/
+ @Override
public void onStatusBarVisibilityChanged(boolean visible) {
if (mStackView != null) {
// Hide the stack temporarily if the status bar has been made invisible, and the stack
@@ -725,17 +791,24 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mInflateSynchronously = inflateSynchronously;
}
- void setOverflowListener(BubbleData.Listener listener) {
+ @Override
+ public void setOverflowListener(BubbleData.Listener listener) {
mOverflowListener = listener;
}
/**
* @return Bubbles for updating overflow.
*/
- List<Bubble> getOverflowBubbles() {
+ @Override
+ public List<Bubble> getOverflowBubbles() {
return mBubbleData.getOverflowBubbles();
}
+ @Override
+ public MultiWindowTaskListener getTaskManager() {
+ return mTaskListener;
+ }
+
/**
* BubbleStackView is lazily created by this method the first time a Bubble is added. This
* method initializes the stack view and adds it to the StatusBar just above the scrim.
@@ -744,8 +817,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mStackView == null) {
mStackView = new BubbleStackView(
mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
- this::hideCurrentInputMethod);
+ this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
+ this::hideCurrentInputMethod, this::onBubbleExpandChanged);
mStackView.setStackStartPosition(mPositionFromRemovedStack);
mStackView.addView(mBubbleScrim);
if (mExpandListener != null) {
@@ -778,9 +851,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- // Start not focusable - we'll become focusable when expanded so the ActivityView
- // can use the IME.
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
@@ -796,16 +868,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mAddedToWindowManager = true;
mWindowManager.addView(mStackView, mWmLayoutParams);
} catch (IllegalStateException e) {
- // This means the stack has already been added. This shouldn't happen, since we keep
- // track of that, but just in case, update the previously added view's layout params.
+ // This means the stack has already been added. This shouldn't happen...
e.printStackTrace();
- updateWmFlags();
}
}
private void onImeVisibilityChanged(boolean imeVisible) {
mImeVisible = imeVisible;
- updateWmFlags();
}
/** Removes the BubbleStackView from the WindowManager if it's there. */
@@ -832,35 +901,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with
- * the new params if the stack has been added.
- */
- private void updateWmFlags() {
- if (mStackView == null) {
- return;
- }
- if (isStackExpanded() && !mImeVisible) {
- // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures
- // that any taps within Bubbles (including on the ActivityView) results in Bubbles
- // receiving focus and clearing it from any other windows that might have it.
- mWmLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- } else {
- // If we're collapsed, we don't want to be focusable since tapping on the stack would
- // steal focus from apps. We also don't want to be focusable if the IME is visible,
- mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
- }
-
- if (mAddedToWindowManager) {
- try {
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
- } catch (IllegalArgumentException e) {
- // If the stack is somehow not there, ignore the attempt to update it.
- e.printStackTrace();
- }
- }
- }
-
- /**
* Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
* added in the meantime.
*/
@@ -948,6 +988,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleIconFactory = new BubbleIconFactory(mContext);
mStackView.onDisplaySizeChanged();
}
+ if (newConfig.fontScale != mFontScale) {
+ mFontScale = newConfig.fontScale;
+ mStackView.updateFlyout(mFontScale);
+ }
if (newConfig.getLayoutDirection() != mLayoutDirection) {
mLayoutDirection = newConfig.getLayoutDirection();
mStackView.onLayoutDirectionChanged(mLayoutDirection);
@@ -955,20 +999,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- boolean inLandscape() {
- return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
- }
-
/**
* Set a listener to be notified of bubble expand events.
*/
+ @Override
public void setExpandListener(BubbleExpandListener listener) {
mExpandListener = ((isExpanding, key) -> {
if (listener != null) {
listener.onBubbleExpandChanged(isExpanding, key);
}
-
- updateWmFlags();
});
if (mStackView != null) {
mStackView.setExpandListener(mExpandListener);
@@ -987,29 +1026,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return mBubbleData.hasBubbles();
}
- /**
- * Whether the stack of bubbles is expanded or not.
- */
+ @Override
public boolean isStackExpanded() {
return mBubbleData.isExpanded();
}
- /**
- * Tell the stack of bubbles to collapse.
- */
+ @Override
public void collapseStack() {
mBubbleData.setExpanded(false /* expanded */);
}
- /**
- * True if either:
- * (1) There is a bubble associated with the provided key and if its notification is hidden
- * from the shade.
- * (2) There is a group summary associated with the provided key that is hidden from the shade
- * because it has been dismissed but still has child bubbles active.
- *
- * False otherwise.
- */
+ @Override
public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
String key = entry.getKey();
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1021,19 +1048,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
return (isSummary && isSuppressedSummary) || isSuppressedBubble;
}
- /**
- * True if:
- * (1) The current notification entry same as selected bubble notification entry and the
- * stack is currently expanded.
- *
- * False otherwise.
- */
+ @Override
public boolean isBubbleExpanded(NotificationEntry entry) {
return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
- && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()) ? true : false;
+ && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey());
}
- void promoteBubbleFromOverflow(Bubble bubble) {
+ @Override
+ public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
@@ -1041,12 +1063,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
setIsBubble(bubble, true /* isBubble */);
}
- /**
- * Request the stack expand if needed, then select the specified Bubble as current.
- * If no bubble exists for this entry, one is created.
- *
- * @param entry the notification for the bubble to be selected
- */
+ @Override
public void expandStackAndSelectBubble(NotificationEntry entry) {
if (mStatusBarStateListener.getCurrentState() == SHADE) {
mNotifEntryToExpandOnShadeUnlock = null;
@@ -1074,12 +1091,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- /**
- * When a notification is marked Priority, expand the stack if needed,
- * then (maybe create and) select the given bubble.
- *
- * @param entry the notification for the bubble to show
- */
+ @Override
public void onUserChangedImportance(NotificationEntry entry) {
try {
int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -1095,16 +1107,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
- * is forwarded a back key down/up pair.
- */
- public void performBackPressIfNeeded() {
- if (mStackView != null) {
- mStackView.performBackPressIfNeeded();
- }
- }
-
- /**
* Adds or updates a bubble associated with the provided notification entry.
*
* @param notif the notification associated with this bubble.
@@ -1140,8 +1142,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
}
- Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);
- inflateAndAdd(bubble, suppressFlyout, showInShade);
+ if (!notif.getRanking().visuallyInterruptive()
+ && (notif.getBubbleMetadata() != null
+ && !notif.getBubbleMetadata().getAutoExpandBubble())
+ && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
+ // Update the bubble but don't promote it out of overflow
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
+ b.setEntry(notifToBubbleEntry(notif));
+ } else {
+ Bubble bubble = mBubbleData.getOrCreateBubble(
+ notifToBubbleEntry(notif), null /* persistedBubble */);
+ inflateAndAdd(bubble, suppressFlyout, showInShade);
+ }
}
void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
@@ -1152,15 +1164,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
}
- /**
- * Called when a user has indicated that an active notification should be shown as a bubble.
- * <p>
- * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
- * the notification from appearing in the shade.
- *
- * @param entry the notification to change bubble state for.
- * @param shouldBubble whether the notification should show as a bubble or not.
- */
+ @Override
public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
NotificationChannel channel = entry.getChannel();
final String appPkg = entry.getSbn().getPackageName();
@@ -1199,13 +1203,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- /**
- * Removes the bubble with the given key.
- * <p>
- * Must be called from the main thread.
- */
@MainThread
- void removeBubble(String key, int reason) {
+ @Override
+ public void removeBubble(String key, int reason) {
if (mBubbleData.hasAnyBubbleWithKey(key)) {
mBubbleData.dismissBubbleWithKey(key, reason);
}
@@ -1237,8 +1237,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mBubbleData.removeSuppressedSummary(groupKey);
// Remove any associated bubble children with the summary
- final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager);
+ final List<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
for (int i = 0; i < bubbleChildren.size(); i++) {
removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
}
@@ -1284,6 +1283,25 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
+ /**
+ * Retrieves any bubbles that are part of the notification group represented by the provided
+ * group key.
+ */
+ private ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+ ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+ if (groupKey == null) {
+ return bubbleChildren;
+ }
+ for (Bubble bubble : mBubbleData.getActiveBubbles()) {
+ final NotificationEntry entry =
+ mNotificationEntryManager.getPendingOrActiveNotif(bubble.getKey());
+ if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
+ bubbleChildren.add(bubble);
+ }
+ }
+ return bubbleChildren;
+ }
+
private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble,
final boolean autoExpand) {
Objects.requireNonNull(entry);
@@ -1394,8 +1412,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
if (entry != null) {
final String groupKey = entry.getSbn().getGroupKey();
- if (mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager).isEmpty()) {
+ if (getBubblesInGroup(groupKey).isEmpty()) {
// Time to potentially remove the summary
for (NotifCallback cb : mCallbacks) {
cb.maybeCancelSummary(entry);
@@ -1447,16 +1464,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
};
- /**
- * We intercept notification entries (including group summaries) dismissed by the user when
- * there is an active bubble associated with it. We do this so that developers can still
- * cancel it (and hence the bubbles associated with it). However, these intercepted
- * notifications should then be hidden from the shade since the user has cancelled them, so we
- * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
- * {@link BubbleData#addSummaryToSuppress}.
- *
- * @return true if we want to intercept the dismissal of the entry, else false.
- */
+ @Override
public boolean handleDismissalInterception(NotificationEntry entry) {
if (entry == null) {
return false;
@@ -1487,8 +1495,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
String groupKey = entry.getSbn().getGroupKey();
- ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(
- groupKey, mNotificationEntryManager);
+ ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
&& mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
@@ -1580,21 +1587,20 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
- * The display id of the expanded view, if the stack is expanded and not occluded by the
- * status bar, otherwise returns {@link Display#INVALID_DISPLAY}.
+ * The task id of the expanded view, if the stack is expanded and not occluded by the
+ * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
*/
- public int getExpandedDisplayId(Context context) {
+ private int getExpandedTaskId() {
if (mStackView == null) {
- return INVALID_DISPLAY;
+ return INVALID_TASK_ID;
}
- final boolean defaultDisplay = context.getDisplay() != null
- && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY;
final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
- if (defaultDisplay && expandedViewProvider != null && isStackExpanded()
+ if (expandedViewProvider != null && isStackExpanded()
+ && !mStackView.isExpansionAnimating()
&& !mNotificationShadeWindowController.getPanelExpanded()) {
- return expandedViewProvider.getDisplayId();
+ return expandedViewProvider.getTaskId();
}
- return INVALID_DISPLAY;
+ return INVALID_TASK_ID;
}
@VisibleForTesting
@@ -1628,18 +1634,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
- if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
- if (!mStackView.isExpansionAnimating()) {
- mBubbleData.setExpanded(false);
- }
+ int expandedId = getExpandedTaskId();
+ if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+ mBubbleData.setExpanded(false);
}
}
@Override
- public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
+ public void onActivityRestartAttempt(RunningTaskInfo taskInfo, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
- if (b.getDisplayId() == task.displayId) {
+ if (taskInfo.taskId == b.getTaskId()) {
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -1647,43 +1652,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
- @Override
- public void onActivityLaunchOnSecondaryDisplayRerouted() {
- if (mStackView != null) {
- mBubbleData.setExpanded(false);
- }
- }
-
- @Override
- public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
- if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
- if (mImeVisible) {
- hideCurrentInputMethod();
- } else {
- mBubbleData.setExpanded(false);
- }
- }
- }
-
- @Override
- public void onSingleTaskDisplayDrawn(int displayId) {
- if (mStackView == null) {
- return;
- }
- mStackView.showExpandedViewContents(displayId);
- }
-
- @Override
- public void onSingleTaskDisplayEmpty(int displayId) {
- final BubbleViewProvider expandedBubble = mStackView != null
- ? mStackView.getExpandedBubble()
- : null;
- int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
- if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
- mBubbleData.setExpanded(false);
- }
- mBubbleData.notifyDisplayEmpty(displayId);
- }
}
/**
@@ -1727,6 +1695,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/** PinnedStackListener that dispatches IME visibility updates to the stack. */
+ //TODO(b/170442945): Better way to do this / insets listener?
private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -1735,4 +1704,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
}
+
+ static BubbleEntry notifToBubbleEntry(NotificationEntry e) {
+ return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(),
+ e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(),
+ e.shouldSuppressPeek());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index bab18ec053ee..b4626f27d370 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -32,12 +32,9 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController.DismissReason;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -52,15 +49,12 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import javax.inject.Inject;
-
/**
* Keeps track of active bubbles.
*/
-@SysUISingleton
public class BubbleData {
- private BubbleLoggerImpl mLogger = new BubbleLoggerImpl();
+ private BubbleLogger mLogger;
private int mCurrentUserId;
@@ -154,14 +148,14 @@ public class BubbleData {
* associated with it). This list is used to check if the summary should be hidden from the
* shade.
*
- * Key: group key of the NotificationEntry
- * Value: key of the NotificationEntry
+ * Key: group key of the notification
+ * Value: key of the notification
*/
private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
- @Inject
- public BubbleData(Context context) {
+ public BubbleData(Context context, BubbleLogger bubbleLogger) {
mContext = context;
+ mLogger = bubbleLogger;
mBubbles = new ArrayList<>();
mOverflowBubbles = new ArrayList<>();
mPendingBubbles = new HashMap<>();
@@ -205,6 +199,11 @@ public class BubbleData {
return mSelectedBubble;
}
+ /** Return a read-only current active bubble lists. */
+ public List<Bubble> getActiveBubbles() {
+ return Collections.unmodifiableList(mBubbles);
+ }
+
public void setExpanded(boolean expanded) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setExpanded: " + expanded);
@@ -235,8 +234,8 @@ public class BubbleData {
* @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from
* the overflow that was persisted over reboot.
*/
- Bubble getOrCreateBubble(NotificationEntry entry, Bubble persistedBubble) {
- String key = entry != null ? entry.getKey() : persistedBubble.getKey();
+ public Bubble getOrCreateBubble(BubbleEntry entry, Bubble persistedBubble) {
+ String key = persistedBubble != null ? persistedBubble.getKey() : entry.getKey();
Bubble bubbleToReturn = getBubbleInStackWithKey(key);
if (bubbleToReturn == null) {
@@ -266,7 +265,7 @@ public class BubbleData {
/**
* When this method is called it is expected that all info in the bubble has completed loading.
* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
- * BubbleStackView, BubbleIconFactory).
+ * BubbleStackView, BubbleIconFactory, boolean).
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
if (DEBUG_BUBBLE_DATA) {
@@ -284,7 +283,8 @@ public class BubbleData {
} else {
// Updates an existing bubble
bubble.setSuppressFlyout(suppressFlyout);
- doUpdate(bubble);
+ // If there is no flyout, we probably shouldn't show the bubble at the top
+ doUpdate(bubble, !suppressFlyout /* reorder */);
}
if (bubble.shouldAutoExpand()) {
@@ -328,7 +328,7 @@ public class BubbleData {
* Retrieves the notif entry key of the summary associated with the provided group key.
*
* @param groupKey the group to look up
- * @return the key for the {@link NotificationEntry} that is the summary of this group.
+ * @return the key for the notification that is the summary of this group.
*/
String getSummaryKey(String groupKey) {
return mSuppressedGroupKeys.get(groupKey);
@@ -349,25 +349,6 @@ public class BubbleData {
}
/**
- * Retrieves any bubbles that are part of the notification group represented by the provided
- * group key.
- */
- ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull
- NotificationEntryManager nem) {
- ArrayList<Bubble> bubbleChildren = new ArrayList<>();
- if (groupKey == null) {
- return bubbleChildren;
- }
- for (Bubble b : mBubbles) {
- final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey());
- if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
- bubbleChildren.add(b);
- }
- }
- return bubbleChildren;
- }
-
- /**
* Removes bubbles from the given package whose shortcut are not in the provided list of valid
* shortcuts.
*/
@@ -438,12 +419,12 @@ public class BubbleData {
}
}
- private void doUpdate(Bubble bubble) {
+ private void doUpdate(Bubble bubble, boolean reorder) {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doUpdate: " + bubble);
}
mStateChange.updatedBubble = bubble;
- if (!isExpanded()) {
+ if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
mBubbles.remove(bubble);
mBubbles.add(0, bubble);
@@ -570,22 +551,6 @@ public class BubbleData {
dispatchPendingChanges();
}
- /**
- * Indicates that the provided display is no longer in use and should be cleaned up.
- *
- * @param displayId the id of the display to clean up.
- */
- void notifyDisplayEmpty(int displayId) {
- for (Bubble b : mBubbles) {
- if (b.getDisplayId() == displayId) {
- if (b.getExpandedView() != null) {
- b.getExpandedView().notifyDisplayEmpty();
- }
- return;
- }
- }
- }
-
private void dispatchPendingChanges() {
if (mListener != null && mStateChange.anythingChanged()) {
mListener.applyUpdate(mStateChange);
@@ -636,12 +601,12 @@ public class BubbleData {
* @param normalX Normalized x position of the stack
* @param normalY Normalized y position of the stack
*/
- void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
+ void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
int bubbleCount, int bubbleIndex, float normalX, float normalY) {
if (provider == null) {
mLogger.logStackUiChanged(packageName, action, bubbleCount, normalX, normalY);
} else if (provider.getKey().equals(BubbleOverflow.KEY)) {
- if (action == SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
+ if (action == FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
mLogger.logShowOverflow(packageName, mCurrentUserId);
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index f129d3147032..2ab9e8734bef 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -17,6 +17,7 @@ package com.android.systemui.bubbles
import android.annotation.SuppressLint
import android.annotation.UserIdInt
+import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
@@ -26,21 +27,16 @@ import android.util.Log
import com.android.systemui.bubbles.storage.BubbleEntity
import com.android.systemui.bubbles.storage.BubblePersistentRepository
import com.android.systemui.bubbles.storage.BubbleVolatileRepository
-import com.android.systemui.dagger.SysUISingleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
-import javax.inject.Inject
-@SysUISingleton
-internal class BubbleDataRepository @Inject constructor(
- private val volatileRepository: BubbleVolatileRepository,
- private val persistentRepository: BubblePersistentRepository,
- private val launcherApps: LauncherApps
-) {
+internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps) {
+ private val volatileRepository = BubbleVolatileRepository(launcherApps)
+ private val persistentRepository = BubblePersistentRepository(context)
private val ioScope = CoroutineScope(Dispatchers.IO)
private val uiScope = CoroutineScope(Dispatchers.Main)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
new file mode 100644
index 000000000000..6a1302518699
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
@@ -0,0 +1,98 @@
+/*
+ * 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.bubbles;
+
+import android.app.Notification.BubbleMetadata;
+import android.app.NotificationManager.Policy;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Represents a notification with needed data and flag for bubbles.
+ *
+ * @see Bubble
+ */
+public class BubbleEntry {
+
+ private StatusBarNotification mSbn;
+ private Ranking mRanking;
+
+ private boolean mIsClearable;
+ private boolean mShouldSuppressNotificationDot;
+ private boolean mShouldSuppressNotificationList;
+ private boolean mShouldSuppressPeek;
+
+ public BubbleEntry(@NonNull StatusBarNotification sbn,
+ Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
+ boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
+ mSbn = sbn;
+ mRanking = ranking;
+
+ mIsClearable = isClearable;
+ mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
+ mShouldSuppressNotificationList = shouldSuppressNotificationList;
+ mShouldSuppressPeek = shouldSuppressPeek;
+ }
+
+ /** @return the {@link StatusBarNotification} for this entry. */
+ @NonNull
+ public StatusBarNotification getStatusBarNotification() {
+ return mSbn;
+ }
+
+ /** @return the {@link Ranking} for this entry. */
+ public Ranking getRanking() {
+ return mRanking;
+ }
+
+ /** @return the key in the {@link StatusBarNotification}. */
+ public String getKey() {
+ return mSbn.getKey();
+ }
+
+ /** @return the {@link BubbleMetadata} in the {@link StatusBarNotification}. */
+ @Nullable
+ public BubbleMetadata getBubbleMetadata() {
+ return getStatusBarNotification().getNotification().getBubbleMetadata();
+ }
+
+ /** @return true if this notification is clearable. */
+ public boolean isClearable() {
+ return mIsClearable;
+ }
+
+ /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
+ public boolean shouldSuppressNotificationDot() {
+ return mShouldSuppressNotificationDot;
+ }
+
+ /**
+ * @return true if {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
+ * set for this notification.
+ */
+ public boolean shouldSuppressNotificationList() {
+ return mShouldSuppressNotificationList;
+ }
+
+ /** @return true if {@link Policy#SUPPRESSED_EFFECT_PEEK} set for this notification. */
+ public boolean shouldSuppressPeek() {
+ return mShouldSuppressPeek;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index ec60cbd175d0..98a2257d2daa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,27 +16,19 @@
package com.android.systemui.bubbles;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static android.graphics.PixelFormat.TRANSPARENT;
-import static android.view.Display.INVALID_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.systemui.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.ActivityView;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -49,18 +41,13 @@ import android.graphics.Outline;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
-import android.hardware.display.VirtualDisplay;
-import android.os.Binder;
-import android.os.RemoteException;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.Gravity;
import android.view.SurfaceControl;
-import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -82,18 +69,6 @@ import java.io.PrintWriter;
*/
public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
- private static final String WINDOW_TITLE = "ImeInsetsWindowWithoutContent";
-
- private enum ActivityViewStatus {
- // ActivityView is being initialized, cannot start an activity yet.
- INITIALIZING,
- // ActivityView is initialized, and ready to start an activity.
- INITIALIZED,
- // Activity runs in the ActivityView.
- ACTIVITY_STARTED,
- // ActivityView is released, so activity launching will no longer be permitted.
- RELEASED,
- }
// The triangle pointing to the expanded view
private View mPointerView;
@@ -101,16 +76,11 @@ public class BubbleExpandedView extends LinearLayout {
@Nullable private int[] mExpandedViewContainerLocation;
private AlphaOptimizedButton mSettingsIcon;
+ private TaskView mTaskView;
- // Views for expanded state
- private ActivityView mActivityView;
-
- private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
- private int mTaskId = -1;
-
- private PendingIntent mPendingIntent;
+ private int mTaskId = INVALID_TASK_ID;
- private boolean mKeyboardVisible;
+ private boolean mImeVisible;
private boolean mNeedsNewHeight;
private Point mDisplaySize;
@@ -121,131 +91,119 @@ public class BubbleExpandedView extends LinearLayout {
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
private int mExpandedViewPadding;
-
+ private float mCornerRadius = 0f;
@Nullable private Bubble mBubble;
+ private PendingIntent mPendingIntent;
private boolean mIsOverflow;
- private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+ private Bubbles mBubbles = Dependency.get(Bubbles.class);
private WindowManager mWindowManager;
- private ActivityManager mActivityManager;
-
private BubbleStackView mStackView;
- private View mVirtualImeView;
- private WindowManager mVirtualDisplayWindowManager;
- private boolean mImeShowing = false;
- private float mCornerRadius = 0f;
/**
* Container for the ActivityView that has a solid, round-rect background that shows if the
* ActivityView hasn't loaded.
*/
- private FrameLayout mActivityViewContainer = new FrameLayout(getContext());
+ private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());
- /** The SurfaceView that the ActivityView draws to. */
- @Nullable private SurfaceView mActivitySurface;
+ private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
- private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
@Override
- public void onActivityViewReady(ActivityView view) {
+ public void onInitialized() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+ Log.d(TAG, "onActivityViewReady: destroyed=" + mDestroyed
+ + " initialized=" + mInitialized
+ " bubble=" + getBubbleKey());
}
- switch (mActivityViewStatus) {
- case INITIALIZING:
- case INITIALIZED:
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- 0 /* enterResId */, 0 /* exitResId */);
- options.setTaskAlwaysOnTop(true);
- // Soptions.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- // Post to keep the lifecycle normal
- post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: calling startActivity, "
- + "bubble=" + getBubbleKey());
- }
- if (mActivityView == null) {
- mBubbleController.removeBubble(getBubbleKey(),
- BubbleController.DISMISS_INVALID_INTENT);
- return;
- }
- try {
- if (!mIsOverflow && mBubble.hasMetadataShortcutId()
- && mBubble.getShortcutInfo() != null) {
- options.setApplyActivityFlagsForBubbles(true);
- mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
- options, null /* sourceBounds */);
- } else {
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- if (mBubble != null) {
- mBubble.setIntentActive();
- }
- mActivityView.startActivity(mPendingIntent, fillInIntent, options);
- }
- } catch (RuntimeException e) {
- // If there's a runtime exception here then there's something
- // wrong with the intent, we can't really recover / try to populate
- // the bubble again so we'll just remove it.
- Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
- + ", " + e.getMessage() + "; removing bubble");
- mBubbleController.removeBubble(getBubbleKey(),
- BubbleController.DISMISS_INVALID_INTENT);
- }
- });
- mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
- break;
- case ACTIVITY_STARTED:
- post(() -> mActivityManager.moveTaskToFront(mTaskId, 0));
- break;
+
+ if (mDestroyed || mInitialized) {
+ return;
}
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onActivityViewReady: calling startActivity, bubble="
+ + getBubbleKey());
+ }
+ try {
+ if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, null /* sourceBounds */);
+ } else {
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mBubbles.removeBubble(getBubbleKey(),
+ BubbleController.DISMISS_INVALID_INTENT);
+ }
+ });
+ mInitialized = true;
}
@Override
- public void onActivityViewDestroyed(ActivityView view) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
- + " bubble=" + getBubbleKey());
- }
- mActivityViewStatus = ActivityViewStatus.RELEASED;
+ public void onReleased() {
+ mDestroyed = true;
}
@Override
- public void onTaskCreated(int taskId, ComponentName componentName) {
+ public void onTaskCreated(int taskId, ComponentName name) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ " bubble=" + getBubbleKey());
}
- // Since Bubble ActivityView applies singleTaskDisplay this is
- // guaranteed to only be called once per ActivityView. The taskId is
- // saved to use for removeTask, preventing appearance in recent tasks.
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ setContentVisibility(true);
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ setContentVisibility(visible);
}
- /**
- * This is only called for tasks on this ActivityView, which is also set to
- * single-task mode -- meaning never more than one task on this display. If a task
- * is being removed, it's the top Activity finishing and this bubble should
- * be removed or collapsed.
- */
@Override
public void onTaskRemovalStarted(int taskId) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " mActivityViewStatus=" + mActivityViewStatus
+ " bubble=" + getBubbleKey());
}
if (mBubble != null) {
// Must post because this is called from a binder thread.
- post(() -> mBubbleController.removeBubble(mBubble.getKey(),
+ post(() -> mBubbles.removeBubble(mBubble.getKey(),
BubbleController.DISMISS_TASK_FINISHED));
}
}
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mStackView.isExpanded()) {
+ mBubbles.collapseStack();
+ }
+ }
};
public BubbleExpandedView(Context context) {
@@ -264,7 +222,6 @@ public class BubbleExpandedView extends LinearLayout {
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
updateDimensions();
- mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
void updateDimensions() {
@@ -282,9 +239,6 @@ public class BubbleExpandedView extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
- }
Resources res = getResources();
mPointerView = findViewById(R.id.pointer_view);
@@ -299,35 +253,21 @@ public class BubbleExpandedView extends LinearLayout {
R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
- mActivityView = new ActivityView.Builder(mContext)
- .setSingleInstance(true)
- .setDisableSurfaceViewBackgroundLayer(true)
- .setUseTrustedDisplay(true)
- .build();
-
+ mTaskView = new TaskView(mContext, mBubbles.getTaskManager());
// Set ActivityView's alpha value as zero, since there is no view content to be shown.
setContentVisibility(false);
- mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
+ mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
}
});
- mActivityViewContainer.setClipToOutline(true);
- mActivityViewContainer.addView(mActivityView);
- mActivityViewContainer.setLayoutParams(
+ mExpandedViewContainer.setClipToOutline(true);
+ mExpandedViewContainer.addView(mTaskView);
+ mExpandedViewContainer.setLayoutParams(
new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- addView(mActivityViewContainer);
-
- if (mActivityView != null
- && mActivityView.getChildCount() > 0
- && mActivityView.getChildAt(0) instanceof SurfaceView) {
- // Retrieve the surface from the ActivityView so we can screenshot it and change its
- // z-ordering. This should always be possible, since ActivityView's constructor adds the
- // SurfaceView as its first child.
- mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
- }
+ addView(mExpandedViewContainer);
// Expanded stack layout, top to bottom:
// Expanded view container
@@ -335,33 +275,22 @@ public class BubbleExpandedView extends LinearLayout {
// ==> expanded view
// ==> activity view
// ==> manage button
- bringChildToFront(mActivityView);
+ bringChildToFront(mTaskView);
bringChildToFront(mSettingsIcon);
+ mTaskView.setListener(mTaskViewListener);
applyThemeAttrs();
- setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- // Keep track of IME displaying because we should not make any adjustments that might
- // cause a config change while the IME is displayed otherwise it'll loose focus.
- final int keyboardHeight = insets.getSystemWindowInsetBottom()
- - insets.getStableInsetBottom();
- mKeyboardVisible = keyboardHeight != 0;
- if (!mKeyboardVisible && mNeedsNewHeight) {
- updateHeight();
- }
- return view.onApplyWindowInsets(insets);
- });
-
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
mExpandedViewPadding);
setOnTouchListener((view, motionEvent) -> {
- if (!usingActivityView()) {
+ if (mTaskView == null) {
return false;
}
final Rect avBounds = new Rect();
- mActivityView.getBoundsOnScreen(avBounds);
+ mTaskView.getBoundsOnScreen(avBounds);
// Consume and ignore events on the expanded view padding that are within the
// ActivityView's vertical bounds. These events are part of a back gesture, and so they
@@ -387,51 +316,58 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Asks the ActivityView's surface to draw on top of all other views in the window. This is
- * useful for ordering surfaces during animations, but should otherwise be set to false so that
- * bubbles and menus can draw over the ActivityView.
+ * Sets whether the surface displaying app content should sit on top. This is useful for
+ * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
+ * being dragged out, the manage menu) this is set to false, otherwise it should be true.
*/
void setSurfaceZOrderedOnTop(boolean onTop) {
- if (mActivitySurface == null) {
+ if (mTaskView == null) {
return;
}
+ mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
+ }
- mActivitySurface.setZOrderedOnTop(onTop, true);
+ void setImeVisible(boolean visible) {
+ mImeVisible = visible;
+ if (!mImeVisible && mNeedsNewHeight) {
+ updateHeight();
+ }
}
- /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
+ /** Return a GraphicBuffer with the contents of the task view surface. */
@Nullable
SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
- if (mActivitySurface == null) {
+ if (mTaskView == null) {
return null;
}
-
return SurfaceControl.captureLayers(
- mActivitySurface.getSurfaceControl(),
- new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
+ mTaskView.getSurfaceControl(),
+ new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()),
1 /* scale */);
}
- int[] getActivityViewLocationOnScreen() {
- if (mActivityView != null) {
- return mActivityView.getLocationOnScreen();
+ int[] getTaskViewLocationOnScreen() {
+ if (mTaskView != null) {
+ return mTaskView.getLocationOnScreen();
} else {
return new int[]{0, 0};
}
}
+ // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
void setManageClickListener(OnClickListener manageClickListener) {
- findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+ mSettingsIcon.setOnClickListener(manageClickListener);
}
/**
- * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
- * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
- * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+ * Updates the obscured touchable region for the task surface. This calls onLocationChanged,
+ * which results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is
+ * useful if a view has been added or removed from on top of the ActivityView, such as the
+ * manage menu.
*/
void updateObscuredTouchableRegion() {
- if (mActivityView != null) {
- mActivityView.onLocationChanged();
+ if (mTaskView != null) {
+ mTaskView.onLocationChanged();
}
}
@@ -440,12 +376,12 @@ public class BubbleExpandedView extends LinearLayout {
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
mCornerRadius = ta.getDimensionPixelSize(0, 0);
- mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
+ mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
ta.recycle();
- if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
+ if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
mContext.getResources())) {
- mActivityView.setCornerRadius(mCornerRadius);
+ mTaskView.setCornerRadius(mCornerRadius);
}
final int mode =
@@ -464,11 +400,8 @@ public class BubbleExpandedView extends LinearLayout {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mKeyboardVisible = false;
+ mImeVisible = false;
mNeedsNewHeight = false;
- if (mActivityView != null) {
- setImeWindowToDisplay(0, 0);
- }
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
}
@@ -490,84 +423,23 @@ public class BubbleExpandedView extends LinearLayout {
final float alpha = visibility ? 1f : 0f;
mPointerView.setAlpha(alpha);
-
- if (mActivityView != null && alpha != mActivityView.getAlpha()) {
- mActivityView.setAlpha(alpha);
- mActivityView.bringToFront();
+ if (mTaskView == null) {
+ return;
+ }
+ if (alpha != mTaskView.getAlpha()) {
+ mTaskView.setAlpha(alpha);
}
}
- @Nullable ActivityView getActivityView() {
- return mActivityView;
+ @Nullable
+ View getTaskView() {
+ return mTaskView;
}
int getTaskId() {
return mTaskId;
}
- /**
- * Called by {@link BubbleStackView} when the insets for the expanded state should be updated.
- * This should be done post-move and post-animation.
- */
- void updateInsets(WindowInsets insets) {
- if (usingActivityView()) {
- int[] screenLoc = mActivityView.getLocationOnScreen();
- final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
- final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(),
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetBottom()
- : 0);
- setImeWindowToDisplay(getWidth(), Math.max(activityViewBottom - keyboardTop, 0));
- }
- }
-
- private void setImeWindowToDisplay(int w, int h) {
- if (getVirtualDisplayId() == INVALID_DISPLAY) {
- return;
- }
- if (h == 0 || w == 0) {
- if (mImeShowing) {
- mVirtualImeView.setVisibility(GONE);
- mImeShowing = false;
- }
- return;
- }
- final Context virtualDisplayContext = mContext.createDisplayContext(
- getVirtualDisplay().getDisplay());
-
- if (mVirtualDisplayWindowManager == null) {
- mVirtualDisplayWindowManager =
- (WindowManager) virtualDisplayContext.getSystemService(Context.WINDOW_SERVICE);
- }
- if (mVirtualImeView == null) {
- mVirtualImeView = new View(virtualDisplayContext);
- mVirtualImeView.setVisibility(VISIBLE);
- mVirtualDisplayWindowManager.addView(mVirtualImeView,
- getVirtualImeViewAttrs(w, h));
- } else {
- mVirtualDisplayWindowManager.updateViewLayout(mVirtualImeView,
- getVirtualImeViewAttrs(w, h));
- mVirtualImeView.setVisibility(VISIBLE);
- }
-
- mImeShowing = true;
- }
-
- private WindowManager.LayoutParams getVirtualImeViewAttrs(int w, int h) {
- // To use TYPE_NAVIGATION_BAR_PANEL instead of TYPE_IME_BAR to bypass the IME window type
- // token check when adding the window.
- final WindowManager.LayoutParams attrs =
- new WindowManager.LayoutParams(w, h, TYPE_NAVIGATION_BAR_PANEL,
- FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE,
- TRANSPARENT);
- attrs.gravity = Gravity.BOTTOM;
- attrs.setTitle(WINDOW_TITLE);
- attrs.token = new Binder();
- attrs.providesInsetsTypes = new int[]{ITYPE_IME};
- attrs.alpha = 0.0f;
- return attrs;
- }
-
void setStackView(BubbleStackView stackView) {
mStackView = stackView;
}
@@ -576,7 +448,10 @@ public class BubbleExpandedView extends LinearLayout {
mIsOverflow = overflow;
Intent target = new Intent(mContext, BubbleOverflowActivity.class);
- mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0,
+ Bundle extras = new Bundle();
+ extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mBubbles));
+ target.putExtras(extras);
+ mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
target, PendingIntent.FLAG_UPDATE_CURRENT);
mSettingsIcon.setVisibility(GONE);
}
@@ -614,7 +489,7 @@ public class BubbleExpandedView extends LinearLayout {
mPendingIntent = mBubble.getBubbleIntent();
if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
setContentVisibility(false);
- mActivityView.setVisibility(VISIBLE);
+ mTaskView.setVisibility(VISIBLE);
}
}
applyThemeAttrs();
@@ -624,59 +499,42 @@ public class BubbleExpandedView extends LinearLayout {
}
}
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubbles'
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
private boolean didBackingContentChange(Bubble newBubble) {
boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
return prevWasIntentBased != newIsIntentBased;
}
- /**
- * Lets activity view know it should be shown / populated with activity content.
- */
- void populateExpandedView() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "populateExpandedView: "
- + "bubble=" + getBubbleKey());
- }
-
- if (usingActivityView()) {
- mActivityView.setCallback(mStateCallback);
- } else {
- Log.e(TAG, "Cannot populate expanded view.");
- }
- }
-
- boolean performBackPressIfNeeded() {
- if (!usingActivityView()) {
- return false;
- }
- mActivityView.performBackPress();
- return true;
- }
-
void updateHeight() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
- }
-
if (mExpandedViewContainerLocation == null) {
return;
}
- if (usingActivityView()) {
+ if (mBubble != null || mIsOverflow) {
float desiredHeight = mOverflowHeight;
if (!mIsOverflow) {
desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
}
float height = Math.min(desiredHeight, getMaxExpandedHeight());
- height = Math.max(height, mMinHeight);
- ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
+ height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
mNeedsNewHeight = lp.height != height;
- if (!mKeyboardVisible) {
- // If the keyboard is visible... don't adjust the height because that will cause
- // a configuration change and the keyboard will be lost.
+ if (!mImeVisible) {
+ // If the ime is visible... don't adjust the height because that will cause
+ // a configuration change and the ime will be lost.
lp.height = (int) height;
- mActivityView.setLayoutParams(lp);
+ mTaskView.setLayoutParams(lp);
mNeedsNewHeight = false;
}
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -689,12 +547,15 @@ public class BubbleExpandedView extends LinearLayout {
private int getMaxExpandedHeight() {
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+ int expandedContainerY = mExpandedViewContainerLocation != null
+ ? mExpandedViewContainerLocation[1]
+ : 0;
int bottomInset = getRootWindowInsets() != null
? getRootWindowInsets().getStableInsetBottom()
: 0;
return mDisplaySize.y
- - mExpandedViewContainerLocation[1]
+ - expandedContainerY
- getPaddingTop()
- getPaddingBottom()
- mSettingsIconHeight
@@ -714,14 +575,12 @@ public class BubbleExpandedView extends LinearLayout {
Log.d(TAG, "updateView: bubble="
+ getBubbleKey());
}
-
mExpandedViewContainerLocation = containerLocationOnScreen;
-
- if (usingActivityView()
- && mActivityView.getVisibility() == VISIBLE
- && mActivityView.isAttachedToWindow()) {
- mActivityView.onLocationChanged();
+ if (mTaskView != null
+ && mTaskView.getVisibility() == VISIBLE
+ && mTaskView.isAttachedToWindow()) {
updateHeight();
+ mTaskView.onLocationChanged();
}
}
@@ -744,65 +603,19 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Removes and releases an ActivityView if one was previously created for this bubble.
+ * Cleans up anything related to the task and TaskView.
*/
public void cleanUpExpandedState() {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
- + ", bubble=" + getBubbleKey());
- }
- if (mActivityView == null) {
- return;
- }
- mActivityView.release();
- if (mTaskId != -1) {
- try {
- ActivityTaskManager.getService().removeTask(mTaskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to remove taskId " + mTaskId);
- }
- mTaskId = -1;
- }
- removeView(mActivityView);
-
- mActivityView = null;
- }
-
- /**
- * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
- * which {@link ActivityView} uses.
- */
- void notifyDisplayEmpty() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "notifyDisplayEmpty: bubble="
- + getBubbleKey()
- + " mActivityViewStatus=" + mActivityViewStatus);
+ Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
}
- if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
- mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+ if (mTaskView != null) {
+ mTaskView.release();
}
- }
-
- private boolean usingActivityView() {
- return (mPendingIntent != null || mBubble.hasMetadataShortcutId())
- && mActivityView != null;
- }
-
- /**
- * @return the display id of the virtual display.
- */
- public int getVirtualDisplayId() {
- if (usingActivityView()) {
- return mActivityView.getVirtualDisplayId();
- }
- return INVALID_DISPLAY;
- }
-
- private VirtualDisplay getVirtualDisplay() {
- if (usingActivityView()) {
- return mActivityView.getVirtualDisplay();
+ if (mTaskView != null) {
+ removeView(mTaskView);
+ mTaskView = null;
}
- return null;
}
/**
@@ -812,7 +625,6 @@ public class BubbleExpandedView extends LinearLayout {
@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print("BubbleExpandedView");
pw.print(" taskId: "); pw.println(mTaskId);
- pw.print(" activityViewStatus: "); pw.println(mActivityViewStatus);
pw.print(" stackView: "); pw.println(mStackView);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 1fa3aaae5e61..009114ffa0be 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -18,6 +18,8 @@ package com.android.systemui.bubbles;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.systemui.Interpolators.ALPHA_IN;
+import static com.android.systemui.Interpolators.ALPHA_OUT;
import android.animation.ArgbEvaluator;
import android.content.Context;
@@ -34,6 +36,7 @@ import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.text.TextUtils;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -55,6 +58,11 @@ public class BubbleFlyoutView extends FrameLayout {
/** Max width of the flyout, in terms of percent of the screen width. */
private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
+ /** Translation Y of fade animation. */
+ private static final float FLYOUT_FADE_Y = 40f;
+
+ private static final long FLYOUT_FADE_DURATION = 200L;
+
private final int mFlyoutPadding;
private final int mFlyoutSpaceFromBubble;
private final int mPointerSize;
@@ -103,6 +111,9 @@ public class BubbleFlyoutView extends FrameLayout {
/** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */
private final RectF mBgRect = new RectF();
+ /** The y position of the flyout, relative to the top of the screen. */
+ private float mFlyoutY = 0f;
+
/**
* Percent progress in the transition from flyout to 'new' dot. These two values are the inverse
* of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code
@@ -212,18 +223,41 @@ public class BubbleFlyoutView extends FrameLayout {
super.onDraw(canvas);
}
- /** Configures the flyout, collapsed into to dot form. */
- void setupFlyoutStartingAsDot(
- Bubble.FlyoutMessage flyoutMessage,
- PointF stackPos,
- float parentWidth,
- boolean arrowPointingLeft,
- int dotColor,
- @Nullable Runnable onLayoutComplete,
- @Nullable Runnable onHide,
- float[] dotCenter,
- boolean hideDot) {
+ void updateFontSize(float fontScale) {
+ final float fontSize = mContext.getResources()
+ .getDimensionPixelSize(com.android.internal.R.dimen.text_size_body_2_material);
+ final float newFontSize = fontSize * fontScale;
+ mMessageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
+ mSenderText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
+ }
+
+ /*
+ * Fade animation for consecutive flyouts.
+ */
+ void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) {
+ fade(false /* in */);
+ updateFlyoutMessage(flyoutMessage, parentWidth);
+ // Wait for TextViews to layout with updated height.
+ post(() -> {
+ mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+ fade(true /* in */);
+ });
+ }
+ private void fade(boolean in) {
+ setAlpha(in ? 0f : 1f);
+ setTranslationY(in ? mFlyoutY : mFlyoutY + FLYOUT_FADE_Y);
+ animate()
+ .alpha(in ? 1f : 0f)
+ .setDuration(FLYOUT_FADE_DURATION)
+ .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+ animate()
+ .translationY(in ? mFlyoutY : mFlyoutY - FLYOUT_FADE_Y)
+ .setDuration(FLYOUT_FADE_DURATION)
+ .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
+ }
+
+ private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) {
final Drawable senderAvatar = flyoutMessage.senderAvatar;
if (senderAvatar != null && flyoutMessage.isGroupChat) {
mSenderAvatar.setVisibility(VISIBLE);
@@ -247,6 +281,27 @@ public class BubbleFlyoutView extends FrameLayout {
mSenderText.setVisibility(GONE);
}
+ // Set the flyout TextView's max width in terms of percent, and then subtract out the
+ // padding so that the entire flyout view will be the desired width (rather than the
+ // TextView being the desired width + extra padding).
+ mMessageText.setMaxWidth(maxTextViewWidth);
+ mMessageText.setText(flyoutMessage.message);
+ }
+
+ /** Configures the flyout, collapsed into dot form. */
+ void setupFlyoutStartingAsDot(
+ Bubble.FlyoutMessage flyoutMessage,
+ PointF stackPos,
+ float parentWidth,
+ boolean arrowPointingLeft,
+ int dotColor,
+ @Nullable Runnable onLayoutComplete,
+ @Nullable Runnable onHide,
+ float[] dotCenter,
+ boolean hideDot) {
+
+ updateFlyoutMessage(flyoutMessage, parentWidth);
+
mArrowPointingLeft = arrowPointingLeft;
mDotColor = dotColor;
mOnHide = onHide;
@@ -254,24 +309,12 @@ public class BubbleFlyoutView extends FrameLayout {
setCollapsePercent(1f);
- // Set the flyout TextView's max width in terms of percent, and then subtract out the
- // padding so that the entire flyout view will be the desired width (rather than the
- // TextView being the desired width + extra padding).
- mMessageText.setMaxWidth(maxTextViewWidth);
- mMessageText.setText(flyoutMessage.message);
-
- // Wait for the TextView to lay out so we know its line count.
+ // Wait for TextViews to layout with updated height.
post(() -> {
- float restingTranslationY;
- // Multi line flyouts get top-aligned to the bubble.
- if (mMessageText.getLineCount() > 1) {
- restingTranslationY = stackPos.y + mBubbleIconTopPadding;
- } else {
- // Single line flyouts are vertically centered with respect to the bubble.
- restingTranslationY =
- stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
- }
- setTranslationY(restingTranslationY);
+ // Flyout is vertically centered with respect to the bubble.
+ mFlyoutY =
+ stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
+ setTranslationY(mFlyoutY);
// Calculate the translation required to position the flyout next to the bubble stack,
// with the desired padding.
@@ -291,7 +334,7 @@ public class BubbleFlyoutView extends FrameLayout {
final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway;
final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
- final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
+ final float distanceFromLayoutTopToDotCenterY = mFlyoutY - dotPositionY;
mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
index 86ba8c5c7192..48c809d1b0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
@@ -19,17 +19,22 @@ package com.android.systemui.bubbles;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
/**
- * Interface for handling bubble-specific logging.
+ * Implementation of UiEventLogger for logging bubble UI events.
+ *
+ * See UiEventReported atom in atoms.proto for more context.
*/
-public interface BubbleLogger extends UiEventLogger {
+public class BubbleLogger {
+
+ private final UiEventLogger mUiEventLogger;
/**
* Bubble UI event.
*/
@VisibleForTesting
- enum Event implements UiEventLogger.UiEventEnum {
+ public enum Event implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "User dismissed the bubble via gesture, add bubble to overflow.")
BUBBLE_OVERFLOW_ADD_USER_GESTURE(483),
@@ -70,23 +75,80 @@ public interface BubbleLogger extends UiEventLogger {
}
}
+ public BubbleLogger(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
+ }
+
/**
* @param b Bubble involved in this UI event
* @param e UI event
*/
- void log(Bubble b, UiEventEnum e);
+ public void log(Bubble b, UiEventLogger.UiEventEnum e) {
+ mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
+ }
/**
- *
* @param b Bubble removed from overflow
- * @param r Reason that bubble was removed from overflow
+ * @param r Reason that bubble was removed
*/
- void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r);
+ public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
+ if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
+ } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
+ } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
+ } else if (r == BubbleController.DISMISS_BLOCKED) {
+ log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
+ }
+ }
/**
- *
* @param b Bubble added to overflow
* @param r Reason that bubble was added to overflow
*/
- void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r);
-}
+ public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
+ if (r == BubbleController.DISMISS_AGED) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
+ } else if (r == BubbleController.DISMISS_USER_GESTURE) {
+ log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+ }
+ }
+
+ void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
+ float normalY) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+ packageName,
+ null /* notification channel */,
+ 0 /* notification ID */,
+ 0 /* bubble position */,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ false /* unread bubble */,
+ false /* on-going bubble */,
+ false /* isAppForeground (unused) */);
+ }
+
+ void logShowOverflow(String packageName, int currentUserId) {
+ mUiEventLogger.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
+ packageName);
+ }
+
+ void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
+ float normalX, float normalY, int index) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
+ packageName,
+ bubble.getChannelId() /* notification channel */,
+ bubble.getNotificationId() /* notification ID */,
+ index,
+ bubbleCount,
+ action,
+ normalX,
+ normalY,
+ bubble.showInShade() /* isUnread */,
+ false /* isOngoing (unused) */,
+ false /* isAppForeground (unused) */);
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
deleted file mode 100644
index ea612af3d4a4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import android.os.UserHandle;
-
-import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.systemui.shared.system.SysUiStatsLog;
-
-/**
- * Implementation of UiEventLogger for logging bubble UI events.
- *
- * See UiEventReported atom in atoms.proto for more context.
- */
-public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger {
-
- /**
- * @param b Bubble involved in this UI event
- * @param e UI event
- */
- public void log(Bubble b, UiEventEnum e) {
- logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
- }
-
- /**
- * @param b Bubble removed from overflow
- * @param r Reason that bubble was removed
- */
- public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
- } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
- } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
- } else if (r == BubbleController.DISMISS_BLOCKED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
- }
- }
-
- /**
- * @param b Bubble added to overflow
- * @param r Reason that bubble was added to overflow
- */
- public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_AGED) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
- } else if (r == BubbleController.DISMISS_USER_GESTURE) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
- }
- }
-
- void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
- float normalY) {
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- null /* notification channel */,
- 0 /* notification ID */,
- 0 /* bubble position */,
- bubbleCount,
- action,
- normalX,
- normalY,
- false /* unread bubble */,
- false /* on-going bubble */,
- false /* isAppForeground (unused) */);
- }
-
- void logShowOverflow(String packageName, int currentUserId) {
- super.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
- packageName);
- }
-
- void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
- float normalX, float normalY, int index) {
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- bubble.getChannelId() /* notification channel */,
- bubble.getNotificationId() /* notification ID */,
- index,
- bubbleCount,
- action,
- normalX,
- normalY,
- bubble.showInShade() /* isUnread */,
- false /* isOngoing (unused) */,
- false /* isAppForeground (unused) */);
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
index bf7c860132bf..102055de2bea 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles
+import android.app.ActivityTaskManager.INVALID_TASK_ID
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
@@ -37,8 +38,8 @@ class BubbleOverflow(
private val stack: BubbleStackView
) : BubbleViewProvider {
- private lateinit var bitmap : Bitmap
- private lateinit var dotPath : Path
+ private lateinit var bitmap: Bitmap
+ private lateinit var dotPath: Path
private var bitmapSize = 0
private var iconBitmapSize = 0
@@ -167,8 +168,8 @@ class BubbleOverflow(
return KEY
}
- override fun getDisplayId(): Int {
- return expandedView.virtualDisplayId
+ override fun getTaskId(): Int {
+ return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 160addc405fa..fc3f5b6cbf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -22,11 +22,13 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
+import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
@@ -47,20 +49,21 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
-import javax.inject.Inject;
/**
* Activity for showing aged out bubbles.
* Must be public to be accessible to androidx...AppComponentFactory
*/
public class BubbleOverflowActivity extends Activity {
+ static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
private LinearLayout mEmptyState;
private TextView mEmptyStateTitle;
private TextView mEmptyStateSubtitle;
private ImageView mEmptyStateImage;
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
private BubbleOverflowAdapter mAdapter;
private RecyclerView mRecyclerView;
private List<Bubble> mOverflowBubbles = new ArrayList<>();
@@ -71,7 +74,8 @@ public class BubbleOverflowActivity extends Activity {
}
@Override
public boolean canScrollVertically() {
- if (mBubbleController.inLandscape()) {
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
return super.canScrollVertically();
}
return false;
@@ -92,11 +96,6 @@ public class BubbleOverflowActivity extends Activity {
}
}
- @Inject
- public BubbleOverflowActivity(BubbleController controller) {
- mBubbleController = controller;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -108,6 +107,15 @@ public class BubbleOverflowActivity extends Activity {
mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
+ Intent intent = getIntent();
+ if (intent != null && intent.getExtras() != null) {
+ IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
+ if (binder instanceof ObjectWrapper) {
+ mBubbles = ((ObjectWrapper<Bubbles>) binder).get();
+ }
+ } else {
+ Log.w(TAG, "Bubble overflow activity created without bubble controller!");
+ }
updateOverflow();
}
@@ -131,15 +139,15 @@ public class BubbleOverflowActivity extends Activity {
final int viewHeight = recyclerViewHeight / rows;
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
- mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
+ mBubbles::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
mOverflowBubbles.clear();
- mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
+ mOverflowBubbles.addAll(mBubbles.getOverflowBubbles());
mAdapter.notifyDataSetChanged();
updateEmptyStateVisibility();
- mBubbleController.setOverflowListener(mDataListener);
+ mBubbles.setOverflowListener(mDataListener);
updateTheme();
}
@@ -209,8 +217,7 @@ public class BubbleOverflowActivity extends Activity {
if (DEBUG_OVERFLOW) {
Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mBubbleController.getOverflowBubbles(),
- null));
+ mBubbles.getOverflowBubbles(), null));
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f2fba23564da..431719f98ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,11 +16,17 @@
package com.android.systemui.bubbles;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.app.ActivityView;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -67,21 +73,16 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.SysUiStatsLog;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.RelativeTouchListener;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -92,12 +93,6 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
*/
@@ -120,6 +115,8 @@ public class BubbleStackView extends FrameLayout
/** Duration of the flyout alpha animations. */
private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
+ private static final int FADE_IN_DURATION = 320;
+
/** Percent to darken the bubbles when they're in the dismiss target. */
private static final float DARKEN_PERCENT = 0.3f;
@@ -301,7 +298,7 @@ public class BubbleStackView extends FrameLayout
pw.println(" expandedViewAlpha: " + expandedView.getAlpha());
pw.println(" expandedViewTaskId: " + expandedView.getTaskId());
- final ActivityView av = expandedView.getActivityView();
+ final View av = expandedView.getTaskView();
if (av != null) {
pw.println(" activityViewVis: " + av.getVisibility());
@@ -322,8 +319,6 @@ public class BubbleStackView extends FrameLayout
/** Callback to run when we want to unbubble the given notification's conversation. */
private Consumer<String> mUnbubbleConversationCallback;
- private SysUiState mSysUiState;
-
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
private boolean mIsBubbleSwitchAnimating = false;
@@ -331,8 +326,6 @@ public class BubbleStackView extends FrameLayout
/** The view to desaturate/darken when magneted to the dismiss target. */
@Nullable private View mDesaturateAndDarkenTargetView;
- private LayoutInflater mInflater;
-
private Rect mTempRect = new Rect();
private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -400,6 +393,11 @@ public class BubbleStackView extends FrameLayout
public final Consumer<Boolean> mOnImeVisibilityChanged;
/**
+ * Callback to run when the bubble expand status changes.
+ */
+ private final Consumer<Boolean> mOnBubbleExpandChanged;
+
+ /**
* Callback to run to ask BubbleController to hide the current IME.
*/
private final Runnable mHideCurrentInputMethodCallback;
@@ -659,7 +657,7 @@ public class BubbleStackView extends FrameLayout
viewInitialX + dx, velX, velY) <= 0;
updateBubbleIcons();
logBubbleEvent(null /* no bubble associated with bubble stack move */,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
}
mDismissView.hide();
}
@@ -741,16 +739,13 @@ public class BubbleStackView extends FrameLayout
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
Runnable allBubblesAnimatedOutAction,
Consumer<Boolean> onImeVisibilityChanged,
- Runnable hideCurrentInputMethodCallback) {
+ Runnable hideCurrentInputMethodCallback,
+ Consumer<Boolean> onBubbleExpandChanged) {
super(context);
mBubbleData = data;
- mInflater = LayoutInflater.from(context);
-
- mSysUiState = sysUiState;
Resources res = getResources();
mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -864,21 +859,13 @@ public class BubbleStackView extends FrameLayout
mOnImeVisibilityChanged = onImeVisibilityChanged;
mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
+ mOnBubbleExpandChanged = onBubbleExpandChanged;
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
-
if (!mIsExpanded || mIsExpansionAnimating) {
return view.onApplyWindowInsets(insets);
}
- mExpandedAnimationController.updateYPosition(
- // Update the insets after we're done translating otherwise position
- // calculation for them won't be correct.
- () -> {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().updateInsets(insets);
- }
- });
return view.onApplyWindowInsets(insets);
});
@@ -973,7 +960,7 @@ public class BubbleStackView extends FrameLayout
animate()
.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
- .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION);
+ .setDuration(FADE_IN_DURATION);
}
/**
@@ -1065,7 +1052,7 @@ public class BubbleStackView extends FrameLayout
mBubbleData.setExpanded(false);
mContext.startActivityAsUser(intent, bubble.getUser());
logBubbleEvent(bubble,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
}
});
@@ -1081,8 +1068,7 @@ public class BubbleStackView extends FrameLayout
* Whether the educational view should show for the expanded view "manage" menu.
*/
private boolean shouldShowManageEdu() {
- final boolean seen = Prefs.getBoolean(mContext,
- Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, false /* default */);
+ final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null;
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
@@ -1106,8 +1092,7 @@ public class BubbleStackView extends FrameLayout
* Whether education view should show for the collapsed stack.
*/
private boolean shouldShowStackEdu() {
- final boolean seen = Prefs.getBoolean(getContext(),
- Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION, false /* default */);
+ final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
Log.d(TAG, "Show stack edu: " + shouldShow);
@@ -1115,6 +1100,11 @@ public class BubbleStackView extends FrameLayout
return shouldShow;
}
+ private boolean getPrefBoolean(String key) {
+ return mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE)
+ .getBoolean(key, false /* default */);
+ }
+
/**
* @return true if education view for collapsed stack should show and was not showing before.
*/
@@ -1155,6 +1145,10 @@ public class BubbleStackView extends FrameLayout
addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
+ void updateFlyout(float fontScale) {
+ mFlyout.updateFontSize(fontScale);
+ }
+
private void updateOverflow() {
mBubbleOverflow.update();
mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
@@ -1249,7 +1243,15 @@ public class BubbleStackView extends FrameLayout
mTempRect.setEmpty();
getTouchableRegion(mTempRect);
- inoutInfo.touchableRegion.set(mTempRect);
+ if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null
+ && mExpandedBubble.getExpandedView().getTaskView() != null) {
+ inoutInfo.touchableRegion.set(mTempRect);
+ mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect);
+ inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE);
+ } else {
+ inoutInfo.touchableRegion.set(mTempRect);
+ }
}
@Override
@@ -1437,13 +1439,6 @@ public class BubbleStackView extends FrameLayout
}
/**
- * The {@link BadgedImageView} that is expanded, null if one does not exist.
- */
- View getExpandedBubbleView() {
- return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
- }
-
- /**
* The {@link Bubble} that is expanded, null if one does not exist.
*/
@Nullable
@@ -1483,7 +1478,7 @@ public class BubbleStackView extends FrameLayout
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
animateInFlyoutForBubble(bubble);
requestUpdate();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
}
// via BubbleData.Listener
@@ -1503,7 +1498,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
}
updatePointerPosition();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
return;
}
}
@@ -1521,7 +1516,7 @@ public class BubbleStackView extends FrameLayout
void updateBubble(Bubble bubble) {
animateInFlyoutForBubble(bubble);
requestUpdate();
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
}
public void updateBubbleOrder(List<Bubble> bubbles) {
@@ -1558,7 +1553,7 @@ public class BubbleStackView extends FrameLayout
return;
}
- if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
+ if (bubbleToSelect.getKey().equals(BubbleOverflow.KEY)) {
mBubbleData.setShowingOverflow(true);
} else {
mBubbleData.setShowingOverflow(false);
@@ -1617,8 +1612,9 @@ public class BubbleStackView extends FrameLayout
requestUpdate();
logBubbleEvent(previouslySelected,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(bubbleToSelect,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
notifyExpansionChanged(previouslySelected, false /* expanded */);
notifyExpansionChanged(bubbleToSelect, true /* expanded */);
});
@@ -1648,30 +1644,21 @@ public class BubbleStackView extends FrameLayout
hideCurrentInputMethod();
- mSysUiState
- .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
- .commitUpdate(mContext.getDisplayId());
+ mOnBubbleExpandChanged.accept(shouldExpand);
if (mIsExpanded) {
animateCollapse();
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
+ logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
// TODO: move next line to BubbleData
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- logBubbleEvent(mExpandedBubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+ logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
+ logBubbleEvent(mExpandedBubble,
+ FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
- void showExpandedViewContents(int displayId) {
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null
- && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) {
- mExpandedBubble.setContentVisibility(true);
- }
- }
-
/**
* Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
* not.
@@ -1706,7 +1693,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
updatePointerPosition();
mExpandedAnimationController.expandFromStack(() -> {
- afterExpandedViewAnimation();
if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
maybeShowManageEdu();
}
@@ -1769,11 +1755,10 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainerMatrix);
})
.withEndActions(() -> {
+ afterExpandedViewAnimation();
if (mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
mExpandedBubble.getExpandedView()
- .setContentVisibility(true);
- mExpandedBubble.getExpandedView()
.setSurfaceZOrderedOnTop(false);
}
})
@@ -1917,7 +1902,6 @@ public class BubbleStackView extends FrameLayout
})
.withEndActions(() -> {
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setContentVisibility(true);
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
}
@@ -1953,13 +1937,6 @@ public class BubbleStackView extends FrameLayout
}
}
- /** Return the BubbleView at the given index from the bubble container. */
- public BadgedImageView getBubbleAt(int i) {
- return getBubbleCount() > i
- ? (BadgedImageView) mBubbleContainer.getChildAt(i)
- : null;
- }
-
/** Moves the bubbles out of the way if they're going to be over the keyboard. */
public void onImeVisibilityChanged(boolean visible, int height) {
mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
@@ -1981,6 +1958,9 @@ public class BubbleStackView extends FrameLayout
FLYOUT_IME_ANIMATION_SPRING_CONFIG)
.start();
}
+ } else if (mIsExpanded && mExpandedBubble != null
+ && mExpandedBubble.getExpandedView() != null) {
+ mExpandedBubble.getExpandedView().setImeVisible(visible);
}
}
@@ -2191,11 +2171,7 @@ public class BubbleStackView extends FrameLayout
return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop;
}
- /**
- * Animates in the flyout for the given bubble, if available, and then hides it after some time.
- */
- @VisibleForTesting
- void animateInFlyoutForBubble(Bubble bubble) {
+ private boolean shouldShowFlyout(Bubble bubble) {
Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
final BadgedImageView bubbleView = bubble.getIconView();
if (flyoutMessage == null
@@ -2207,11 +2183,22 @@ public class BubbleStackView extends FrameLayout
|| mIsGestureInProgress
|| mBubbleToExpandAfterFlyoutCollapse != null
|| bubbleView == null) {
- if (bubbleView != null) {
+ if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
}
// Skip the message if none exists, we're expanded or animating expansion, or we're
// about to expand a bubble from the previous tapped flyout, or if bubble view is null.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Animates in the flyout for the given bubble, if available, and then hides it after some time.
+ */
+ @VisibleForTesting
+ void animateInFlyoutForBubble(Bubble bubble) {
+ if (!shouldShowFlyout(bubble)) {
return;
}
@@ -2229,25 +2216,22 @@ public class BubbleStackView extends FrameLayout
}
// Stop suppressing the dot now that the flyout has morphed into the dot.
- bubbleView.removeDotSuppressionFlag(
+ bubble.getIconView().removeDotSuppressionFlag(
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
- mFlyout.setVisibility(INVISIBLE);
-
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
};
- mFlyout.setVisibility(INVISIBLE);
// Suppress the dot when we are animating the flyout.
- bubbleView.addDotSuppressionFlag(
+ bubble.getIconView().addDotSuppressionFlag(
BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
// Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
post(() -> {
// An auto-expanding bubble could have been posted during the time it takes to
// layout.
- if (isExpanded()) {
+ if (isExpanded() || bubble.getIconView() == null) {
return;
}
final Runnable expandFlyoutAfterDelay = () -> {
@@ -2264,23 +2248,26 @@ public class BubbleStackView extends FrameLayout
mFlyout.postDelayed(mAnimateInFlyout, 200);
};
- if (bubble.getIconView() == null) {
- return;
- }
- mFlyout.setupFlyoutStartingAsDot(flyoutMessage,
- mStackAnimationController.getStackPosition(), getWidth(),
- mStackAnimationController.isStackOnLeftSide(),
- bubble.getIconView().getDotColor() /* dotColor */,
- expandFlyoutAfterDelay /* onLayoutComplete */,
- mAfterFlyoutHidden,
- bubble.getIconView().getDotCenter(),
- !bubble.showDot());
+ if (mFlyout.getVisibility() == View.VISIBLE) {
+ mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
+ mStackAnimationController.getStackPosition().y);
+ } else {
+ mFlyout.setVisibility(INVISIBLE);
+ mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
+ mStackAnimationController.getStackPosition(), getWidth(),
+ mStackAnimationController.isStackOnLeftSide(),
+ bubble.getIconView().getDotColor() /* dotColor */,
+ expandFlyoutAfterDelay /* onLayoutComplete */,
+ mAfterFlyoutHidden,
+ bubble.getIconView().getDotCenter(),
+ !bubble.showDot());
+ }
mFlyout.bringToFront();
});
mFlyout.removeCallbacks(mHideFlyout);
mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+ logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
}
/** Hide the flyout immediately and cancel any pending hide runnables. */
@@ -2388,7 +2375,6 @@ public class BubbleStackView extends FrameLayout
final float targetY = mTempRect.bottom - mManageMenu.getHeight();
final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
-
if (show) {
mManageMenu.setScaleX(0.5f);
mManageMenu.setScaleY(0.5f);
@@ -2405,6 +2391,8 @@ public class BubbleStackView extends FrameLayout
.withEndActions(() -> {
View child = mManageMenu.getChildAt(0);
child.requestAccessibilityFocus();
+ // Update the AV's obscured touchable region for the new visibility state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
})
.start();
@@ -2416,12 +2404,15 @@ public class BubbleStackView extends FrameLayout
.spring(DynamicAnimation.SCALE_Y, 0.5f)
.spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation)
.spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f)
- .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+ .withEndActions(() -> {
+ mManageMenu.setVisibility(View.INVISIBLE);
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // Update the AV's obscured touchable region for the new state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
+ })
.start();
}
-
- // Update the AV's obscured touchable region for the new menu visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
}
private void updateExpandedBubble() {
@@ -2441,7 +2432,6 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAlpha(0f);
mExpandedViewContainer.addView(bev);
bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
- bev.populateExpandedView();
if (!mIsExpansionAnimating) {
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
@@ -2498,7 +2488,7 @@ public class BubbleStackView extends FrameLayout
mAnimatingOutSurfaceContainer.setTranslationY(0);
final int[] activityViewLocation =
- mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen();
+ mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen();
final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
// Translate the surface to overlap the real ActivityView.
@@ -2674,17 +2664,6 @@ public class BubbleStackView extends FrameLayout
getNormalizedYPosition());
}
- /**
- * Called when a back gesture should be directed to the Bubbles stack. When expanded,
- * a back key down/up event pair is forwarded to the bubble Activity.
- */
- boolean performBackPressIfNeeded() {
- if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
- return false;
- }
- return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
- }
-
/** For debugging only */
List<Bubble> getBubblesOnScreen() {
List<Bubble> bubbles = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
deleted file mode 100644
index 06205c5c1c41..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.window.TaskEmbedder;
-import android.window.TaskOrganizerTaskEmbedder;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.IWindow;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-import dalvik.system.CloseGuard;
-
-
-public class BubbleTaskView extends SurfaceView implements SurfaceHolder.Callback,
- TaskEmbedder.Host {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleTaskView" : TAG_BUBBLES;
-
- private final CloseGuard mGuard = CloseGuard.get();
- private boolean mOpened; // Protected by mGuard.
-
- private TaskEmbedder mTaskEmbedder;
- private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
- private final Rect mTmpRect = new Rect();
-
- public BubbleTaskView(Context context) {
- super(context);
-
- mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
- setUseAlpha();
- getHolder().addCallback(this);
-
- mOpened = true;
- mGuard.open("release");
- }
-
- public void setCallback(TaskEmbedder.Listener callback) {
- if (callback == null) {
- mTaskEmbedder.setListener(null);
- return;
- }
- mTaskEmbedder.setListener(callback);
- }
-
- public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
- @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
- mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
- }
-
- public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @NonNull ActivityOptions options) {
- mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
- }
-
- public void onLocationChanged() {
- mTaskEmbedder.notifyBoundsChanged();
- }
-
- @Override
- public Rect getScreenBounds() {
- getBoundsOnScreen(mTmpRect);
- return mTmpRect;
- }
-
- @Override
- public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
- setResizeBackgroundColor(bgColor);
- }
-
- @Override
- public Region getTapExcludeRegion() {
- // Not used
- return null;
- }
-
- @Override
- public Matrix getScreenToTaskMatrix() {
- // Not used
- return null;
- }
-
- @Override
- public IWindow getWindow() {
- // Not used
- return null;
- }
-
- @Override
- public Point getPositionInWindow() {
- // Not used
- return null;
- }
-
- @Override
- public boolean canReceivePointerEvents() {
- // Not used
- return false;
- }
-
- public void release() {
- if (!mTaskEmbedder.isInitialized()) {
- throw new IllegalStateException(
- "Trying to release container that is not initialized.");
- }
- performRelease();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mGuard != null) {
- mGuard.warnIfOpen();
- performRelease();
- }
- } finally {
- super.finalize();
- }
- }
-
- private void performRelease() {
- if (!mOpened) {
- return;
- }
- getHolder().removeCallback(this);
- mTaskEmbedder.release();
- mTaskEmbedder.setListener(null);
-
- mGuard.close();
- mOpened = false;
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- if (!mTaskEmbedder.isInitialized()) {
- mTaskEmbedder.initialize(getSurfaceControl());
- } else {
- mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
- getSurfaceControl()).apply();
- }
- mTaskEmbedder.start();
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- mTaskEmbedder.resizeTask(width, height);
- mTaskEmbedder.notifyBoundsChanged();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- mTaskEmbedder.stop();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 28757facc220..010a29e3560a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -22,8 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -36,8 +34,6 @@ import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
-import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
@@ -47,10 +43,8 @@ import androidx.annotation.Nullable;
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.icons.BitmapInfo;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.ref.WeakReference;
-import java.util.List;
import java.util.Objects;
/**
@@ -208,73 +202,6 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
}
-
- /**
- * Returns our best guess for the most relevant text summary of the latest update to this
- * notification, based on its type. Returns null if there should not be an update message.
- */
- @NonNull
- static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) {
- Objects.requireNonNull(entry);
- final Notification underlyingNotif = entry.getSbn().getNotification();
- final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
- Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
- bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
- Notification.EXTRA_IS_GROUP_CONVERSATION);
- try {
- if (Notification.BigTextStyle.class.equals(style)) {
- // Return the big text, it is big so probably important. If it's not there use the
- // normal text.
- CharSequence bigText =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
- bubbleMessage.message = !TextUtils.isEmpty(bigText)
- ? bigText
- : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- } else if (Notification.MessagingStyle.class.equals(style)) {
- final List<Notification.MessagingStyle.Message> messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- (Parcelable[]) underlyingNotif.extras.get(
- Notification.EXTRA_MESSAGES));
-
- final Notification.MessagingStyle.Message latestMessage =
- Notification.MessagingStyle.findLatestIncomingMessage(messages);
- if (latestMessage != null) {
- bubbleMessage.message = latestMessage.getText();
- Person sender = latestMessage.getSenderPerson();
- bubbleMessage.senderName = sender != null ? sender.getName() : null;
- bubbleMessage.senderAvatar = null;
- bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
- return bubbleMessage;
- }
- } else if (Notification.InboxStyle.class.equals(style)) {
- CharSequence[] lines =
- underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
- // Return the last line since it should be the most recent.
- if (lines != null && lines.length > 0) {
- bubbleMessage.message = lines[lines.length - 1];
- return bubbleMessage;
- }
- } else if (Notification.MediaStyle.class.equals(style)) {
- // Return nothing, media updates aren't typically useful as a text update.
- return bubbleMessage;
- } else {
- // Default to text extra.
- bubbleMessage.message =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- }
- } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
- // No use crashing, we'll just return null and the caller will assume there's no update
- // message.
- e.printStackTrace();
- }
-
- return bubbleMessage;
- }
-
@Nullable
static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
Objects.requireNonNull(context);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index 916ad18b2812..5cc24ce5a775 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -48,5 +48,5 @@ interface BubbleViewProvider {
boolean showDot();
- int getDisplayId();
+ int getTaskId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
new file mode 100644
index 000000000000..39c750de28ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.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.systemui.bubbles;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.MainThread;
+
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.ScrimController;
+
+import java.util.List;
+
+/**
+ * Interface to engage bubbles feature.
+ */
+public interface Bubbles {
+
+ /**
+ * @return {@code true} if there is a bubble associated with the provided key and if its
+ * notification is hidden from the shade or there is a group summary associated with the
+ * provided key that is hidden from the shade because it has been dismissed but still has child
+ * bubbles active.
+ */
+ boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry);
+
+ /**
+ * @return {@code true} if the current notification entry same as selected bubble
+ * notification entry and the stack is currently expanded.
+ */
+ boolean isBubbleExpanded(NotificationEntry entry);
+
+ /** @return {@code true} if stack of bubbles is expanded or not. */
+ boolean isStackExpanded();
+
+ /**
+ * @return the {@link ScrimView} drawn behind the bubble stack. This is managed by
+ * {@link ScrimController} since we want the scrim's appearance and behavior to be identical to
+ * that of the notification shade scrim.
+ */
+ ScrimView getScrimForBubble();
+
+ /** @return Bubbles for updating overflow. */
+ List<Bubble> getOverflowBubbles();
+
+ /** Tell the stack of bubbles to collapse. */
+ void collapseStack();
+
+ /**
+ * Request the stack expand if needed, then select the specified Bubble as current.
+ * If no bubble exists for this entry, one is created.
+ *
+ * @param entry the notification for the bubble to be selected
+ */
+ void expandStackAndSelectBubble(NotificationEntry entry);
+
+ /** Promote the provided bubbles when overflow view. */
+ void promoteBubbleFromOverflow(Bubble bubble);
+
+ /**
+ * We intercept notification entries (including group summaries) dismissed by the user when
+ * there is an active bubble associated with it. We do this so that developers can still
+ * cancel it (and hence the bubbles associated with it). However, these intercepted
+ * notifications should then be hidden from the shade since the user has cancelled them, so we
+ * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
+ * {@link BubbleData#addSummaryToSuppress}.
+ *
+ * @return true if we want to intercept the dismissal of the entry, else false.
+ */
+ boolean handleDismissalInterception(NotificationEntry entry);
+
+ /**
+ * Removes the bubble with the given key.
+ * <p>
+ * Must be called from the main thread.
+ */
+ @MainThread
+ void removeBubble(String key, int reason);
+
+
+ /**
+ * When a notification is marked Priority, expand the stack if needed,
+ * then (maybe create and) select the given bubble.
+ *
+ * @param entry the notification for the bubble to show
+ */
+ void onUserChangedImportance(NotificationEntry entry);
+
+ /**
+ * Called when the status bar has become visible or invisible (either permanently or
+ * temporarily).
+ */
+ void onStatusBarVisibilityChanged(boolean visible);
+
+ /**
+ * Called when a user has indicated that an active notification should be shown as a bubble.
+ * <p>
+ * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
+ * the notification from appearing in the shade.
+ *
+ * @param entry the notification to change bubble state for.
+ * @param shouldBubble whether the notification should show as a bubble or not.
+ */
+ void onUserChangedBubble(@NonNull NotificationEntry entry, boolean shouldBubble);
+
+
+ /** See {@link BubbleController.NotifCallback}. */
+ void addNotifCallback(BubbleController.NotifCallback callback);
+
+ /** Set a listener to be notified of bubble expand events. */
+ void setExpandListener(BubbleController.BubbleExpandListener listener);
+
+ /** Set a listener to be notified of when overflow view update. */
+ void setOverflowListener(BubbleData.Listener listener);
+
+ /** The task listener for events in bubble tasks. **/
+ MultiWindowTaskListener getTaskManager();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
index 71faf4a2eeb7..b3c552d24dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
@@ -10,8 +10,8 @@ import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.systemui.R
-import com.android.systemui.util.DismissCircleView
-import com.android.systemui.util.animation.PhysicsAnimator
+import com.android.wm.shell.common.DismissCircleView
+import com.android.wm.shell.animation.PhysicsAnimator
/*
* View that handles interactions between DismissCircleView and BubbleStackView.
@@ -29,7 +29,7 @@ class DismissView(context: Context) : FrameLayout(context) {
var isShowing = false
private val animator = PhysicsAnimator.getInstance(circle)
- private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
+ private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
private val DISMISS_SCRIM_FADE_MS = 200
init {
setLayoutParams(LayoutParams(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
index 26a9773f9bb8..3db07c227d02 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
@@ -25,8 +25,6 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION
import com.android.systemui.R
/**
@@ -38,8 +36,8 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
- private val ANIMATE_DURATION : Long = 200
- private val ANIMATE_DURATION_SHORT : Long = 40
+ private val ANIMATE_DURATION: Long = 200
+ private val ANIMATE_DURATION_SHORT: Long = 40
private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
private val manageButton by lazy { findViewById<Button>(R.id.manage) }
@@ -50,7 +48,7 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
private var isHiding = false
init {
- LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this);
+ LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
@@ -95,7 +93,7 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
*
* @param show whether the user education view should show or not.
*/
- fun show(expandedView: BubbleExpandedView, rect : Rect) {
+ fun show(expandedView: BubbleExpandedView, rect: Rect) {
if (visibility == VISIBLE) return
alpha = 0f
@@ -136,10 +134,13 @@ class ManageEducationView constructor(context: Context) : LinearLayout(context)
.withEndAction {
isHiding = false
visibility = GONE
- };
+ }
}
private fun setShouldShow(shouldShow: Boolean) {
- Prefs.putBoolean(context, HAS_SEEN_BUBBLES_MANAGE_EDUCATION, !shouldShow)
+ context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
}
-} \ No newline at end of file
+}
+
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
new file mode 100644
index 000000000000..dff8becccb86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
@@ -0,0 +1,177 @@
+/*
+ * 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.bubbles;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/**
+ * Manages tasks that are displayed in multi-window (e.g. bubbles). These are displayed in a
+ * {@link TaskView}.
+ *
+ * This class listens on {@link TaskOrganizer} callbacks for events. Once visible, these tasks will
+ * intercept back press events.
+ *
+ * @see android.app.WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW
+ * @see TaskView
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class MultiWindowTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = MultiWindowTaskListener.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ //TODO(b/170153209): Have shell listener allow per task registration and remove this.
+ public interface Listener {
+ void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash);
+ void onTaskVanished(RunningTaskInfo taskInfo);
+ void onTaskInfoChanged(RunningTaskInfo taskInfo);
+ void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo);
+ }
+
+ private static class TaskData {
+ final RunningTaskInfo taskInfo;
+ final Listener listener;
+
+ TaskData(RunningTaskInfo info, Listener l) {
+ taskInfo = info;
+ listener = l;
+ }
+ }
+
+ private final Handler mHandler;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final ArrayMap<WindowContainerToken, TaskData> mTasks = new ArrayMap<>();
+
+ private MultiWindowTaskListener.Listener mPendingListener;
+
+ /**
+ * Create a listener for tasks in multi-window mode.
+ */
+ public MultiWindowTaskListener(Handler handler, ShellTaskOrganizer organizer) {
+ mHandler = handler;
+ mTaskOrganizer = organizer;
+ mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_MULTI_WINDOW);
+ }
+
+ /**
+ * @return the task organizer that is listened to.
+ */
+ public TaskOrganizer getTaskOrganizer() {
+ return mTaskOrganizer;
+ }
+
+ // TODO(b/129067201): track launches for bubbles
+ // Once we have key in ActivityOptions, match listeners via that key
+ public void setPendingListener(Listener listener) {
+ mPendingListener = listener;
+ }
+
+ /**
+ * Removes a task listener previously registered when starting a new activity.
+ */
+ public void removeListener(Listener listener) {
+ if (DEBUG) {
+ Log.d(TAG, "removeListener: listener=" + listener);
+ }
+ if (mPendingListener == listener) {
+ mPendingListener = null;
+ }
+ for (int i = 0; i < mTasks.size(); i++) {
+ if (mTasks.valueAt(i).listener == listener) {
+ mTasks.removeAt(i);
+ }
+ }
+ }
+
+ @Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (DEBUG) {
+ Log.d(TAG, "onTaskAppeared: taskInfo=" + taskInfo
+ + " mPendingListener=" + mPendingListener);
+ }
+ if (mPendingListener == null) {
+ // If there is no pending listener, then we are either receiving this task as a part of
+ // registering the task org again (ie. after SysUI dies) or the previously started
+ // task is no longer needed (ie. bubble is closed soon after), for now, just finish the
+ // associated task
+ try {
+ ActivityTaskManager.getService().removeTask(taskInfo.taskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to remove taskId " + taskInfo.taskId);
+ }
+ return;
+ }
+
+ mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true);
+
+ final TaskData data = new TaskData(taskInfo, mPendingListener);
+ mTasks.put(taskInfo.token, data);
+ mHandler.post(() -> data.listener.onTaskAppeared(taskInfo, leash));
+ mPendingListener = null;
+ }
+
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.remove(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskVanished: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onTaskVanished(taskInfo));
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.get(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onTaskInfoChanged(taskInfo));
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+ final TaskData data = mTasks.get(taskInfo.token);
+ if (data == null) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
+ }
+ mHandler.post(() -> data.listener.onBackPressedOnTaskRoot(taskInfo));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java b/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
new file mode 100644
index 000000000000..f054122eaa47
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.bubbles;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+// Copied from Launcher3
+/**
+ * Utility class to pass non-parcealable objects within same process using parcealable payload.
+ *
+ * It wraps the object in a binder as binders are singleton within a process
+ */
+public class ObjectWrapper<T> extends Binder {
+
+ private T mObject;
+
+ public ObjectWrapper(T object) {
+ mObject = object;
+ }
+
+ public T get() {
+ return mObject;
+ }
+
+ public void clear() {
+ mObject = null;
+ }
+
+ public static IBinder wrap(Object obj) {
+ return new ObjectWrapper<>(obj);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
index 8880df9959c1..b1291a507b57 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.util
+package com.android.systemui.bubbles
import android.graphics.PointF
import android.os.Handler
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
index 3e4c729d8315..216df2e1f402 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
@@ -24,21 +24,19 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.android.internal.util.ContrastColorUtil
import com.android.systemui.Interpolators
-import com.android.systemui.Prefs
-import com.android.systemui.Prefs.Key.HAS_SEEN_BUBBLES_EDUCATION
import com.android.systemui.R
/**
* User education view to highlight the collapsed stack of bubbles.
* Shown only the first time a user taps the stack.
*/
-class StackEducationView constructor(context: Context) : LinearLayout(context){
+class StackEducationView constructor(context: Context) : LinearLayout(context) {
private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
- private val ANIMATE_DURATION : Long = 200
- private val ANIMATE_DURATION_SHORT : Long = 40
+ private val ANIMATE_DURATION: Long = 200
+ private val ANIMATE_DURATION_SHORT: Long = 40
private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
@@ -47,7 +45,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
private var isHiding = false
init {
- LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this);
+ LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
visibility = View.GONE
elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
@@ -93,7 +91,7 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
*
* @return true if user education was shown, false otherwise.
*/
- fun show(stackPosition: PointF) : Boolean{
+ fun show(stackPosition: PointF): Boolean {
if (visibility == VISIBLE) return false
setAlpha(0f)
@@ -129,6 +127,9 @@ class StackEducationView constructor(context: Context) : LinearLayout(context){
}
private fun setShouldShow(shouldShow: Boolean) {
- Prefs.putBoolean(context, HAS_SEEN_BUBBLES_EDUCATION, !shouldShow)
+ context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
}
-} \ No newline at end of file
+}
+
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
new file mode 100644
index 000000000000..524fa42af7d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
@@ -0,0 +1,308 @@
+/*
+ * 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.bubbles;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * View that can display a task.
+ */
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
+ MultiWindowTaskListener.Listener {
+
+ public interface Listener {
+ /** Called when the container is ready for launching activities. */
+ default void onInitialized() {}
+
+ /** Called when the container can no longer launch activities. */
+ default void onReleased() {}
+
+ /** Called when a task is created inside the container. */
+ default void onTaskCreated(int taskId, ComponentName name) {}
+
+ /** Called when a task visibility changes. */
+ default void onTaskVisibilityChanged(int taskId, boolean visible) {}
+
+ /** Called when a task is about to be removed from the stack inside the container. */
+ default void onTaskRemovalStarted(int taskId) {}
+
+ /** Called when a task is created inside the container. */
+ default void onBackPressedOnTaskRoot(int taskId) {}
+ }
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ private final MultiWindowTaskListener mMultiWindowTaskListener;
+
+ private ActivityManager.RunningTaskInfo mTaskInfo;
+ private WindowContainerToken mTaskToken;
+ private SurfaceControl mTaskLeash;
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private boolean mSurfaceCreated;
+ private boolean mIsInitialized;
+ private Listener mListener;
+
+ private final Rect mTmpRect = new Rect();
+ private final Rect mTmpRootRect = new Rect();
+
+ public TaskView(Context context, MultiWindowTaskListener taskListener) {
+ super(context, null, 0, 0, true /* disableBackgroundLayer */);
+
+ mMultiWindowTaskListener = taskListener;
+ setUseAlpha();
+ getHolder().addCallback(this);
+ mGuard.open("release");
+ }
+
+ /**
+ * Only one listener may be set on the view, throws an exception otherwise.
+ */
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new IllegalStateException(
+ "Trying to set a listener when one has already been set");
+ }
+ mListener = listener;
+ }
+
+ /**
+ * Launch an activity represented by {@link ShortcutInfo}.
+ * <p>The owner of this container must be allowed to access the shortcut information,
+ * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
+ *
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param sourceBounds the rect containing the source bounds of the clicked icon to open
+ * this shortcut.
+ */
+ public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+ @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
+ mMultiWindowTaskListener.setPendingListener(this);
+ prepareActivityOptions(options);
+ LauncherApps service = mContext.getSystemService(LauncherApps.class);
+ try {
+ service.startShortcut(shortcut, sourceBounds, options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Launch a new activity.
+ *
+ * @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
+ * @param options options for the activity.
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options) {
+ mMultiWindowTaskListener.setPendingListener(this);
+ prepareActivityOptions(options);
+ try {
+ pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void prepareActivityOptions(ActivityOptions options) {
+ options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ options.setTaskAlwaysOnTop(true);
+ }
+
+ /**
+ * Call when view position or size has changed. Do not call when animating.
+ */
+ public void onLocationChanged() {
+ if (mTaskToken == null) {
+ return;
+ }
+ // Update based on the screen bounds
+ getBoundsOnScreen(mTmpRect);
+ getRootView().getBoundsOnScreen(mTmpRootRect);
+ if (!mTmpRootRect.contains(mTmpRect)) {
+ mTmpRect.offsetTo(0, 0);
+ }
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mTaskToken, mTmpRect);
+ // TODO(b/151449487): Enable synchronization
+ mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+ }
+
+ /**
+ * Release this container if it is initialized.
+ */
+ public void release() {
+ performRelease();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ performRelease();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void performRelease() {
+ getHolder().removeCallback(this);
+ mMultiWindowTaskListener.removeListener(this);
+ resetTaskInfo();
+ mGuard.close();
+ if (mListener != null && mIsInitialized) {
+ mListener.onReleased();
+ mIsInitialized = false;
+ }
+ }
+
+ private void resetTaskInfo() {
+ mTaskInfo = null;
+ mTaskToken = null;
+ mTaskLeash = null;
+ }
+
+ private void updateTaskVisibility() {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
+ // TODO(b/151449487): Only call callback once we enable synchronization
+ if (mListener != null) {
+ mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+ }
+ }
+
+ @Override
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash) {
+ mTaskInfo = taskInfo;
+ mTaskToken = taskInfo.token;
+ mTaskLeash = leash;
+
+ if (mSurfaceCreated) {
+ // Surface is ready, so just reparent the task to this surface control
+ mTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .show(mTaskLeash)
+ .apply();
+ } else {
+ // The surface has already been destroyed before the task has appeared, so go ahead and
+ // hide the task entirely
+ updateTaskVisibility();
+ }
+
+ // TODO: Synchronize show with the resize
+ onLocationChanged();
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+
+ if (mListener != null) {
+ mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+ }
+ }
+
+ @Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+ if (mListener != null) {
+ mListener.onTaskRemovalStarted(taskInfo.taskId);
+ }
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ resetTaskInfo();
+ }
+ }
+
+ @Override
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mTaskInfo.taskDescription = taskInfo.taskDescription;
+ setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
+ if (mListener != null) {
+ mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+ }
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurfaceCreated = true;
+ if (mListener != null && !mIsInitialized) {
+ mIsInitialized = true;
+ mListener.onInitialized();
+ }
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+ // Reparent the task when this surface is created
+ mTransaction.reparent(mTaskLeash, getSurfaceControl())
+ .show(mTaskLeash)
+ .apply();
+ updateTaskVisibility();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (mTaskToken == null) {
+ return;
+ }
+ onLocationChanged();
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurfaceCreated = false;
+ if (mTaskToken == null) {
+ // Nothing to update, task is not yet available
+ return;
+ }
+
+ // Unparent the task when this surface is destroyed
+ mTransaction.reparent(mTaskLeash, null).apply();
+ updateTaskVisibility();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index f2a4f159f959..7fdc01961aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -32,8 +32,8 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -58,6 +58,9 @@ public class ExpandedAnimationController
/** Duration of the expand/collapse target path animation. */
public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
+ /** Damping ratio for expand/collapse spring. */
+ private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
+
/** Stiffness for the expand/collapse path-following animation. */
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
@@ -271,16 +274,14 @@ public class ExpandedAnimationController
// Then, draw a line across the screen to the bubble's resting position.
path.lineTo(getBubbleLeft(index), expandedY);
} else {
- final float sideMultiplier =
- mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
- final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+ final float stackedX = mCollapsePoint.x;
// If we're collapsing, draw a line from the bubble's current position to the side
// of the screen where the bubble will be stacked.
path.lineTo(stackedX, expandedY);
// Then, draw a line down to the stack position.
- path.lineTo(stackedX, mCollapsePoint.y);
+ path.lineTo(stackedX, mCollapsePoint.y + index * mStackOffsetPx);
}
// The lead bubble should be the bubble with the longest distance to travel when we're
@@ -510,7 +511,7 @@ public class ExpandedAnimationController
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
return new SpringForce()
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index e835ea206e59..12051241f049 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -36,9 +36,9 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleStackView;
-import com.android.systemui.util.FloatingContentCoordinator;
-import com.android.systemui.util.animation.PhysicsAnimator;
-import com.android.systemui.util.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.google.android.collect.Sets;
@@ -72,9 +72,9 @@ public class StackAnimationController extends
/**
* Values to use for the default {@link SpringForce} provided to the physics animation layout.
*/
- public static final int DEFAULT_STIFFNESS = 12000;
+ public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
- private static final int FLING_FOLLOW_STIFFNESS = 20000;
+ private static final int CHAIN_STIFFNESS = 600;
public static final float DEFAULT_BOUNCINESS = 0.9f;
private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
@@ -629,7 +629,7 @@ public class StackAnimationController extends
public void moveStackFromTouch(float x, float y) {
// Begin the spring-to-touch catch up animation if needed.
if (mSpringToTouchOnNextMotionEvent) {
- springStack(x, y, DEFAULT_STIFFNESS);
+ springStack(x, y, SPRING_TO_TOUCH_STIFFNESS);
mSpringToTouchOnNextMotionEvent = false;
mFirstBubbleSpringingToTouch = true;
} else if (mFirstBubbleSpringingToTouch) {
@@ -744,15 +744,13 @@ public class StackAnimationController extends
@Override
float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
- if (property.equals(DynamicAnimation.TRANSLATION_X)) {
+ if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
// If we're in the dismiss target, have the bubbles pile on top of each other with no
// offset.
if (isStackStuckToTarget()) {
return 0f;
} else {
- // Offset to the left if we're on the left, or the right otherwise.
- return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
- ? -mStackOffset : mStackOffset;
+ return mStackOffset;
}
} else {
return 0f;
@@ -762,14 +760,12 @@ public class StackAnimationController extends
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
- mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS /* default */);
final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
DEFAULT_BOUNCINESS);
return new SpringForce()
.setDampingRatio(dampingRatio)
- .setStiffness(stiffness);
+ .setStiffness(CHAIN_STIFFNESS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index 9efc3c20f55a..6b5f237ac76f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -19,13 +19,15 @@ package com.android.systemui.bubbles.dagger;
import android.app.INotificationManager;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.os.Handler;
import android.view.WindowManager;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubbleData;
-import com.android.systemui.bubbles.BubbleDataRepository;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,7 +41,9 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import dagger.Module;
import dagger.Provides;
@@ -52,12 +56,11 @@ public interface BubbleModule {
*/
@SysUISingleton
@Provides
- static BubbleController newBubbleController(
+ static Bubbles newBubbleController(
Context context,
NotificationShadeWindowController notificationShadeWindowController,
StatusBarStateController statusBarStateController,
ShadeController shadeController,
- BubbleData data,
ConfigurationController configurationController,
NotificationInterruptStateProvider interruptionStateProvider,
ZenModeController zenModeController,
@@ -68,18 +71,20 @@ public interface BubbleModule {
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
- BubbleDataRepository bubbleDataRepository,
SysUiState sysUiState,
INotificationManager notifManager,
IStatusBarService statusBarService,
WindowManager windowManager,
- LauncherApps launcherApps) {
- return new BubbleController(
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ @Main Handler mainHandler,
+ ShellTaskOrganizer organizer) {
+ return BubbleController.create(
context,
notificationShadeWindowController,
statusBarStateController,
shadeController,
- data,
null /* synchronizer */,
configurationController,
interruptionStateProvider,
@@ -91,11 +96,14 @@ public interface BubbleModule {
featureFlags,
dumpManager,
floatingContentCoordinator,
- bubbleDataRepository,
sysUiState,
notifManager,
statusBarService,
windowManager,
- launcherApps);
+ windowManagerShellWrapper,
+ launcherApps,
+ uiEventLogger,
+ mainHandler,
+ organizer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
index f4479653d12e..ce0786d86460 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
@@ -18,16 +18,11 @@ package com.android.systemui.bubbles.storage
import android.content.Context
import android.util.AtomicFile
import android.util.Log
-import com.android.systemui.dagger.SysUISingleton
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
-import javax.inject.Inject
-@SysUISingleton
-class BubblePersistentRepository @Inject constructor(
- context: Context
-) {
+class BubblePersistentRepository(context: Context) {
private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
"overflow_bubbles.xml"), "overflow-bubbles")
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
index c6d57326357c..e0a7c7879f43 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
@@ -19,8 +19,6 @@ import android.content.pm.LauncherApps
import android.os.UserHandle
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.bubbles.ShortcutKey
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
private const val CAPACITY = 16
@@ -28,10 +26,7 @@ private const val CAPACITY = 16
* BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
* manipulation.
*/
-@SysUISingleton
-class BubbleVolatileRepository @Inject constructor(
- private val launcherApps: LauncherApps
-) {
+class BubbleVolatileRepository(private val launcherApps: LauncherApps) {
/**
* An ordered set of bubbles based on their natural ordering.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 658f46e3bb96..2f0fd99337e5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.os.IBinder
@@ -30,6 +29,7 @@ import android.util.Log
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.util.concurrent.atomic.AtomicBoolean
@@ -40,7 +40,8 @@ import javax.inject.Inject
open class ControlsBindingControllerImpl @Inject constructor(
private val context: Context,
@Background private val backgroundExecutor: DelayableExecutor,
- private val lazyController: Lazy<ControlsController>
+ private val lazyController: Lazy<ControlsController>,
+ userTracker: UserTracker
) : ControlsBindingController {
companion object {
@@ -56,7 +57,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
}
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId: Int
get() = currentUser.identifier
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 495872f3433d..d3d24be0ad9d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.controller
-import android.app.ActivityManager
import android.app.PendingIntent
import android.app.backup.BackupManager
import android.content.BroadcastReceiver
@@ -46,6 +45,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.globalactions.GlobalActionsDialog
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
import java.io.PrintWriter
@@ -56,14 +56,15 @@ import javax.inject.Inject
@SysUISingleton
class ControlsControllerImpl @Inject constructor (
- private val context: Context,
- @Background private val executor: DelayableExecutor,
- private val uiController: ControlsUiController,
- private val bindingController: ControlsBindingController,
- private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
- optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
- dumpManager: DumpManager
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val listingController: ControlsListingController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpManager: DumpManager,
+ userTracker: UserTracker
) : Dumpable, ControlsController {
companion object {
@@ -85,7 +86,7 @@ class ControlsControllerImpl @Inject constructor (
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
- private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
+ private var currentUser = userTracker.userHandle
override val currentUserId
get() = currentUser.identifier
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 0d4439fe8ccb..2d76ff2774d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.management
-import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.pm.ServiceInfo
@@ -29,6 +28,7 @@ import com.android.settingslib.widget.CandidateInfo
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject
@@ -56,14 +56,16 @@ private fun createServiceListing(context: Context): ServiceListing {
class ControlsListingControllerImpl @VisibleForTesting constructor(
private val context: Context,
@Background private val backgroundExecutor: Executor,
- private val serviceListingBuilder: (Context) -> ServiceListing
+ private val serviceListingBuilder: (Context) -> ServiceListing,
+ userTracker: UserTracker
) : ControlsListingController {
@Inject
- constructor(context: Context, executor: Executor): this(
+ constructor(context: Context, executor: Executor, userTracker: UserTracker): this(
context,
executor,
- ::createServiceListing
+ ::createServiceListing,
+ userTracker
)
private var serviceListing = serviceListingBuilder(context)
@@ -78,7 +80,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private var availableServices = emptyList<ServiceInfo>()
private var userChangeInProgress = AtomicInteger(0)
- override var currentUserId = ActivityManager.getCurrentUser()
+ override var currentUserId = userTracker.userId
private set
private val serviceListingCallback = ServiceListing.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 28bcf3a35117..d13e194a0d44 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,13 +19,13 @@ package com.android.systemui.dagger;
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
-import com.android.systemui.bubbles.BubbleOverflowActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
+import com.android.systemui.user.CreateUserActivity;
import dagger.Binds;
import dagger.Module;
@@ -67,12 +67,6 @@ public abstract class DefaultActivityBinder {
@ClassKey(ScreenRecordDialog.class)
public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
- /** Inject into BubbleOverflowActivity. */
- @Binds
- @IntoMap
- @ClassKey(BubbleOverflowActivity.class)
- public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
-
/** Inject into UsbDebuggingActivity. */
@Binds
@IntoMap
@@ -85,4 +79,10 @@ public abstract class DefaultActivityBinder {
@ClassKey(UsbDebuggingSecondaryUserActivity.class)
public abstract Activity bindUsbDebuggingSecondaryUserActivity(
UsbDebuggingSecondaryUserActivity activity);
+
+ /** Inject into CreateUserActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(CreateUserActivity.class)
+ public abstract Activity bindCreateUserActivity(CreateUserActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index eb431274b8a3..cb90b6114396 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -66,6 +66,8 @@ import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -262,6 +264,13 @@ public class DependencyProvider {
return ActivityManagerWrapper.getInstance();
}
+ /** */
+ @Provides
+ @SysUISingleton
+ public TaskStackChangeListeners provideTaskStackChangeListeners() {
+ return TaskStackChangeListeners.getInstance();
+ }
+
/** Provides and initializes the {#link BroadcastDispatcher} for SystemUI */
@Provides
@SysUISingleton
@@ -313,6 +322,13 @@ public class DependencyProvider {
/** */
@Provides
+ public WindowManagerWrapper providesWindowManagerWrapper() {
+ return WindowManagerWrapper.getInstance();
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
public SystemActions providesSystemActions(Context context) {
return new SystemActions(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 8f4e738e5a5f..63d9a831b33f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,8 +41,10 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.tuner.dagger.TunerModule;
+import com.android.systemui.user.UserModule;
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
import com.android.systemui.util.sensors.SensorModule;
@@ -73,19 +75,23 @@ import dagger.Provides;
SensorModule.class,
SettingsModule.class,
SettingsUtilModule.class,
+ SmartRepliesInflationModule.class,
StatusBarPolicyModule.class,
SysUIConcurrencyModule.class,
TunerModule.class,
+ UserModule.class,
UtilModule.class,
VolumeModule.class
},
- subcomponents = {StatusBarComponent.class,
- NotificationRowComponent.class,
- DozeComponent.class,
- ExpandableNotificationRowComponent.class,
- KeyguardBouncerComponent.class,
- NotificationShelfComponent.class,
- FragmentService.FragmentCreator.class})
+ subcomponents = {
+ StatusBarComponent.class,
+ NotificationRowComponent.class,
+ DozeComponent.class,
+ ExpandableNotificationRowComponent.class,
+ KeyguardBouncerComponent.class,
+ NotificationShelfComponent.class,
+ FragmentService.FragmentCreator.class
+ })
public abstract class SystemUIModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 424a8246b278..f470a6b55b76 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -123,6 +123,14 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends dozing event to the logs
+ * @param suppressed true if dozing is suppressed
+ */
+ public void traceDozingSuppressed(boolean suppressed) {
+ mLogger.logDozingSuppressed(suppressed);
+ }
+
+ /**
* Appends fling event to the logs
*/
public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
@@ -198,6 +206,22 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends doze state changed sent to all DozeMachine parts event to the logs
+ * @param state new DozeMachine state
+ */
+ public void traceDozeStateSendComplete(DozeMachine.State state) {
+ mLogger.logStateChangedSent(state);
+ }
+
+ /**
+ * Appends display state changed event to the logs
+ * @param displayState new DozeMachine state
+ */
+ public void traceDisplayState(int displayState) {
+ mLogger.logDisplayStateChanged(displayState);
+ }
+
+ /**
* Appends wake-display event to the logs.
* @param wake if we're waking up or sleeping.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 732745a1158b..0c9e14352946 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -64,6 +64,14 @@ class DozeLogger @Inject constructor(
})
}
+ fun logDozingSuppressed(isDozingSuppressed: Boolean) {
+ buffer.log(TAG, INFO, {
+ bool1 = isDozingSuppressed
+ }, {
+ "DozingSuppressed=$bool1"
+ })
+ }
+
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
@@ -143,6 +151,22 @@ class DozeLogger @Inject constructor(
})
}
+ fun logStateChangedSent(state: DozeMachine.State) {
+ buffer.log(TAG, INFO, {
+ str1 = state.name
+ }, {
+ "Doze state sent to all DozeMachineParts stateSent=$str1"
+ })
+ }
+
+ fun logDisplayStateChanged(displayState: Int) {
+ buffer.log(TAG, INFO, {
+ int1 = displayState
+ }, {
+ "Display state changed to $int1"
+ })
+ }
+
fun logWakeDisplay(isAwake: Boolean) {
buffer.log(TAG, DEBUG, {
bool1 = isAwake
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index d5820f3e05e4..befb648152d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -205,6 +205,7 @@ public class DozeMachine {
}
void onScreenState(int state) {
+ mDozeLog.traceDisplayState(state);
for (Part part : mParts) {
part.onScreenState(state);
}
@@ -308,6 +309,7 @@ public class DozeMachine {
for (Part p : mParts) {
p.transitionTo(oldState, newState);
}
+ mDozeLog.traceDozeStateSendComplete(newState);
switch (newState) {
case FINISH:
@@ -411,6 +413,7 @@ public class DozeMachine {
pw.print(" state="); pw.println(mState);
pw.print(" wakeLockHeldForCurrentState="); pw.println(mWakeLockHeldForCurrentState);
pw.print(" wakeLock="); pw.println(mWakeLock);
+ pw.print(" isDozeSuppressed="); pw.println(mDozeHost.isDozeSuppressed());
pw.println("Parts:");
for (Part p : mParts) {
p.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 5aeb8df2028d..92494cf5b546 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -35,6 +35,7 @@ import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import java.io.PrintWriter;
import java.util.Optional;
import javax.inject.Inject;
@@ -221,4 +222,9 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
mDebugBrightnessBucket = intent.getIntExtra(BRIGHTNESS_BUCKET, -1);
updateBrightnessAndReady(false /* force */);
}
+
+ /** Dump current state */
+ public void dump(PrintWriter pw) {
+ pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
index 7a8b8166a969..435859afc03f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeScope.java
@@ -24,7 +24,7 @@ import java.lang.annotation.Retention;
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the StatusBarComponent.
+ * Scope annotation for singleton items within the DozeComponent.
*/
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java
new file mode 100644
index 000000000000..dccb24dfe21e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java
@@ -0,0 +1,40 @@
+/*
+ * 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.emergency;
+
+/**
+ * Constants for the Emergency gesture.
+ *
+ * TODO (b/169175022) Update classname and docs when feature name is locked
+ */
+public final class EmergencyGesture {
+
+ /**
+ * Launches the emergency flow.
+ *
+ * <p>The emergency flow is triggered by the Emergency gesture. By default the flow will call
+ * local emergency services, though OEMs can customize the flow.
+ *
+ * <p>This action can only be triggered by System UI through the emergency gesture.
+ *
+ * <p>TODO (b/169175022) Update action name and docs when feature name is locked
+ */
+ public static final String ACTION_LAUNCH_EMERGENCY =
+ "com.android.systemui.action.LAUNCH_EMERGENCY";
+
+ private EmergencyGesture() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index daef2c506ad3..5f726cd1e1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -38,6 +38,7 @@ import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.text.TextUtils;
import android.text.style.StyleSpan;
+import android.util.Log;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
@@ -52,6 +53,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
@@ -62,6 +65,8 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -80,6 +85,8 @@ public class KeyguardSliceProvider extends SliceProvider implements
NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
SystemUIAppComponentFactory.ContextInitializer {
+ private static final String TAG = "KgdSliceProvider";
+
private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
private static final String KEYGUARD_HEADER_URI =
@@ -310,7 +317,25 @@ public class KeyguardSliceProvider extends SliceProvider implements
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
mPendingIntent = PendingIntent.getActivity(getContext(), 0,
new Intent(getContext(), KeyguardSliceProvider.class), 0);
- mMediaManager.addCallback(this);
+ try {
+ //TODO(b/168778439): Remove this whole try catch. This is for debugging in dogfood.
+ mMediaManager.addCallback(this);
+ } catch (NullPointerException e) {
+ // We are sometimes failing to set the media manager. Why?
+ Log.w(TAG, "Failed to setup mMediaManager. Trying again.");
+ SysUIComponent rootComponent = SystemUIFactory.getInstance().getSysUIComponent();
+ try {
+ Method injectMethod = rootComponent.getClass()
+ .getMethod("inject", getClass());
+ injectMethod.invoke(rootComponent, this);
+ Log.w("TAG", "mMediaManager is now: " + mMediaManager);
+ } catch (NoSuchMethodException ex) {
+ Log.e(TAG, "Failed to find inject method for KeyguardSliceProvider", ex);
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ Log.e(TAG, "Failed to call inject", ex);
+ }
+ throw e;
+ }
mStatusBarStateController.addCallback(this);
mNextAlarmController.addCallback(this);
mZenModeController.addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
index 1b20cfbc4e55..3da6caf31968 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/Lifecycle.java
@@ -16,7 +16,10 @@
package com.android.systemui.keyguard;
+import androidx.annotation.NonNull;
+
import java.util.ArrayList;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -26,8 +29,8 @@ public class Lifecycle<T> {
private ArrayList<T> mObservers = new ArrayList<>();
- public void addObserver(T observer) {
- mObservers.add(observer);
+ public void addObserver(@NonNull T observer) {
+ mObservers.add(Objects.requireNonNull(observer));
}
public void removeObserver(T observer) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index c281ece59c5a..75851102bc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -30,8 +30,8 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
public class WorkLockActivityController {
private static final String TAG = WorkLockActivityController.class.getSimpleName();
@@ -40,16 +40,16 @@ public class WorkLockActivityController {
private final IActivityTaskManager mIatm;
public WorkLockActivityController(Context context) {
- this(context, ActivityManagerWrapper.getInstance(), ActivityTaskManager.getService());
+ this(context, TaskStackChangeListeners.getInstance(), ActivityTaskManager.getService());
}
@VisibleForTesting
WorkLockActivityController(
- Context context, ActivityManagerWrapper am, IActivityTaskManager iAtm) {
+ Context context, TaskStackChangeListeners tscl, IActivityTaskManager iAtm) {
mContext = context;
mIatm = iAtm;
- am.registerTaskStackListener(mLockListener);
+ tscl.registerTaskStackListener(mLockListener);
}
private void startWorkChallengeInTask(int taskId, int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 9d8e73a0ff47..e50fd6a96b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -134,7 +134,7 @@ public class KeyguardModule {
// Cameras that support "self illumination," via IR for example, don't need low light
// environment mitigation.
- boolean needsLowLightMitigation = faceManager.getSensorProperties().stream()
+ boolean needsLowLightMitigation = faceManager.getSensorPropertiesInternal().stream()
.anyMatch((properties) -> !properties.supportsSelfIllumination);
if (!needsLowLightMitigation) {
return Optional.empty();
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 6db408659c96..e3ee2a10821b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -108,6 +108,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to Toasts shown by SystemUI. */
+ @Provides
+ @SysUISingleton
+ @ToastLog
+ public static LogBuffer provideToastLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer("ToastLog", 50, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
new file mode 100644
index 000000000000..8671dbfdf1fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ToastLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for ToastLog-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ToastLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
new file mode 100644
index 000000000000..aca033e99623
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaBrowser} constructor
+ */
+public class MediaBrowserFactory {
+ private final Context mContext;
+
+ @Inject
+ public MediaBrowserFactory(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates a new MediaBrowser
+ *
+ * @param serviceComponent
+ * @param callback
+ * @param rootHints
+ * @return
+ */
+ public MediaBrowser create(ComponentName serviceComponent,
+ MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
+ return new MediaBrowser(mContext, serviceComponent, callback, rootHints);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index f150381f4070..1beb875af70c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -174,7 +174,7 @@ class MediaCarouselController @Inject constructor(
mediaManager.addListener(object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
addOrUpdatePlayer(key, oldKey, data)
- val canRemove = data.isPlaying?.let { !it } ?: data.isClearable
+ val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active
if (canRemove && !Utils.useMediaResumption(context)) {
// This view isn't playing, let's remove this! This happens e.g when
// dismissing/timing out a view. We still have the data around because
@@ -250,13 +250,13 @@ class MediaCarouselController @Inject constructor(
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
newPlayer.view?.player?.setLayoutParams(lp)
- newPlayer.bind(data)
+ newPlayer.bind(data, key)
newPlayer.setListening(currentlyExpanded)
MediaPlayerData.addMediaPlayer(key, data, newPlayer)
updatePlayerToState(newPlayer, noAnimation = true)
reorderAllPlayers()
} else {
- existingPlayer.bind(data)
+ existingPlayer.bind(data, key)
MediaPlayerData.addMediaPlayer(key, data, existingPlayer)
if (visualStabilityManager.isReorderingAllowed) {
reorderAllPlayers()
@@ -274,7 +274,7 @@ class MediaCarouselController @Inject constructor(
}
}
- private fun removePlayer(key: String) {
+ private fun removePlayer(key: String, dismissMediaData: Boolean = true) {
val removed = MediaPlayerData.removeMediaPlayer(key)
removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
@@ -283,13 +283,16 @@ class MediaCarouselController @Inject constructor(
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
- // Inform the media manager of a potentially late dismissal
- mediaManager.dismissMediaData(key, 0L)
+ if (dismissMediaData) {
+ // Inform the media manager of a potentially late dismissal
+ mediaManager.dismissMediaData(key, 0L)
+ }
}
}
private fun recreatePlayers() {
MediaPlayerData.mediaData().forEach { (key, data) ->
+ removePlayer(key, dismissMediaData = false)
addOrUpdatePlayer(key = key, oldKey = null, data = data)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 486399979db7..d80aafb714d3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -32,7 +32,7 @@ import com.android.systemui.qs.PageIndicator
import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.util.animation.PhysicsAnimator
+import com.android.wm.shell.animation.PhysicsAnimator
import com.android.systemui.util.concurrency.DelayableExecutor
private const val FLING_SLOP = 1000000
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index e55678dc986b..5b096ea363b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -42,10 +42,10 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.settingslib.Utils;
-import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.util.animation.TransitionLayout;
@@ -82,6 +82,7 @@ public class MediaControlPanel {
private Context mContext;
private PlayerViewHolder mViewHolder;
+ private String mKey;
private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
@@ -92,7 +93,7 @@ public class MediaControlPanel {
private int mAlbumArtRadius;
// This will provide the corners for the album art.
private final ViewOutlineProvider mViewOutlineProvider;
-
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/**
* Initialize a new control panel
* @param context
@@ -103,7 +104,8 @@ public class MediaControlPanel {
public MediaControlPanel(Context context, @Background Executor backgroundExecutor,
ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
- KeyguardDismissUtil keyguardDismissUtil) {
+ KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
+ mediaOutputDialogFactory) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -111,6 +113,7 @@ public class MediaControlPanel {
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
mKeyguardDismissUtil = keyguardDismissUtil;
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
loadDimens();
mViewOutlineProvider = new ViewOutlineProvider() {
@@ -206,10 +209,11 @@ public class MediaControlPanel {
/**
* Bind this view based on the data given
*/
- public void bind(@NonNull MediaData data) {
+ public void bind(@NonNull MediaData data, String key) {
if (mViewHolder == null) {
return;
}
+ mKey = key;
MediaSession.Token token = data.getToken();
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -249,10 +253,9 @@ public class MediaControlPanel {
// App icon
ImageView appIcon = mViewHolder.getAppIcon();
if (data.getAppIcon() != null) {
- appIcon.setImageDrawable(data.getAppIcon());
+ appIcon.setImageIcon(data.getAppIcon());
} else {
- Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_music_note);
- appIcon.setImageDrawable(iconDrawable);
+ appIcon.setImageResource(R.drawable.ic_music_note);
}
// Song name
@@ -272,13 +275,7 @@ public class MediaControlPanel {
setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
mViewHolder.getSeamless().setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- data.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mMediaOutputDialogFactory.create(data.getPackageName(), true);
});
ImageView iconView = mViewHolder.getSeamlessIcon();
@@ -330,7 +327,7 @@ public class MediaControlPanel {
int actionId = ACTION_IDS[i];
final ImageButton button = mViewHolder.getAction(actionId);
MediaAction mediaAction = actionIcons.get(i);
- button.setImageDrawable(mediaAction.getDrawable());
+ button.setImageIcon(mediaAction.getIcon());
button.setContentDescription(mediaAction.getContentDescription());
Runnable action = mediaAction.getAction();
@@ -359,10 +356,10 @@ public class MediaControlPanel {
// Dismiss
mViewHolder.getDismiss().setOnClickListener(v -> {
- if (data.getNotificationKey() != null) {
+ if (mKey != null) {
closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
- mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(),
+ mMediaDataManagerLazy.get().dismissMediaData(mKey,
MediaViewController.GUTS_ANIMATION_DURATION + 100);
return true;
}, /* requiresShadeOpen */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 40a879abde34..0ed96eeac402 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -33,7 +33,7 @@ data class MediaData(
/**
* Icon shown on player, close to app name.
*/
- val appIcon: Drawable?,
+ val appIcon: Icon?,
/**
* Artist name.
*/
@@ -109,7 +109,7 @@ data class MediaData(
/** State of a media action. */
data class MediaAction(
- val drawable: Drawable?,
+ val icon: Icon?,
val action: Runnable?,
val contentDescription: CharSequence?
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index cb6b22c2321f..6f6ee4c8091d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -455,7 +455,7 @@ class MediaDataManager(
val app = builder.loadHeaderAppName()
// App Icon
- val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
+ val smallIcon = sbn.notification.smallIcon
// Song name
var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
@@ -501,8 +501,13 @@ class MediaDataManager(
} else {
null
}
+ val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) {
+ Icon.createWithResource(packageContext, action.getIcon()!!.getResId())
+ } else {
+ action.getIcon()
+ }
val mediaAction = MediaAction(
- action.getIcon().loadDrawable(packageContext),
+ mediaActionIcon,
runnable,
action.title)
actionIcons.add(mediaAction)
@@ -518,7 +523,7 @@ class MediaDataManager(
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
- smallIconDrawable, artist, song, artWorkIcon, actionIcons,
+ smallIcon, artist, song, artWorkIcon, actionIcons,
actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
active, resumeAction = resumeAction, isLocalSession = isLocalSession,
notificationKey = key, hasCheckedForResume = hasCheckedForResume,
@@ -572,7 +577,7 @@ class MediaDataManager(
val source = ImageDecoder.createSource(context.getContentResolver(), uri)
return try {
ImageDecoder.decodeBitmap(source) {
- decoder, info, source -> decoder.isMutableRequired = true
+ decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
} catch (e: IOException) {
Log.e(TAG, "Unable to load bitmap", e)
@@ -612,7 +617,7 @@ class MediaDataManager(
private fun getResumeMediaAction(action: Runnable): MediaAction {
return MediaAction(
- context.getDrawable(R.drawable.lb_ic_play),
+ Icon.createWithResource(context, R.drawable.lb_ic_play),
action,
context.getString(R.string.controls_media_resume)
)
@@ -631,13 +636,13 @@ class MediaDataManager(
Assert.isMainThread()
val removed = mediaEntries.remove(key)
if (useMediaResumption && removed?.resumeAction != null &&
- !isBlockedFromResume(removed?.packageName)) {
+ !isBlockedFromResume(removed.packageName)) {
Log.d(TAG, "Not removing $key because resumable")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
val updated = removed.copy(token = null, actions = listOf(resumeAction),
actionsToShowInCompact = listOf(0), active = false, resumption = true)
- val pkg = removed?.packageName
+ val pkg = removed.packageName
val migrate = mediaEntries.put(pkg, updated) == null
// Notify listeners of "new" controls when migrating or removed and update when not
if (migrate) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 2bc908be055c..a993d00df01e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.content.Context
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import androidx.annotation.AnyThread
@@ -25,7 +24,6 @@ import androidx.annotation.WorkerThread
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -34,11 +32,13 @@ import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
+private const val PLAYBACK_TYPE_UNKNOWN = 0
+
/**
* Provides information about the route (ie. device) where playback is occurring.
*/
class MediaDeviceManager @Inject constructor(
- private val context: Context,
+ private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
@Main private val fgExecutor: Executor,
@@ -72,7 +72,7 @@ class MediaDeviceManager @Inject constructor(
if (entry == null || entry?.token != data.token) {
entry?.stop()
val controller = data.token?.let {
- MediaController(context, it)
+ controllerFactory.create(it)
}
entry = Entry(key, oldKey, controller,
localMediaManagerFactory.create(data.packageName))
@@ -123,11 +123,12 @@ class MediaDeviceManager @Inject constructor(
val oldKey: String?,
val controller: MediaController?,
val localMediaManager: LocalMediaManager
- ) : LocalMediaManager.DeviceCallback {
+ ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
val token
get() = controller?.sessionToken
private var started = false
+ private var playbackType = PLAYBACK_TYPE_UNKNOWN
private var current: MediaDevice? = null
set(value) {
if (!started || value != field) {
@@ -142,6 +143,8 @@ class MediaDeviceManager @Inject constructor(
fun start() = bgExecutor.execute {
localMediaManager.registerCallback(this)
localMediaManager.startScan()
+ playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ controller?.registerCallback(this)
updateCurrent()
started = true
}
@@ -149,22 +152,37 @@ class MediaDeviceManager @Inject constructor(
@AnyThread
fun stop() = bgExecutor.execute {
started = false
+ controller?.unregisterCallback(this)
localMediaManager.stopScan()
localMediaManager.unregisterCallback(this)
}
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
- val route = controller?.let {
+ val routingSession = controller?.let {
mr2manager.getRoutingSessionForMediaController(it)
}
+ val selectedRoutes = routingSession?.let {
+ mr2manager.getSelectedRoutes(it)
+ }
with(pw) {
println(" current device is ${current?.name}")
val type = controller?.playbackInfo?.playbackType
- println(" PlaybackType=$type (1 for local, 2 for remote)")
- println(" route=$route")
+ println(" PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
+ println(" routingSession=$routingSession")
+ println(" selectedRoutes=$selectedRoutes")
}
}
+ @WorkerThread
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+ val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+ if (newPlaybackType == playbackType) {
+ return
+ }
+ playbackType = newPlaybackType
+ updateCurrent()
+ }
+
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
updateCurrent()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 5b59214afdc9..5c1c60c5b07e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -28,6 +28,7 @@ import android.os.UserHandle
import android.provider.Settings
import android.service.media.MediaBrowserService
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -47,7 +48,8 @@ class MediaResumeListener @Inject constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
@Background private val backgroundExecutor: Executor,
- private val tunerService: TunerService
+ private val tunerService: TunerService,
+ private val mediaBrowserFactory: ResumeMediaBrowserFactory
) : MediaDataManager.Listener {
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
@@ -59,7 +61,8 @@ class MediaResumeListener @Inject constructor(
private var mediaBrowser: ResumeMediaBrowser? = null
private var currentUserId: Int = context.userId
- private val userChangeReceiver = object : BroadcastReceiver() {
+ @VisibleForTesting
+ val userChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
loadMediaResumptionControls()
@@ -152,7 +155,7 @@ class MediaResumeListener @Inject constructor(
resumeComponents.forEach {
if (!blockedApps.contains(it.packageName)) {
- val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
+ val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
browser.findRecentMedia()
}
}
@@ -193,14 +196,10 @@ class MediaResumeListener @Inject constructor(
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
mediaBrowser?.disconnect()
- mediaBrowser = ResumeMediaBrowser(context,
+ mediaBrowser = mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
- Log.d(TAG, "yes we can resume with $componentName")
- mediaDataManager.setResumeAction(key, getResumeAction(componentName))
- updateResumptionList(componentName)
- mediaBrowser?.disconnect()
- mediaBrowser = null
+ Log.d(TAG, "Connected to $componentName")
}
override fun onError() {
@@ -209,6 +208,19 @@ class MediaResumeListener @Inject constructor(
mediaBrowser?.disconnect()
mediaBrowser = null
}
+
+ override fun addTrack(
+ desc: MediaDescription,
+ component: ComponentName,
+ browser: ResumeMediaBrowser
+ ) {
+ // Since this is a test, just save the component for later
+ Log.d(TAG, "Can get resumable media from $componentName")
+ mediaDataManager.setResumeAction(key, getResumeAction(componentName))
+ updateResumptionList(componentName)
+ mediaBrowser?.disconnect()
+ mediaBrowser = null
+ }
},
componentName)
mediaBrowser?.testConnection()
@@ -245,7 +257,7 @@ class MediaResumeListener @Inject constructor(
private fun getResumeAction(componentName: ComponentName): Runnable {
return Runnable {
mediaBrowser?.disconnect()
- mediaBrowser = ResumeMediaBrowser(context,
+ mediaBrowser = mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
override fun onConnected() {
if (mediaBrowser?.token == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
index b8872250bb6c..00273bc34552 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt
@@ -8,7 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import com.android.systemui.Gefingerpoken
-import com.android.systemui.util.animation.physicsAnimator
+import com.android.wm.shell.animation.physicsAnimator
/**
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 6bd5274fa331..51dbfa733541 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -126,6 +126,7 @@ class MediaTimeoutListener @Inject constructor(
fun destroy() {
mediaController?.unregisterCallback(this)
+ cancellation?.run()
}
override fun onPlaybackStateChanged(state: PlaybackState?) {
@@ -182,4 +183,4 @@ class MediaTimeoutListener @Inject constructor(
cancellation = null
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 11551aca80f2..666a6038a8b6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -23,7 +23,6 @@ import android.widget.ImageButton
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
-import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
import com.android.systemui.util.animation.TransitionLayout
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 68b6785849aa..a4d44367be73 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -30,6 +30,8 @@ import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.List;
/**
@@ -46,6 +48,7 @@ public class ResumeMediaBrowser {
private static final String TAG = "ResumeMediaBrowser";
private final Context mContext;
private final Callback mCallback;
+ private MediaBrowserFactory mBrowserFactory;
private MediaBrowser mMediaBrowser;
private ComponentName mComponentName;
@@ -55,10 +58,12 @@ public class ResumeMediaBrowser {
* @param callback used to report media items found
* @param componentName Component name of the MediaBrowserService this browser will connect to
*/
- public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+ public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName,
+ MediaBrowserFactory browserFactory) {
mContext = context;
mCallback = callback;
mComponentName = componentName;
+ mBrowserFactory = browserFactory;
}
/**
@@ -74,7 +79,7 @@ public class ResumeMediaBrowser {
disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext,
+ mMediaBrowser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
@@ -88,17 +93,19 @@ public class ResumeMediaBrowser {
List<MediaBrowser.MediaItem> children) {
if (children.size() == 0) {
Log.d(TAG, "No children found for " + mComponentName);
- return;
- }
- // We ask apps to return a playable item as the first child when sending
- // a request with EXTRA_RECENT; if they don't, no resume controls
- MediaBrowser.MediaItem child = children.get(0);
- MediaDescription desc = child.getDescription();
- if (child.isPlayable() && mMediaBrowser != null) {
- mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
- ResumeMediaBrowser.this);
+ mCallback.onError();
} else {
- Log.d(TAG, "Child found but not playable for " + mComponentName);
+ // We ask apps to return a playable item as the first child when sending
+ // a request with EXTRA_RECENT; if they don't, no resume controls
+ MediaBrowser.MediaItem child = children.get(0);
+ MediaDescription desc = child.getDescription();
+ if (child.isPlayable() && mMediaBrowser != null) {
+ mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
+ ResumeMediaBrowser.this);
+ } else {
+ Log.d(TAG, "Child found but not playable for " + mComponentName);
+ mCallback.onError();
+ }
}
disconnect();
}
@@ -131,7 +138,7 @@ public class ResumeMediaBrowser {
Log.d(TAG, "Service connected for " + mComponentName);
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
String root = mMediaBrowser.getRoot();
- if (!TextUtils.isEmpty(root)) {
+ if (!TextUtils.isEmpty(root) && mMediaBrowser != null) {
mCallback.onConnected();
mMediaBrowser.subscribe(root, mSubscriptionCallback);
return;
@@ -182,7 +189,7 @@ public class ResumeMediaBrowser {
disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+ mMediaBrowser = mBrowserFactory.create(mComponentName,
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
@@ -192,7 +199,7 @@ public class ResumeMediaBrowser {
return;
}
MediaSession.Token token = mMediaBrowser.getSessionToken();
- MediaController controller = new MediaController(mContext, token);
+ MediaController controller = createMediaController(token);
controller.getTransportControls();
controller.getTransportControls().prepare();
controller.getTransportControls().play();
@@ -212,6 +219,11 @@ public class ResumeMediaBrowser {
mMediaBrowser.connect();
}
+ @VisibleForTesting
+ protected MediaController createMediaController(MediaSession.Token token) {
+ return new MediaController(mContext, token);
+ }
+
/**
* Get the media session token
* @return the token, or null if the MediaBrowser is null or disconnected
@@ -235,42 +247,19 @@ public class ResumeMediaBrowser {
/**
* Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
- * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
- * depending on whether it was successful.
+ * If it can connect, ResumeMediaBrowser.Callback#onConnected will be called. If valid media is
+ * found, then ResumeMediaBrowser.Callback#addTrack will also be called. This allows for more
+ * detailed logging if the service has issues. If it cannot connect, or cannot find valid media,
+ * then ResumeMediaBrowser.Callback#onError will be called.
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
*/
public void testConnection() {
disconnect();
- final MediaBrowser.ConnectionCallback connectionCallback =
- new MediaBrowser.ConnectionCallback() {
- @Override
- public void onConnected() {
- Log.d(TAG, "connected");
- if (mMediaBrowser == null || !mMediaBrowser.isConnected()
- || TextUtils.isEmpty(mMediaBrowser.getRoot())) {
- mCallback.onError();
- } else {
- mCallback.onConnected();
- }
- }
-
- @Override
- public void onConnectionSuspended() {
- Log.d(TAG, "suspended");
- mCallback.onError();
- }
-
- @Override
- public void onConnectionFailed() {
- Log.d(TAG, "failed");
- mCallback.onError();
- }
- };
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = new MediaBrowser(mContext,
+ mMediaBrowser = mBrowserFactory.create(
mComponentName,
- connectionCallback,
+ mConnectionCallback,
rootHints);
mMediaBrowser.connect();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
new file mode 100644
index 000000000000..2261aa5ac265
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link ResumeMediaBrowser} constructor
+ */
+public class ResumeMediaBrowserFactory {
+ private final Context mContext;
+ private final MediaBrowserFactory mBrowserFactory;
+
+ @Inject
+ public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) {
+ mContext = context;
+ mBrowserFactory = browserFactory;
+ }
+
+ /**
+ * Creates a new ResumeMediaBrowser.
+ *
+ * @param callback will be called on connection or error, and addTrack when media item found
+ * @param componentName component to browse
+ * @return
+ */
+ public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
+ ComponentName componentName) {
+ return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 9fc64d51cdf7..d1630ebe8dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -42,7 +42,9 @@ import java.util.List;
public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final String TAG = "MediaOutputAdapter";
- private static final int PAIR_NEW = 1;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private ViewGroup mConnectedItem;
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
@@ -58,11 +60,14 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
- if (mController.isZeroMode() && position == (mController.getMediaDevices().size())) {
- viewHolder.onBind(PAIR_NEW);
- } else if (position < (mController.getMediaDevices().size())) {
- viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position));
- } else {
+ final int size = mController.getMediaDevices().size();
+ if (mController.isZeroMode() && position == size) {
+ viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
+ true /* bottomMargin */);
+ } else if (position < size) {
+ viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
+ position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */);
+ } else if (DEBUG) {
Log.d(TAG, "Incorrect position: " + position);
}
}
@@ -76,18 +81,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
return mController.getMediaDevices().size();
}
- void onItemClick(MediaDevice device) {
- mController.connectDevice(device);
- device.setState(MediaDeviceState.STATE_CONNECTING);
- notifyDataSetChanged();
- }
-
- void onItemClick(int customizedItem) {
- if (customizedItem == PAIR_NEW) {
- mController.launchBluetoothPairing();
- }
- }
-
@Override
CharSequence getItemTitle(MediaDevice device) {
if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
@@ -112,51 +105,72 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
@Override
- void onBind(MediaDevice device) {
- super.onBind(device);
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
+ super.onBind(device, topMargin, bottomMargin);
+ final boolean currentlyConnected = isCurrentlyConnected(device);
+ if (currentlyConnected) {
+ mConnectedItem = mFrameLayout;
+ }
if (mController.isTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
- setTwoLineLayout(device, true);
- mProgressBar.setVisibility(View.VISIBLE);
- mSeekBar.setVisibility(View.GONE);
- mSubTitleText.setVisibility(View.GONE);
+ setTwoLineLayout(device, null /* title */, true /* bFocused */,
+ false /* showSeekBar*/, true /* showProgressBar */,
+ false /* showSubtitle */);
} else {
- setSingleLineLayout(getItemTitle(device), false);
+ setSingleLineLayout(getItemTitle(device), false /* bFocused */);
}
} else {
// Set different layout for each device
if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
- setTwoLineLayout(device, false);
- mSubTitleText.setVisibility(View.VISIBLE);
- mSeekBar.setVisibility(View.GONE);
- mProgressBar.setVisibility(View.GONE);
+ setTwoLineLayout(device, null /* title */, false /* bFocused */,
+ false /* showSeekBar*/, false /* showProgressBar */,
+ true /* showSubtitle */);
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
- mFrameLayout.setOnClickListener(v -> onItemClick(device));
+ mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
} else if (!mController.hasAdjustVolumeUserRestriction()
- && isCurrentConnected(device)) {
- setTwoLineLayout(device, true);
- mSeekBar.setVisibility(View.VISIBLE);
- mProgressBar.setVisibility(View.GONE);
- mSubTitleText.setVisibility(View.GONE);
+ && currentlyConnected) {
+ setTwoLineLayout(device, null /* title */, true /* bFocused */,
+ true /* showSeekBar*/, false /* showProgressBar */,
+ false /* showSubtitle */);
initSeekbar(device);
} else {
- setSingleLineLayout(getItemTitle(device), false);
- mFrameLayout.setOnClickListener(v -> onItemClick(device));
+ setSingleLineLayout(getItemTitle(device), false /* bFocused */);
+ mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
}
}
}
@Override
- void onBind(int customizedItem) {
- if (customizedItem == PAIR_NEW) {
+ void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+ super.onBind(customizedItem, topMargin, bottomMargin);
+ if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
- false);
+ false /* bFocused */);
final Drawable d = mContext.getDrawable(R.drawable.ic_add);
d.setColorFilter(new PorterDuffColorFilter(
Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
mTitleIcon.setImageDrawable(d);
- mFrameLayout.setOnClickListener(v -> onItemClick(PAIR_NEW));
+ mFrameLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+ }
+ }
+
+ private void onItemClick(View view, MediaDevice device) {
+ if (mController.isTransferring()) {
+ return;
+ }
+
+ playSwitchingAnim(mConnectedItem, view);
+ mController.connectDevice(device);
+ device.setState(MediaDeviceState.STATE_CONNECTING);
+ if (!isAnimating()) {
+ notifyDataSetChanged();
+ }
+ }
+
+ private void onItemClick(int customizedItem) {
+ if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
+ mController.launchBluetoothPairing();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 7579c25b030a..2d3e77db1ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Typeface;
import android.text.TextUtils;
@@ -33,6 +35,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
/**
@@ -44,9 +47,13 @@ public abstract class MediaOutputBaseAdapter extends
private static final String FONT_SELECTED_TITLE = "sans-serif-medium";
private static final String FONT_TITLE = "sans-serif";
+ static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
+
final MediaOutputController mController;
private boolean mIsDragging;
+ private int mMargin;
+ private boolean mIsAnimating;
Context mContext;
View mHolderView;
@@ -60,6 +67,8 @@ public abstract class MediaOutputBaseAdapter extends
public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
+ mMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_list_margin);
mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
viewGroup, false);
@@ -70,7 +79,7 @@ public abstract class MediaOutputBaseAdapter extends
return device.getName();
}
- boolean isCurrentConnected(MediaDevice device) {
+ boolean isCurrentlyConnected(MediaDevice device) {
return TextUtils.equals(device.getId(),
mController.getCurrentConnectedMediaDevice().getId());
}
@@ -79,10 +88,17 @@ public abstract class MediaOutputBaseAdapter extends
return mIsDragging;
}
+ boolean isAnimating() {
+ return mIsAnimating;
+ }
+
/**
* ViewHolder for binding device view.
*/
abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder {
+
+ private static final int ANIM_DURATION = 200;
+
final FrameLayout mFrameLayout;
final TextView mTitleText;
final TextView mTwoLineTitleText;
@@ -106,15 +122,28 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar = view.requireViewById(R.id.volume_seekbar);
}
- void onBind(MediaDevice device) {
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
mTitleIcon.setImageIcon(mController.getDeviceIconCompat(device).toIcon(mContext));
+ setMargin(topMargin, bottomMargin);
+ }
+
+ void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+ setMargin(topMargin, bottomMargin);
}
- void onBind(int customizedItem) { }
+ private void setMargin(boolean topMargin, boolean bottomMargin) {
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mFrameLayout
+ .getLayoutParams();
+ params.topMargin = topMargin ? mMargin : 0;
+ params.bottomMargin = bottomMargin ? mMargin : 0;
+ mFrameLayout.setLayoutParams(params);
+ }
void setSingleLineLayout(CharSequence title, boolean bFocused) {
- mTitleText.setVisibility(View.VISIBLE);
mTwoLineLayout.setVisibility(View.GONE);
+ mProgressBar.setVisibility(View.GONE);
+ mTitleText.setVisibility(View.VISIBLE);
+ mTitleText.setTranslationY(0);
mTitleText.setText(title);
if (bFocused) {
mTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
@@ -123,10 +152,21 @@ public abstract class MediaOutputBaseAdapter extends
}
}
- void setTwoLineLayout(MediaDevice device, boolean bFocused) {
+ void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
+ boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) {
mTitleText.setVisibility(View.GONE);
mTwoLineLayout.setVisibility(View.VISIBLE);
- mTwoLineTitleText.setText(getItemTitle(device));
+ mSeekBar.setAlpha(1);
+ mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
+ mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
+ mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
+ mTwoLineTitleText.setTranslationY(0);
+ if (device == null) {
+ mTwoLineTitleText.setText(title);
+ } else {
+ mTwoLineTitleText.setText(getItemTitle(device));
+ }
+
if (bFocused) {
mTwoLineTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE,
Typeface.NORMAL));
@@ -161,5 +201,53 @@ public abstract class MediaOutputBaseAdapter extends
}
});
}
+
+ void playSwitchingAnim(@NonNull View from, @NonNull View to) {
+ final float delta = (float) (mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_title_anim_y_delta));
+ final SeekBar fromSeekBar = from.requireViewById(R.id.volume_seekbar);
+ final TextView toTitleText = to.requireViewById(R.id.title);
+ if (fromSeekBar.getVisibility() != View.VISIBLE || toTitleText.getVisibility()
+ != View.VISIBLE) {
+ return;
+ }
+ mIsAnimating = true;
+ // Animation for title text
+ toTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
+ toTitleText.animate()
+ .setDuration(ANIM_DURATION)
+ .translationY(-delta)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ to.requireViewById(R.id.volume_indeterminate_progress).setVisibility(
+ View.VISIBLE);
+ }
+ });
+ // Animation for seek bar
+ fromSeekBar.animate()
+ .alpha(0)
+ .setDuration(ANIM_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final TextView fromTitleText = from.requireViewById(
+ R.id.two_line_title);
+ fromTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
+ fromTitleText.animate()
+ .setDuration(ANIM_DURATION)
+ .translationY(delta)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIsAnimating = false;
+ notifyDataSetChanged();
+ }
+ });
+ }
+ });
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f8f4f4df58bc..caef536961f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -33,7 +33,6 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.Button;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -50,7 +49,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
* Base dialog for media output UI
*/
public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
- MediaOutputController.Callback {
+ MediaOutputController.Callback, Window.Callback {
private static final String TAG = "MediaOutputDialog";
@@ -69,12 +68,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private LinearLayout mDeviceListLayout;
private Button mDoneButton;
private Button mStopButton;
- private View mListBottomPadding;
private int mListMaxHeight;
MediaOutputBaseAdapter mAdapter;
- FrameLayout mGroupItemController;
- View mGroupDivider;
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
// Set max height for list
@@ -114,12 +110,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
mHeaderIcon = mDialogView.requireViewById(R.id.header_icon);
mDevicesRecyclerView = mDialogView.requireViewById(R.id.list_result);
- mGroupItemController = mDialogView.requireViewById(R.id.group_item_controller);
- mGroupDivider = mDialogView.requireViewById(R.id.group_item_divider);
mDeviceListLayout = mDialogView.requireViewById(R.id.device_list);
mDoneButton = mDialogView.requireViewById(R.id.done);
mStopButton = mDialogView.requireViewById(R.id.stop);
- mListBottomPadding = mDialogView.requireViewById(R.id.list_bottom_padding);
mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
mDeviceListLayoutListener);
@@ -162,7 +155,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
if (mHeaderIcon.getVisibility() == View.VISIBLE) {
final int size = getHeaderIconSize();
- mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size, size));
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_header_icon_padding);
+ mHeaderIcon.setLayoutParams(new LinearLayout.LayoutParams(size + padding, size));
}
// Update title and subtitle
mHeaderTitle.setText(getHeaderText());
@@ -175,15 +170,11 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mHeaderSubtitle.setText(subTitle);
mHeaderTitle.setGravity(Gravity.NO_GRAVITY);
}
- if (!mAdapter.isDragging()) {
+ if (!mAdapter.isDragging() && !mAdapter.isAnimating()) {
mAdapter.notifyDataSetChanged();
}
- // Add extra padding when device amount is less than 6
- if (mMediaOutputController.getMediaDevices().size() < 6) {
- mListBottomPadding.setVisibility(View.VISIBLE);
- } else {
- mListBottomPadding.setVisibility(View.GONE);
- }
+ // Show when remote media session is available
+ mStopButton.setVisibility(getStopButtonVisibility());
}
abstract int getHeaderIconRes();
@@ -196,6 +187,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
abstract CharSequence getHeaderSubtitle();
+ abstract int getStopButtonVisibility();
+
@Override
public void onMediaChanged() {
mMainThreadHandler.post(() -> refresh());
@@ -217,4 +210,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
public void dismissDialog() {
dismiss();
}
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 64d20a273931..b1f1bda25961 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -24,6 +24,7 @@ import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
+import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -63,7 +64,7 @@ import javax.inject.Inject;
public class MediaOutputController implements LocalMediaManager.DeviceCallback{
private static final String TAG = "MediaOutputController";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final String mPackageName;
private final Context mContext;
@@ -406,6 +407,14 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
}
+ boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
+ final List<String> features = device.getFeatures();
+ return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK)
+ || features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK));
+ }
+
private final MediaController.Callback mCb = new MediaController.Callback() {
@Override
public void onMetadataChanged(MediaMetadata metadata) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index ac9d8ce52d88..a892a12f387b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -45,8 +45,6 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mGroupItemController.setVisibility(View.GONE);
- mGroupDivider.setVisibility(View.GONE);
}
@Override
@@ -74,4 +72,10 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
CharSequence getHeaderSubtitle() {
return mMediaOutputController.getHeaderSubTitle();
}
+
+ @Override
+ int getStopButtonVisibility() {
+ return mMediaOutputController.isActiveRemoteDevice(
+ mMediaOutputController.getCurrentConnectedMediaDevice()) ? View.VISIBLE : View.GONE;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index bc1dca58990d..4cdca4cbcf1e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -33,10 +33,22 @@ class MediaOutputDialogFactory @Inject constructor(
private val shadeController: ShadeController,
private val starter: ActivityStarter
) {
+ companion object {
+ var mediaOutputDialog: MediaOutputDialog? = null
+ }
+
/** Creates a [MediaOutputDialog] for the given package. */
fun create(packageName: String, aboveStatusBar: Boolean) {
- MediaOutputController(context, packageName, mediaSessionManager, lbm, shadeController,
- starter).run {
+ mediaOutputDialog?.dismiss()
+
+ mediaOutputDialog = MediaOutputController(context, packageName, mediaSessionManager, lbm,
+ shadeController, starter).run {
MediaOutputDialog(context, aboveStatusBar, this) }
}
+
+ /** dismiss [MediaOutputDialog] if exist. */
+ fun dismiss() {
+ mediaOutputDialog?.dismiss()
+ mediaOutputDialog = null
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 6cbf065ea6a9..df9e7a428877 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -47,6 +47,7 @@ import com.android.systemui.R;
import com.android.systemui.navigationbar.buttons.KeyButtonDrawable;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -156,7 +157,7 @@ public class RotationButtonController {
throw e.rethrowFromSystemServer();
}
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
}
void unregisterListeners() {
@@ -171,7 +172,7 @@ public class RotationButtonController {
throw e.rethrowFromSystemServer();
}
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
void addRotationCallback(Consumer<Integer> watcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
index fc2016913292..702be72ff4ed 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonDrawable.java
@@ -171,6 +171,24 @@ public class KeyButtonDrawable extends Drawable {
}
@Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ super.jumpToCurrentState();
+ if (mAnimatedDrawable != null) {
+ mAnimatedDrawable.jumpToCurrentState();
+ }
+ }
+
+ @Override
public void setAlpha(int alpha) {
mState.mAlpha = alpha;
mIconPaint.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 72cd4f1343e6..cf45f52e3367 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -225,6 +225,16 @@ public class KeyButtonRipple extends Drawable {
}
@Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ boolean changed = super.setVisible(visible, restart);
+ if (changed) {
+ // End any existing animations when the visibility changes
+ jumpToCurrentState();
+ }
+ return changed;
+ }
+
+ @Override
public void jumpToCurrentState() {
endAnimations("jumpToCurrentState", false /* cancel */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index d6b831640326..4efe4d85156b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -58,7 +58,6 @@ import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.system.QuickStepContract;
@@ -426,12 +425,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
if (getDisplay() != null) {
displayId = getDisplay().getDisplayId();
}
- // Bubble controller will give us a valid display id if it should get the back event
- BubbleController bubbleController = Dependency.get(BubbleController.class);
- int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
- if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
- displayId = bubbleDisplayId;
- }
if (displayId != INVALID_DISPLAY) {
ev.setDisplayId(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 56943604e9b3..18cc746666d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.navigationbar.gestural;
-import static android.view.Display.INVALID_DISPLAY;
-
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -57,7 +55,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -71,6 +68,7 @@ import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
@@ -207,6 +205,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
private boolean mUseMLModel;
private float mMLModelThreshold;
private String mPackageName;
+ private float mMLResults;
private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
@@ -386,7 +385,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
mGestureNavigationSettingsObserver.unregister();
mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);
mPluginManager.removePluginListener(this);
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
try {
@@ -402,7 +401,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
updateDisplaySize();
mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
mContext.getMainThreadHandler());
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
runnable -> (mContext.getMainThreadHandler()).post(runnable),
mOnPropertiesChangedListener);
@@ -531,10 +530,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
new long[]{(long) y},
};
- final float results = mBackGestureTfClassifierProvider.predict(featuresVector);
- if (results == -1) return -1;
+ mMLResults = mBackGestureTfClassifierProvider.predict(featuresVector);
+ if (mMLResults == -1) return -1;
- return results >= mMLModelThreshold ? 1 : 0;
+ return mMLResults >= mMLModelThreshold ? 1 : 0;
}
private boolean isWithinTouchRegion(int x, int y) {
@@ -604,6 +603,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
return;
}
mLogGesture = false;
+ String logPackageName = "";
+ // Due to privacy, only top 100 most used apps by all users can be logged.
+ if (mUseMLModel && mVocab.containsKey(mPackageName) && mVocab.get(mPackageName) < 100) {
+ logPackageName = mPackageName;
+ }
SysUiStatsLog.write(SysUiStatsLog.BACK_GESTURE_REPORTED_REPORTED, backType,
(int) mDownPoint.y, mIsOnLeftEdge
? SysUiStatsLog.BACK_GESTURE__X_LOCATION__LEFT
@@ -611,7 +615,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
(int) mDownPoint.x, (int) mDownPoint.y,
(int) mEndPoint.x, (int) mEndPoint.y,
mEdgeWidthLeft + mLeftInset,
- mDisplaySize.x - (mEdgeWidthRight + mRightInset));
+ mDisplaySize.x - (mEdgeWidthRight + mRightInset),
+ mUseMLModel ? mMLResults : -2, logPackageName);
}
private void onMotionEvent(MotionEvent ev) {
@@ -621,6 +626,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
+ mMLResults = 0;
mLogGesture = false;
mInRejectedExclusion = false;
mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
@@ -725,14 +731,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
- // Bubble controller will give us a valid display id if it should get the back event
- BubbleController bubbleController = Dependency.get(BubbleController.class);
- int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
- if (bubbleDisplayId != INVALID_DISPLAY) {
- ev.setDisplayId(bubbleDisplayId);
- } else {
- ev.setDisplayId(mContext.getDisplay().getDisplayId());
- }
+ ev.setDisplayId(mContext.getDisplay().getDisplayId());
InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 284f41a416d6..fbbda5f31093 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -16,6 +16,8 @@
package com.android.systemui.navigationbar.gestural;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
@@ -334,6 +336,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
.getDimension(R.dimen.navigation_edge_action_drag_threshold);
setVisibility(GONE);
+ boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@Override
@@ -345,8 +348,14 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
public Rect getSampledRegion(View sampledView) {
return mSamplingRect;
}
+
+ @Override
+ public boolean isSamplingEnabled() {
+ return isPrimaryDisplay;
+ }
});
mRegionSamplingHelper.setWindowVisible(true);
+ mShowProtection = !isPrimaryDisplay;
}
@Override
@@ -366,11 +375,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
updateIsDark(animate);
}
- private void setShowProtection(boolean showProtection) {
- mShowProtection = showProtection;
- invalidate();
- }
-
@Override
public void setIsLeftPanel(boolean isLeftPanel) {
mIsLeftPanel = isLeftPanel;
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
new file mode 100644
index 000000000000..eeb93bb7d766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -0,0 +1,169 @@
+/*
+ * 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.people;
+
+import android.app.Activity;
+import android.app.INotificationManager;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.service.notification.ConversationChannelWrapper;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * Shows the user their tiles for their priority People (go/live-status).
+ */
+public class PeopleSpaceActivity extends Activity {
+
+ private static String sTAG = "PeopleSpaceActivity";
+
+ private ViewGroup mPeopleSpaceLayout;
+ private IPeopleManager mPeopleManager;
+ private INotificationManager mNotificationManager;
+ private PackageManager mPackageManager;
+ private LauncherApps mLauncherApps;
+ private List<ConversationChannelWrapper> mConversations;
+ private Context mContext;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.people_space_activity);
+ mPeopleSpaceLayout = findViewById(R.id.people_space_layout);
+ mContext = getApplicationContext();
+ mNotificationManager =
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mPackageManager = getPackageManager();
+ mPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ setTileViewsWithPriorityConversations();
+ }
+
+ /**
+ * Retrieves all priority conversations and sets a {@link PeopleSpaceTileView}s for each
+ * priority conversation.
+ */
+ private void setTileViewsWithPriorityConversations() {
+ try {
+ List<ConversationChannelWrapper> conversations =
+ mNotificationManager.getConversations(
+ true /* priority only */).getList();
+ mConversations = conversations.stream().filter(
+ c -> shouldKeepConversation(c)).collect(Collectors.toList());
+ for (ConversationChannelWrapper conversation : mConversations) {
+ PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
+ mPeopleSpaceLayout,
+ conversation.getShortcutInfo().getId());
+ setTileView(tileView, conversation);
+ }
+ } catch (Exception e) {
+ Log.e(sTAG, "Couldn't retrieve conversations", e);
+ }
+ }
+
+ /** Sets {@code tileView} with the data in {@code conversation}. */
+ private void setTileView(PeopleSpaceTileView tileView,
+ ConversationChannelWrapper conversation) {
+ try {
+ ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
+ int userId = UserHandle.getUserHandleForUid(
+ conversation.getUid()).getIdentifier();
+
+ String pkg = shortcutInfo.getPackage();
+ long lastInteraction = mPeopleManager.getLastInteraction(
+ pkg, userId,
+ shortcutInfo.getId());
+ String status = lastInteraction != 0l ? mContext.getString(
+ R.string.last_interaction_status,
+ getLastInteractionString(
+ lastInteraction)) : mContext.getString(R.string.basic_status);
+ tileView.setStatus(status);
+
+ tileView.setName(shortcutInfo.getLabel().toString());
+ tileView.setPackageIcon(mPackageManager.getApplicationIcon(pkg));
+ tileView.setPersonIcon(mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0));
+ tileView.setOnClickListener(mLauncherApps, shortcutInfo);
+ } catch (Exception e) {
+ Log.e(sTAG, "Couldn't retrieve shortcut information", e);
+ }
+ }
+
+ /** Returns a readable representation of {@code lastInteraction}. */
+ private String getLastInteractionString(long lastInteraction) {
+ long now = System.currentTimeMillis();
+ Duration durationSinceLastInteraction = Duration.ofMillis(
+ now - lastInteraction);
+ MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
+ MeasureFormat.FormatWidth.WIDE);
+ if (durationSinceLastInteraction.toDays() >= 1) {
+ return
+ formatter
+ .formatMeasures(new Measure(durationSinceLastInteraction.toDays(),
+ MeasureUnit.DAY));
+ } else if (durationSinceLastInteraction.toHours() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.HOUR));
+ } else if (durationSinceLastInteraction.toMinutes() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toMinutes(),
+ MeasureUnit.MINUTE));
+ } else {
+ return formatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toMillis() / 1000,
+ MeasureUnit.SECOND));
+ }
+ }
+
+ /**
+ * Returns whether the {@code conversation} should be kept for display in the People Space.
+ *
+ * <p>A valid {@code conversation} must:
+ * <ul>
+ * <li>Have a non-null {@link ShortcutInfo}
+ * <li>Have an associated label in the {@link ShortcutInfo}
+ * </ul>
+ * </li>
+ */
+ private boolean shouldKeepConversation(ConversationChannelWrapper conversation) {
+ ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
+ return shortcutInfo != null && shortcutInfo.getLabel().length() != 0;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // Refresh tile views to sync new conversations.
+ setTileViewsWithPriorityConversations();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
new file mode 100644
index 000000000000..d5ef190d5ff1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
@@ -0,0 +1,83 @@
+/*
+ * 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.people;
+
+import android.content.Context;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * PeopleSpaceTileView renders an individual person's tile with associated status.
+ */
+public class PeopleSpaceTileView extends LinearLayout {
+
+ private View mTileView;
+ private TextView mNameView;
+ private TextView mStatusView;
+ private ImageView mPackageIconView;
+ private ImageView mPersonIconView;
+
+ public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId) {
+ super(context);
+ mTileView = view.findViewWithTag(shortcutId);
+ if (mTileView == null) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false);
+ view.addView(mTileView, LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
+ mTileView.setTag(shortcutId);
+ }
+ mNameView = mTileView.findViewById(R.id.tile_view_name);
+ mStatusView = mTileView.findViewById(R.id.tile_view_status);
+ mPackageIconView = mTileView.findViewById(R.id.tile_view_package_icon);
+ mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon);
+ }
+
+ /** Sets the name text on the tile. */
+ public void setName(String name) {
+ mNameView.setText(name);
+ }
+
+ /** Sets the status text on the tile. */
+ public void setStatus(String status) {
+ mStatusView.setText(status);
+ }
+
+ /** Sets the package drawable on the tile. */
+ public void setPackageIcon(Drawable drawable) {
+ mPackageIconView.setImageDrawable(drawable);
+ }
+
+ /** Sets the person drawable on the tile. */
+ public void setPersonIcon(Drawable drawable) {
+ mPersonIconView.setImageDrawable(drawable);
+ }
+
+ /** Sets the click listener of the tile. */
+ public void setOnClickListener(LauncherApps launcherApps, ShortcutInfo shortcutInfo) {
+ mTileView.setOnClickListener(v -> launcherApps.startShortcut(shortcutInfo, null, null));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/Pip.java b/packages/SystemUI/src/com/android/systemui/pip/Pip.java
deleted file mode 100644
index b068370da9e3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/Pip.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import android.content.res.Configuration;
-import android.media.session.MediaController;
-
-import com.android.systemui.pip.tv.PipController;
-import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
-
-import java.io.PrintWriter;
-
-/**
- * Interface to engage picture in picture feature.
- */
-public interface Pip {
- /**
- * Called when showing Pip menu.
- */
- void showPictureInPictureMenu();
-
- /**
- * Registers {@link com.android.systemui.pip.tv.PipController.Listener} that gets called.
- * whenever receiving notification on changes in PIP.
- */
- default void addListener(PipController.Listener listener) {
- }
-
- /**
- * Registers a {@link com.android.systemui.pip.tv.PipController.MediaListener} to PipController.
- */
- default void addMediaListener(PipController.MediaListener listener) {
- }
-
- /**
- * Closes PIP (PIPed activity and PIP system UI).
- */
- default void closePip() {
- }
-
- /**
- * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
- */
- default void expandPip() {
- }
-
- /**
- * Get current play back state. (e.g: Used in TV)
- *
- * @return The state of defined in PipController.
- */
- default int getPlaybackState() {
- return 0;
- }
-
- /**
- * Get MediaController.
- *
- * @return The MediaController instance.
- */
- default MediaController getMediaController() {
- return null;
- }
-
- /**
- * Hides the PIP menu.
- */
- void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback);
-
- /**
- * Returns {@code true} if PIP is shown.
- */
- default boolean isPipShown() {
- return false;
- }
-
- /**
- * Moves the PIPed activity to the fullscreen and closes PIP system UI.
- */
- default void movePipToFullscreen() {
- }
-
- /**
- * Called when configuration change invoked.
- */
- void onConfigurationChanged(Configuration newConfig);
-
- /**
- * Removes a {@link PipController.Listener} from PipController.
- */
- default void removeListener(PipController.Listener listener) {
- }
-
- /**
- * Removes a {@link com.android.systemui.pip.tv.PipController.MediaListener} from PipController.
- */
- default void removeMediaListener(PipController.MediaListener listener) {
- }
-
- /**
- * Resize the Pip to the appropriate size for the input state.
- *
- * @param state In Pip state also used to determine the new size for the Pip.
- */
- default void resizePinnedStack(int state) {
- }
-
- /**
- * Resumes resizing operation on the Pip that was previously suspended.
- *
- * @param reason The reason resizing operations on the Pip was suspended.
- */
- default void resumePipResizing(int reason) {
- }
-
- /**
- * Sets both shelf visibility and its height.
- *
- * @param visible visibility of shelf.
- * @param height to specify the height for shelf.
- */
- default void setShelfHeight(boolean visible, int height) {
- }
-
- /**
- * Set the pinned stack with {@link PipAnimationController.AnimationType}
- *
- * @param animationType The pre-defined {@link PipAnimationController.AnimationType}
- */
- default void setPinnedStackAnimationType(int animationType) {
- }
-
- /**
- * Registers the pinned stack animation listener.
- *
- * @param listener The listener of pinned stack animation.
- */
- default void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- }
-
- /**
- * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
- *
- * @param reason The reason for suspending resizing operations on the Pip.
- */
- default void suspendPipResizing(int reason) {
- }
-
- /**
- * Dump the current state and information if need.
- *
- * @param pw The stream to dump information to.
- */
- default void dump(PrintWriter pw) {
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java b/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
deleted file mode 100644
index 8e8b7f37b8d6..000000000000
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/dagger/TvPipComponent.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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 com.android.systemui.pip.tv.dagger;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.android.systemui.pip.tv.PipControlsView;
-import com.android.systemui.pip.tv.PipControlsViewController;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Scope;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Component for injecting into Pip related classes.
- */
-@Subcomponent
-public interface TvPipComponent {
- /**
- * Builder for {@link StatusBarComponent}.
- */
- @Subcomponent.Builder
- interface Builder {
- @BindsInstance
- TvPipComponent.Builder pipControlsView(PipControlsView pipControlsView);
- TvPipComponent build();
- }
-
- /**
- * Scope annotation for singleton items within the PipComponent.
- */
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface PipScope {}
-
- /**
- * Creates a StatusBarWindowViewController.
- */
- @TvPipComponent.PipScope
- PipControlsViewController getPipControlsViewController();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 0fbd73b615ce..f56e6cdf5cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -16,25 +16,23 @@
package com.android.systemui.privacy
-import android.app.ActivityManager
import android.app.AppOpsManager
-import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.UserInfo
import android.os.UserHandle
-import android.os.UserManager
import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.Dumpable
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.FileDescriptor
@@ -48,9 +46,8 @@ class PrivacyItemController @Inject constructor(
private val appOpsController: AppOpsController,
@Main uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
- private val broadcastDispatcher: BroadcastDispatcher,
private val deviceConfigProxy: DeviceConfigProxy,
- private val userManager: UserManager,
+ private val userTracker: UserTracker,
dumpManager: DumpManager
) : Dumpable {
@@ -153,13 +150,16 @@ class PrivacyItemController @Inject constructor(
}
@VisibleForTesting
- internal var userSwitcherReceiver = Receiver()
- set(value) {
- unregisterReceiver()
- field = value
- if (listening) registerReceiver()
+ internal var userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ update(true)
}
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ update(true)
+ }
+ }
+
init {
deviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_PRIVACY,
@@ -168,20 +168,18 @@ class PrivacyItemController @Inject constructor(
dumpManager.registerDumpable(TAG, this)
}
- private fun unregisterReceiver() {
- broadcastDispatcher.unregisterReceiver(userSwitcherReceiver)
+ private fun unregisterListener() {
+ userTracker.removeCallback(userTrackerCallback)
}
private fun registerReceiver() {
- broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter,
- null /* handler */, UserHandle.ALL)
+ userTracker.addCallback(userTrackerCallback, bgExecutor)
}
private fun update(updateUsers: Boolean) {
bgExecutor.execute {
if (updateUsers) {
- val currentUser = ActivityManager.getCurrentUser()
- currentUserIds = userManager.getProfiles(currentUser).map { it.id }
+ currentUserIds = userTracker.userProfiles.map { it.id }
}
updateListAndNotifyChanges.run()
}
@@ -206,7 +204,7 @@ class PrivacyItemController @Inject constructor(
update(true)
} else {
appOpsController.removeCallback(OPS, cb)
- unregisterReceiver()
+ unregisterListener()
// Make sure that we remove all indicators and notify listeners if we are not
// listening anymore due to indicators being disabled
update(false)
@@ -275,14 +273,6 @@ class PrivacyItemController @Inject constructor(
fun onFlagMicCameraChanged(flag: Boolean) {}
}
- internal inner class Receiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intentFilter.hasAction(intent.action)) {
- update(true)
- }
- }
- }
-
private class NotifyChangesToCallback(
private val callback: Callback?,
private val list: List<PrivacyItem>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 52a2cecec6b1..0053fea35262 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -2,6 +2,7 @@ package com.android.systemui.qs;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -31,9 +32,6 @@ public class PageIndicator extends ViewGroup {
private static final long ANIMATION_DURATION = 250;
- // The size of a single dot in relation to the whole animation.
- private static final float SINGLE_SCALE = .4f;
-
private static final float MINOR_ALPHA = .42f;
private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
@@ -75,11 +73,10 @@ public class PageIndicator extends ViewGroup {
}
array.recycle();
- mPageIndicatorWidth =
- (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_width);
- mPageIndicatorHeight =
- (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_height);
- mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE);
+ Resources res = context.getResources();
+ mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+ mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+ mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
}
public void setNumPages(int numPages) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 22c735d5fa11..04f379ef35ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -177,6 +177,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
}
@Override
+ public void endFakeDrag() {
+ try {
+ super.endFakeDrag();
+ } catch (NullPointerException e) {
+ // Not sure what's going on. Let's log it
+ Log.e(TAG, "endFakeDrag called without velocityTracker", e);
+ }
+ }
+
+ @Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
if (!isFakeDragging()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index eba4465018ab..7e2433a1fd33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.View;
import android.widget.FrameLayout;
@@ -31,7 +32,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.wm.shell.animation.PhysicsAnimator;
/**
* Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
@@ -282,7 +283,7 @@ public class QSContainerImpl extends FrameLayout {
View view = getChildAt(i);
if (view == mStatusBarBackground || view == mBackgroundGradient
|| view == mQSCustomizer) {
- // Some views are always full width
+ // Some views are always full width or have dependent padding
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
@@ -291,6 +292,9 @@ public class QSContainerImpl extends FrameLayout {
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd);
+ Pair<Integer, Integer> margins = mQSPanel.getVisualSideMargins();
+ // Apply paddings based on QSPanel
+ mQSCustomizer.setContentPaddings(margins.first, margins.second);
} else if (view == mHeader) {
// The header contains the QQS panel which needs to have special padding, to
// visually align them.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index fa3328417bd6..1e239b1e9ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -17,30 +17,44 @@
package com.android.systemui.qs;
import com.android.systemui.R;
+import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-public class QSContainerImplController {
- private final QSContainerImpl mView;
+class QSContainerImplController extends ViewController<QSContainerImpl> {
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
private QSContainerImplController(QSContainerImpl view,
QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
- mView = view;
+ super(view);
mQuickStatusBarHeaderController = quickStatusBarHeaderControllerBuilder
.setQuickStatusBarHeader(mView.findViewById(R.id.header)).build();
}
+ @Override
+ public void init() {
+ super.init();
+ mQuickStatusBarHeaderController.init();
+ }
+
public void setListening(boolean listening) {
mQuickStatusBarHeaderController.setListening(listening);
}
- public static class Builder {
+ @Override
+ protected void onViewAttached() {
+ }
+
+ @Override
+ protected void onViewDetached() {
+ }
+
+ static class Builder {
private final QuickStatusBarHeaderController.Builder mQuickStatusBarHeaderControllerBuilder;
private QSContainerImpl mView;
@Inject
- public Builder(
+ Builder(
QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
mQuickStatusBarHeaderControllerBuilder = quickStatusBarHeaderControllerBuilder;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 6e4ab9a3323a..84563a078447 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -20,6 +20,8 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -34,6 +36,7 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
@@ -57,6 +60,7 @@ import com.android.systemui.R;
import com.android.systemui.R.dimen;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -75,6 +79,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
private final DeviceProvisionedController mDeviceProvisionedController;
+ private final UserTracker mUserTracker;
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
@@ -115,11 +120,12 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ActivityStarter activityStarter, UserInfoController userInfoController,
- DeviceProvisionedController deviceProvisionedController) {
+ DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
super(context, attrs);
mActivityStarter = activityStarter;
mUserInfoController = userInfoController;
mDeviceProvisionedController = deviceProvisionedController;
+ mUserTracker = userTracker;
}
@VisibleForTesting
@@ -127,7 +133,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
this(context, attrs,
Dependency.get(ActivityStarter.class),
Dependency.get(UserInfoController.class),
- Dependency.get(DeviceProvisionedController.class));
+ Dependency.get(DeviceProvisionedController.class),
+ Dependency.get(UserTracker.class));
}
@Override
@@ -150,6 +157,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
+ mBuildText.setOnLongClickListener(view -> {
+ CharSequence buildText = mBuildText.getText();
+ if (!TextUtils.isEmpty(buildText)) {
+ ClipboardManager service =
+ mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
+ String label = mContext.getString(R.string.build_number_clip_data_label);
+ service.setPrimaryClip(ClipData.newPlainText(label, buildText));
+ Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ return false;
+ });
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -176,6 +196,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mBuildText.setSelected(true);
mShouldShowBuildText = true;
} else {
+ mBuildText.setText(null);
mShouldShowBuildText = false;
mBuildText.setSelected(false);
}
@@ -317,12 +338,14 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
+ mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
private void updateVisibilities() {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
+ : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -376,15 +399,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (TunerService.isTunerEnabled(mContext)) {
- TunerService.showResetRequest(mContext, () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
+ if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
+ TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
+ () -> {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity();
+ });
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, true);
+ TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
}
startSettingsActivity();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f1bb8996e181..3a783653a2d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -142,7 +142,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSContainerImplController = mQSContainerImplControllerBuilder
.setQSContainerImpl((QSContainerImpl) view)
.build();
-
+ mQSContainerImplController.init();
mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
@@ -367,14 +367,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening;
mQSContainerImplController.setListening(listening);
- mHeader.setListening(listening);
mFooter.setListening(listening);
mQSPanel.setListening(mListening, mQsExpanded);
}
@Override
public void setHeaderListening(boolean listening) {
- mHeader.setListening(listening);
+ mQSContainerImplController.setListening(listening);
mFooter.setListening(listening);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 290ab8594fc0..000fd1c4bd2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -30,6 +30,7 @@ public interface QSHost {
void openPanels();
Context getContext();
Context getUserContext();
+ int getUserId();
UiEventLogger getUiEventLogger();
Collection<QSTile> getTiles();
void addCallback(Callback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1eea4337eac1..61c6d3a629ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,12 +25,12 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.PointF;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,6 +57,7 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -114,6 +115,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private final QSLogger mQSLogger;
protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
+ private final UserTracker mUserTracker;
@Nullable
protected QSSecurityFooter mSecurityFooter;
@@ -157,7 +159,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -173,6 +176,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mDumpManager = dumpManager;
mBroadcastDispatcher = broadcastDispatcher;
mUiEventLogger = uiEventLogger;
+ mUserTracker = userTracker;
setOrientation(VERTICAL);
@@ -221,7 +225,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
protected void addSecurityFooter() {
- mSecurityFooter = new QSSecurityFooter(this, mContext);
+ mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker);
}
protected void addViewsAboveTiles() {
@@ -1080,6 +1084,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
updateTileLayoutMargins();
}
+ public Pair<Integer, Integer> getVisualSideMargins() {
+ return new Pair(mVisualMarginStart, mUsingHorizontalLayout ? 0 : mVisualMarginEnd);
+ }
+
private void updateTileLayoutMargins() {
int marginEnd = mVisualMarginEnd;
if (mUsingHorizontalLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index afc5be4e6c2f..0891972c11d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyEventLogger;
import android.content.Context;
@@ -45,6 +44,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
@@ -61,8 +61,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private final SecurityController mSecurityController;
private final ActivityStarter mActivityStarter;
private final Handler mMainHandler;
-
- private final UserManager mUm;
+ private final UserTracker mUserTracker;
private AlertDialog mDialog;
private QSTileHost mHost;
@@ -73,7 +72,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private int mFooterTextId;
private int mFooterIconId;
- public QSSecurityFooter(QSPanel qsPanel, Context context) {
+ public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) {
mRootView = LayoutInflater.from(context)
.inflate(R.layout.quick_settings_footer, qsPanel, false);
mRootView.setOnClickListener(this);
@@ -85,7 +84,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
mActivityStarter = Dependency.get(ActivityStarter.class);
mSecurityController = Dependency.get(SecurityController.class);
mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
- mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUserTracker = userTracker;
}
public void setHostEnvironment(QSTileHost host) {
@@ -138,7 +137,7 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private void handleRefreshState() {
final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
- final UserInfo currentUser = mUm.getUserInfo(ActivityManager.getCurrentUser());
+ final UserInfo currentUser = mUserTracker.getUserInfo();
final boolean isDemoDevice = UserManager.isDeviceInDemoMode(mContext) && currentUser != null
&& currentUser.isDemo();
final boolean hasWorkProfile = mSecurityController.hasWorkProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9a63a56b2c8e..0d0d01249c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -14,7 +14,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +48,7 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -99,6 +99,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private int mCurrentUser;
private final Optional<StatusBar> mStatusBarOptional;
private Context mUserContext;
+ private UserTracker mUserTracker;
@Inject
public QSTileHost(Context context,
@@ -113,7 +114,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
BroadcastDispatcher broadcastDispatcher,
Optional<StatusBar> statusBarOptional,
QSLogger qsLogger,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -125,12 +127,13 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mBroadcastDispatcher = broadcastDispatcher;
mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
- mServices = new TileServices(this, bgLooper, mBroadcastDispatcher);
+ mServices = new TileServices(this, bgLooper, mBroadcastDispatcher, userTracker);
mStatusBarOptional = statusBarOptional;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
mDumpManager.registerDumpable(TAG, this);
+ mUserTracker = userTracker;
mainHandler.post(() -> {
// This is technically a hack to avoid circular dependency of
@@ -230,6 +233,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
@Override
+ public int getUserId() {
+ return mCurrentUser;
+ }
+
+ @Override
public TileServices getTileServices() {
return mServices;
}
@@ -248,9 +256,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- int currentUser = ActivityManager.getCurrentUser();
+ int currentUser = mUserTracker.getUserId();
if (currentUser != mCurrentUser) {
- mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0);
+ mUserContext = mUserTracker.getUserContext();
if (mAutoTiles != null) {
mAutoTiles.changeUser(UserHandle.of(currentUser));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index affb7b91b6a5..ea036f6fe0e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -70,9 +71,11 @@ public class QuickQSPanel extends QSPanel {
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
MediaHost mediaHost,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ UserTracker userTracker
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger,
+ userTracker);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
applyBottomMargin((View) mRegularTileLayout);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 2e258d56ece0..a9fbc744b38e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -17,27 +17,16 @@ package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
import android.annotation.ColorInt;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.AlarmClock;
-import android.provider.Settings;
-import android.service.notification.ZenModeConfig;
-import android.text.format.DateUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.MathUtils;
import android.util.Pair;
import android.view.ContextThemeWrapper;
@@ -53,90 +42,48 @@ import android.widget.Space;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
-import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.DualToneHandler;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.privacy.PrivacyChipEvent;
-import com.android.systemui.privacy.PrivacyItem;
-import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.QSDetail.Callback;
-import com.android.systemui.qs.carrier.QSCarrierGroup;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.DateView;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.RingerModeTracker;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Objects;
-import javax.inject.Inject;
-import javax.inject.Named;
-
/**
* View that contains the top-most bits of the screen (primarily the status bar with date, time, and
* battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner
* contents.
*/
-public class QuickStatusBarHeader extends RelativeLayout implements
- View.OnClickListener, NextAlarmController.NextAlarmChangeCallback,
- ZenModeController.Callback, LifecycleOwner {
- private static final String TAG = "QuickStatusBarHeader";
- private static final boolean DEBUG = false;
-
- /** Delay for auto fading out the long press tooltip after it's fully visible (in ms). */
- private static final long AUTO_FADE_OUT_DELAY_MS = DateUtils.SECOND_IN_MILLIS * 6;
- private static final int FADE_ANIMATION_DURATION_MS = 300;
- private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0;
- public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
+public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwner {
- private final NextAlarmController mAlarmController;
- private final ZenModeController mZenController;
- private final StatusBarIconController mStatusBarIconController;
- private final ActivityStarter mActivityStarter;
-
- private QSPanel mQsPanel;
+ public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
private boolean mExpanded;
- private boolean mListening;
private boolean mQsDisabled;
- private QSCarrierGroup mCarrierGroup;
protected QuickQSPanel mHeaderQsPanel;
- protected QSTileHost mHost;
- private TintedIconManager mIconManager;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
private TouchAnimator mPrivacyChipAlphaAnimator;
private DualToneHandler mDualToneHandler;
- private final CommandQueue mCommandQueue;
private View mSystemIconsView;
private View mQuickQsStatusIcons;
private View mHeaderTextContainerView;
- private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
- private AlarmManager.AlarmClockInfo mNextAlarm;
-
private ImageView mNextAlarmIcon;
/** {@link TextView} containing the actual text indicating when the next alarm will go off. */
private TextView mNextAlarmTextView;
@@ -146,20 +93,13 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private TextView mRingerModeTextView;
private View mRingerContainer;
private Clock mClockView;
- private DateView mDateView;
private OngoingPrivacyChip mPrivacyChip;
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
- private RingerModeTracker mRingerModeTracker;
- private boolean mAllIndicatorsEnabled;
- private boolean mMicCameraIndicatorsEnabled;
- private PrivacyItemController mPrivacyItemController;
- private final UiEventLogger mUiEventLogger;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
- private boolean mHasTopCutout = false;
private int mStatusBarPaddingTop = 0;
private int mRoundedCornerPadding = 0;
private int mContentMarginStart;
@@ -169,56 +109,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private int mCutOutPaddingRight;
private float mExpandedHeaderAlpha = 1.0f;
private float mKeyguardExpansionFraction;
- private boolean mPrivacyChipLogged = false;
-
- private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
- @Override
- public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
- mPrivacyChip.setPrivacyList(privacyItems);
- setChipVisibility(!privacyItems.isEmpty());
- }
- @Override
- public void onFlagAllChanged(boolean flag) {
- if (mAllIndicatorsEnabled != flag) {
- mAllIndicatorsEnabled = flag;
- update();
- }
- }
-
- @Override
- public void onFlagMicCameraChanged(boolean flag) {
- if (mMicCameraIndicatorsEnabled != flag) {
- mMicCameraIndicatorsEnabled = flag;
- update();
- }
- }
-
- private void update() {
- StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
- iconContainer.setIgnoredSlots(getIgnoredIconSlots());
- setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
- }
- };
-
- @Inject
- public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- NextAlarmController nextAlarmController, ZenModeController zenModeController,
- StatusBarIconController statusBarIconController,
- ActivityStarter activityStarter, PrivacyItemController privacyItemController,
- CommandQueue commandQueue, RingerModeTracker ringerModeTracker,
- UiEventLogger uiEventLogger) {
+ public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
- mAlarmController = nextAlarmController;
- mZenController = zenModeController;
- mStatusBarIconController = statusBarIconController;
- mActivityStarter = activityStarter;
- mPrivacyItemController = privacyItemController;
mDualToneHandler = new DualToneHandler(
new ContextThemeWrapper(context, R.style.QSHeaderTheme));
- mCommandQueue = commandQueue;
- mRingerModeTracker = ringerModeTracker;
- mUiEventLogger = uiEventLogger;
}
@Override
@@ -228,11 +123,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons);
mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
- StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
- // Ignore privacy icons because they show in the space above QQS
- iconContainer.addIgnoredSlots(getIgnoredIconSlots());
- iconContainer.setShouldRestrictIcons(false);
- mIconManager = new TintedIconManager(iconContainer, mCommandQueue);
// Views corresponding to the header info section (e.g. ringer and next alarm).
mHeaderTextContainerView = findViewById(R.id.header_text_container);
@@ -240,35 +130,18 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mNextAlarmIcon = findViewById(R.id.next_alarm_icon);
mNextAlarmTextView = findViewById(R.id.next_alarm_text);
mNextAlarmContainer = findViewById(R.id.alarm_container);
- mNextAlarmContainer.setOnClickListener(this::onClick);
mRingerModeIcon = findViewById(R.id.ringer_mode_icon);
mRingerModeTextView = findViewById(R.id.ringer_mode_text);
mRingerContainer = findViewById(R.id.ringer_container);
- mRingerContainer.setOnClickListener(this::onClick);
mPrivacyChip = findViewById(R.id.privacy_chip);
- mPrivacyChip.setOnClickListener(this::onClick);
- mCarrierGroup = findViewById(R.id.carrier_group);
-
updateResources();
Rect tintArea = new Rect(0, 0, 0, 0);
- int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
- android.R.attr.colorForeground);
- float intensity = getColorIntensity(colorForeground);
- int fillColor = mDualToneHandler.getSingleColor(intensity);
-
// Set light text on the header icons because they will always be on a black background
applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
- // Set the correct tint for the status icons so they contrast
- mIconManager.setTint(fillColor);
- mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor));
- mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor));
-
mClockView = findViewById(R.id.clock);
- mClockView.setOnClickListener(this);
- mDateView = findViewById(R.id.date);
mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
@@ -280,33 +153,28 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
mRingerModeTextView.setSelected(true);
mNextAlarmTextView.setSelected(true);
+ }
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
- mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ void onAttach(TintedIconManager iconManager) {
+ int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
+ android.R.attr.colorForeground);
+ float intensity = getColorIntensity(colorForeground);
+ int fillColor = mDualToneHandler.getSingleColor(intensity);
+
+ // Set the correct tint for the status icons so they contrast
+ iconManager.setTint(fillColor);
+ mNextAlarmIcon.setImageTintList(ColorStateList.valueOf(fillColor));
+ mRingerModeIcon.setImageTintList(ColorStateList.valueOf(fillColor));
}
public QuickQSPanel getHeaderQsPanel() {
return mHeaderQsPanel;
}
- private List<String> getIgnoredIconSlots() {
- ArrayList<String> ignored = new ArrayList<>();
- if (getChipEnabled()) {
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_camera));
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_microphone));
- if (mAllIndicatorsEnabled) {
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_location));
- }
- }
-
- return ignored;
- }
-
- private void updateStatusText() {
- boolean changed = updateRingerStatus() || updateAlarmStatus();
+ void updateStatusText(int ringerMode, AlarmClockInfo nextAlarm, boolean zenOverridingRinger,
+ boolean use24HourFormat) {
+ boolean changed = updateRingerStatus(ringerMode, zenOverridingRinger)
+ || updateAlarmStatus(nextAlarm, use24HourFormat);
if (changed) {
boolean alarmVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
@@ -316,32 +184,17 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
}
- private void setChipVisibility(boolean chipVisible) {
- if (chipVisible && getChipEnabled()) {
- mPrivacyChip.setVisibility(View.VISIBLE);
- // Makes sure that the chip is logged as viewed at most once each time QS is opened
- // mListening makes sure that the callback didn't return after the user closed QS
- if (!mPrivacyChipLogged && mListening) {
- mPrivacyChipLogged = true;
- mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
- }
- } else {
- mPrivacyChip.setVisibility(View.GONE);
- }
- }
-
- private boolean updateRingerStatus() {
+ private boolean updateRingerStatus(int ringerMode, boolean zenOverridingRinger) {
boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
CharSequence originalRingerText = mRingerModeTextView.getText();
boolean ringerVisible = false;
- if (!ZenModeConfig.isZenOverridingRinger(mZenController.getZen(),
- mZenController.getConsolidatedPolicy())) {
- if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
+ if (!zenOverridingRinger) {
+ if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
mRingerModeTextView.setText(R.string.qs_status_phone_vibrate);
ringerVisible = true;
- } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) {
+ } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
mRingerModeIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
mRingerModeTextView.setText(R.string.qs_status_phone_muted);
ringerVisible = true;
@@ -355,14 +208,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
!Objects.equals(originalRingerText, mRingerModeTextView.getText());
}
- private boolean updateAlarmStatus() {
+ private boolean updateAlarmStatus(AlarmClockInfo nextAlarm, boolean use24HourFormat) {
boolean isOriginalVisible = mNextAlarmTextView.getVisibility() == View.VISIBLE;
CharSequence originalAlarmText = mNextAlarmTextView.getText();
boolean alarmVisible = false;
- if (mNextAlarm != null) {
+ if (nextAlarm != null) {
alarmVisible = true;
- mNextAlarmTextView.setText(formatNextAlarm(mNextAlarm));
+ mNextAlarmTextView.setText(formatNextAlarm(nextAlarm, use24HourFormat));
}
mNextAlarmIcon.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
mNextAlarmTextView.setVisibility(alarmVisible ? View.VISIBLE : View.GONE);
@@ -409,7 +262,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
setMinimumHeight(sbHeight + qqsHeight);
}
- private void updateResources() {
+ void updateResources() {
Resources resources = mContext.getResources();
updateMinimumHeight();
@@ -519,17 +372,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
@Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mRingerModeTracker.getRingerModeInternal().observe(this, ringer -> {
- mRingerMode = ringer;
- updateStatusText();
- });
- mStatusBarIconController.addIconGroup(mIconManager);
- requestApplyInsets();
- }
-
- @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
// Handle padding of the clock
DisplayCutout cutout = insets.getDisplayCutout();
@@ -552,17 +394,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
if (cutout != null) {
Rect topCutout = cutout.getBoundingRectTop();
if (topCutout.isEmpty() || cornerCutout) {
- mHasTopCutout = false;
lp.width = 0;
mSpace.setVisibility(View.GONE);
} else {
- mHasTopCutout = true;
lp.width = topCutout.width();
mSpace.setVisibility(View.VISIBLE);
}
}
mSpace.setLayoutParams(lp);
- setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
mCutOutPaddingLeft = padding.first;
mCutOutPaddingRight = padding.second;
mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
@@ -600,102 +439,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements
0);
}
- @Override
- @VisibleForTesting
- public void onDetachedFromWindow() {
- setListening(false);
- mRingerModeTracker.getRingerModeInternal().removeObservers(this);
- mStatusBarIconController.removeIconGroup(mIconManager);
- super.onDetachedFromWindow();
- }
-
- public void setListening(boolean listening) {
- if (listening == mListening) {
- return;
- }
- mHeaderQsPanel.setListening(listening);
- if (mHeaderQsPanel.switchTileLayout()) {
- updateResources();
- }
- mListening = listening;
-
- if (listening) {
- mZenController.addCallback(this);
- mAlarmController.addCallback(this);
- mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
- // Get the most up to date info
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
- mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
- mPrivacyItemController.addCallback(mPICCallback);
- } else {
- mZenController.removeCallback(this);
- mAlarmController.removeCallback(this);
- mLifecycle.setCurrentState(Lifecycle.State.CREATED);
- mPrivacyItemController.removeCallback(mPICCallback);
- mPrivacyChipLogged = false;
- }
- }
-
- @Override
- public void onClick(View v) {
- if (v == mClockView) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- } else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) {
- if (mNextAlarm.getShowIntent() != null) {
- mActivityStarter.postStartActivityDismissingKeyguard(
- mNextAlarm.getShowIntent());
- } else {
- Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
- } else if (v == mPrivacyChip) {
- // If the privacy chip is visible, it means there were some indicators
- Handler mUiHandler = new Handler(Looper.getMainLooper());
- mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
- mUiHandler.post(() -> {
- mActivityStarter.postStartActivityDismissingKeyguard(
- new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
- mHost.collapsePanels();
- });
- } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
- mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
- Settings.ACTION_SOUND_SETTINGS), 0);
- }
- }
-
- @Override
- public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
- mNextAlarm = nextAlarm;
- updateStatusText();
- }
-
- @Override
- public void onZenChanged(int zen) {
- updateStatusText();
- }
-
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- updateStatusText();
- }
-
public void updateEverything() {
post(() -> setClickable(!mExpanded));
}
public void setQSPanel(final QSPanel qsPanel) {
- mQsPanel = qsPanel;
- setupHost(qsPanel.getHost());
- }
-
- public void setupHost(final QSTileHost host) {
- mHost = host;
//host.setHeaderView(mExpandIndicator);
- mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
- mHeaderQsPanel.setHost(host, null /* No customization in header */);
-
+ mHeaderQsPanel.setQSPanelAndHeader(qsPanel, this);
+ mHeaderQsPanel.setHost(qsPanel.getHost(), null /* No customization in header */);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
@@ -709,12 +460,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mHeaderQsPanel.setCallback(qsPanelCallback);
}
- private String formatNextAlarm(AlarmManager.AlarmClockInfo info) {
+ private String formatNextAlarm(AlarmClockInfo info, boolean use24HourFormat) {
if (info == null) {
return "";
}
- String skeleton = android.text.format.DateFormat
- .is24HourFormat(mContext, ActivityManager.getCurrentUser()) ? "EHm" : "Ehma";
+ String skeleton = use24HourFormat ? "EHm" : "Ehma";
String pattern = android.text.format.DateFormat
.getBestDateTimePattern(Locale.getDefault(), skeleton);
return android.text.format.DateFormat.format(pattern, info.getTriggerTime()).toString();
@@ -765,8 +515,4 @@ public class QuickStatusBarHeader extends RelativeLayout implements
updateHeaderTextContainerAlphaAnimator();
}
}
-
- private boolean getChipEnabled() {
- return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index d899acbade4a..676a300b0ff2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -16,36 +16,393 @@
package com.android.systemui.qs;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.AlarmClock;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.privacy.PrivacyChipEvent;
+import com.android.systemui.privacy.PrivacyItem;
+import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusIconContainer;
+import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
-public class QuickStatusBarHeaderController {
- private final QuickStatusBarHeader mView;
+/**
+ * Controller for {@link QuickStatusBarHeader}.
+ */
+class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
+ private static final String TAG = "QuickStatusBarHeader";
+
+ private final ZenModeController mZenModeController;
+ private final NextAlarmController mNextAlarmController;
+ private final PrivacyItemController mPrivacyItemController;
+ private final RingerModeTracker mRingerModeTracker;
+ private final ActivityStarter mActivityStarter;
+ private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController;
+ private final QuickQSPanel mHeaderQsPanel;
+ private final LifecycleRegistry mLifecycle;
+ private final OngoingPrivacyChip mPrivacyChip;
+ private final Clock mClockView;
+ private final View mNextAlarmContainer;
+ private final View mRingerContainer;
+ private final QSTileHost mQSTileHost;
+ private final StatusBarIconController mStatusBarIconController;
+ private final CommandQueue mCommandQueue;
+ private final DemoModeController mDemoModeController;
+ private final UserTracker mUserTracker;
+ private final StatusIconContainer mIconContainer;
+ private final StatusBarIconController.TintedIconManager mIconManager;
+ private final DemoMode mDemoModeReceiver;
+
+ private boolean mListening;
+ private AlarmClockInfo mNextAlarm;
+ private boolean mAllIndicatorsEnabled;
+ private boolean mMicCameraIndicatorsEnabled;
+ private boolean mPrivacyChipLogged;
+ private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+
+ private final ZenModeController.Callback mZenModeControllerCallback = new Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+
+ @Override
+ public void onConfigChanged(ZenModeConfig config) {
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+ };
+
+ private boolean use24HourFormat() {
+ return android.text.format.DateFormat.is24HourFormat(
+ mView.getContext(), mUserTracker.getUserId());
+
+ }
+
+ private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() {
+ @Override
+ public void onNextAlarmChanged(AlarmClockInfo nextAlarm) {
+ mNextAlarm = nextAlarm;
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ }
+ };
+
+ private final LifecycleOwner mLifecycleOwner = new LifecycleOwner() {
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ return mLifecycle;
+ }
+ };
+
+ private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
+ @Override
+ public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
+ mPrivacyChip.setPrivacyList(privacyItems);
+ setChipVisibility(!privacyItems.isEmpty());
+ }
+
+ @Override
+ public void onFlagAllChanged(boolean flag) {
+ if (mAllIndicatorsEnabled != flag) {
+ mAllIndicatorsEnabled = flag;
+ update();
+ }
+ }
+
+ @Override
+ public void onFlagMicCameraChanged(boolean flag) {
+ if (mMicCameraIndicatorsEnabled != flag) {
+ mMicCameraIndicatorsEnabled = flag;
+ update();
+ }
+ }
+
+ private void update() {
+ StatusIconContainer iconContainer = mView.requireViewById(R.id.statusIcons);
+ iconContainer.setIgnoredSlots(getIgnoredIconSlots());
+ setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
+ }
+ };
+
+ private View.OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v == mClockView) {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ } else if (v == mNextAlarmContainer && mNextAlarmContainer.isVisibleToUser()) {
+ if (mNextAlarm.getShowIntent() != null) {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ mNextAlarm.getShowIntent());
+ } else {
+ Log.d(TAG, "No PendingIntent for next alarm. Using default intent");
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS), 0);
+ }
+ } else if (v == mPrivacyChip) {
+ // If the privacy chip is visible, it means there were some indicators
+ Handler mUiHandler = new Handler(Looper.getMainLooper());
+ mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
+ mUiHandler.post(() -> {
+ mActivityStarter.postStartActivityDismissingKeyguard(
+ new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
+ mQSTileHost.collapsePanels();
+ });
+ } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
+ mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
+ Settings.ACTION_SOUND_SETTINGS), 0);
+ }
+ }
+ };
private QuickStatusBarHeaderController(QuickStatusBarHeader view,
+ ZenModeController zenModeController, NextAlarmController nextAlarmController,
+ PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger,
+ QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
+ CommandQueue commandQueue, DemoModeController demoModeController,
+ UserTracker userTracker,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
- mView = view;
+ super(view);
+ mZenModeController = zenModeController;
+ mNextAlarmController = nextAlarmController;
+ mPrivacyItemController = privacyItemController;
+ mRingerModeTracker = ringerModeTracker;
+ mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
+ mQSTileHost = qsTileHost;
+ mStatusBarIconController = statusBarIconController;
+ mCommandQueue = commandQueue;
+ mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
+ mLifecycle = new LifecycleRegistry(mLifecycleOwner);
+
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
.build();
+
+
+ mPrivacyChip = mView.findViewById(R.id.privacy_chip);
+ mHeaderQsPanel = mView.findViewById(R.id.quick_qs_panel);
+ mNextAlarmContainer = mView.findViewById(R.id.alarm_container);
+ mClockView = mView.findViewById(R.id.clock);
+ mRingerContainer = mView.findViewById(R.id.ringer_container);
+ mIconContainer = mView.findViewById(R.id.statusIcons);
+
+ mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, mCommandQueue);
+ mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mRingerModeTracker.getRingerModeInternal().observe(mLifecycleOwner, ringer -> {
+ mRingerMode = ringer;
+ mView.updateStatusText(mRingerMode, mNextAlarm, isZenOverridingRinger(),
+ use24HourFormat());
+ });
+
+ mClockView.setOnClickListener(mOnClickListener);
+ mNextAlarmContainer.setOnClickListener(mOnClickListener);
+ mRingerContainer.setOnClickListener(mOnClickListener);
+ mPrivacyChip.setOnClickListener(mOnClickListener);
+
+ // Ignore privacy icons because they show in the space above QQS
+ mIconContainer.addIgnoredSlots(getIgnoredIconSlots());
+ mIconContainer.setShouldRestrictIcons(false);
+ mStatusBarIconController.addIconGroup(mIconManager);
+
+ mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+
+ setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
+
+ mView.onAttach(mIconManager);
+
+ mDemoModeController.addCallback(mDemoModeReceiver);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mRingerModeTracker.getRingerModeInternal().removeObservers(mLifecycleOwner);
+ mClockView.setOnClickListener(null);
+ mNextAlarmContainer.setOnClickListener(null);
+ mRingerContainer.setOnClickListener(null);
+ mPrivacyChip.setOnClickListener(null);
+ mStatusBarIconController.removeIconGroup(mIconManager);
+ mDemoModeController.removeCallback(mDemoModeReceiver);
+ setListening(false);
}
public void setListening(boolean listening) {
mQSCarrierGroupController.setListening(listening);
- // TODO: move mView.setListening logic into here.
- mView.setListening(listening);
+
+ if (listening == mListening) {
+ return;
+ }
+ mListening = listening;
+
+ mHeaderQsPanel.setListening(listening);
+ if (mHeaderQsPanel.switchTileLayout()) {
+ mView.updateResources();
+ }
+
+ if (listening) {
+ mZenModeController.addCallback(mZenModeControllerCallback);
+ mNextAlarmController.addCallback(mNextAlarmChangeCallback);
+ mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
+ // Get the most up to date info
+ mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ mPrivacyItemController.addCallback(mPICCallback);
+ } else {
+ mZenModeController.removeCallback(mZenModeControllerCallback);
+ mNextAlarmController.removeCallback(mNextAlarmChangeCallback);
+ mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ mPrivacyItemController.removeCallback(mPICCallback);
+ mPrivacyChipLogged = false;
+ }
}
+ private void setChipVisibility(boolean chipVisible) {
+ if (chipVisible && getChipEnabled()) {
+ mPrivacyChip.setVisibility(View.VISIBLE);
+ // Makes sure that the chip is logged as viewed at most once each time QS is opened
+ // mListening makes sure that the callback didn't return after the user closed QS
+ if (!mPrivacyChipLogged && mListening) {
+ mPrivacyChipLogged = true;
+ mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
+ }
+ } else {
+ mPrivacyChip.setVisibility(View.GONE);
+ }
+ }
- public static class Builder {
+ private List<String> getIgnoredIconSlots() {
+ ArrayList<String> ignored = new ArrayList<>();
+ if (getChipEnabled()) {
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_camera));
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_microphone));
+ if (mAllIndicatorsEnabled) {
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_location));
+ }
+ }
+
+ return ignored;
+ }
+
+ private boolean getChipEnabled() {
+ return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
+ }
+
+ private boolean isZenOverridingRinger() {
+ return ZenModeConfig.isZenOverridingRinger(mZenModeController.getZen(),
+ mZenModeController.getConsolidatedPolicy());
+ }
+
+
+ private static class ClockDemoModeReceiver implements DemoMode {
+ private Clock mClockView;
+
+ @Override
+ public List<String> demoCommands() {
+ return List.of(COMMAND_CLOCK);
+ }
+
+ ClockDemoModeReceiver(Clock clockView) {
+ mClockView = clockView;
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ mClockView.dispatchDemoCommand(command, args);
+ }
+
+ @Override
+ public void onDemoModeStarted() {
+ mClockView.onDemoModeStarted();
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ mClockView.onDemoModeFinished();
+ }
+ }
+
+ static class Builder {
+ private final ZenModeController mZenModeController;
+ private final NextAlarmController mNextAlarmController;
+ private final PrivacyItemController mPrivacyItemController;
+ private final RingerModeTracker mRingerModeTracker;
+ private final ActivityStarter mActivityStarter;
+ private final UiEventLogger mUiEventLogger;
+ private final QSTileHost mQsTileHost;
+ private final StatusBarIconController mStatusBarIconController;
+ private final CommandQueue mCommandQueue;
+ private final DemoModeController mDemoModeController;
+ private final UserTracker mUserTracker;
private final QSCarrierGroupController.Builder mQSCarrierGroupControllerBuilder;
private QuickStatusBarHeader mView;
@Inject
- public Builder(QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ Builder(ZenModeController zenModeController, NextAlarmController nextAlarmController,
+ PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
+ ActivityStarter activityStarter, UiEventLogger uiEventLogger, QSTileHost qsTileHost,
+ StatusBarIconController statusBarIconController, CommandQueue commandQueue,
+ DemoModeController demoModeController, UserTracker userTracker,
+ QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ mZenModeController = zenModeController;
+ mNextAlarmController = nextAlarmController;
+ mPrivacyItemController = privacyItemController;
+ mRingerModeTracker = ringerModeTracker;
+ mActivityStarter = activityStarter;
+ mUiEventLogger = uiEventLogger;
+ mQsTileHost = qsTileHost;
+ mStatusBarIconController = statusBarIconController;
+ mCommandQueue = commandQueue;
+ mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
mQSCarrierGroupControllerBuilder = qsCarrierGroupControllerBuilder;
}
@@ -54,8 +411,13 @@ public class QuickStatusBarHeaderController {
return this;
}
- public QuickStatusBarHeaderController build() {
- return new QuickStatusBarHeaderController(mView, mQSCarrierGroupControllerBuilder);
+
+ QuickStatusBarHeaderController build() {
+ return new QuickStatusBarHeaderController(mView, mZenModeController,
+ mNextAlarmController, mPrivacyItemController, mRingerModeTracker,
+ mActivityStarter, mUiEventLogger, mQsTileHost, mStatusBarIconController,
+ mCommandQueue, mDemoModeController, mUserTracker,
+ mQSCarrierGroupControllerBuilder);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 65d815053e47..3ee3e117fb0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
@@ -37,10 +36,6 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
protected abstract void handleValueChanged(int value, boolean observedChange);
- protected SecureSetting(Context context, Handler handler, String settingName) {
- this(context, handler, settingName, ActivityManager.getCurrentUser());
- }
-
public SecureSetting(Context context, Handler handler, String settingName, int userId) {
super(handler);
mContext = context;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index e5ed88c10a2e..55b67e061c13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -132,6 +132,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
mRecyclerView.setLayoutManager(layout);
mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+ mRecyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
@@ -221,6 +222,22 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
}
}
+ /**
+ * Sets the padding for the RecyclerView. Also, updates the margin between the tiles in the
+ * {@link TileAdapter}.
+ */
+ public void setContentPaddings(int paddingStart, int paddingEnd) {
+ int halfMargin = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
+ mTileAdapter.changeHalfMargin(halfMargin);
+ mRecyclerView.setPaddingRelative(
+ paddingStart,
+ mRecyclerView.getPaddingTop(),
+ paddingEnd,
+ mRecyclerView.getPaddingBottom()
+ );
+ }
+
private void queryTiles() {
mTileQueryHelper.queryTiles(mHost);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index bffeb3ec3c70..b471dfae02d1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -18,6 +18,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.LayoutInflater;
@@ -75,6 +76,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
private final List<TileInfo> mTiles = new ArrayList<>();
private final ItemTouchHelper mItemTouchHelper;
private final ItemDecoration mDecoration;
+ private final MarginTileDecoration mMarginDecoration;
private final int mMinNumTiles;
private int mEditIndex;
private int mTileDividerIndex;
@@ -97,6 +99,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mUiEventLogger = uiEventLogger;
mItemTouchHelper = new ItemTouchHelper(mCallbacks);
mDecoration = new TileItemDecoration(context);
+ mMarginDecoration = new MarginTileDecoration();
mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
mAccessibilityDelegate = new TileAdapterDelegate();
}
@@ -123,6 +126,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
return mDecoration;
}
+ public ItemDecoration getMarginItemDecoration() {
+ return mMarginDecoration;
+ }
+
+ public void changeHalfMargin(int halfMargin) {
+ mMarginDecoration.setHalfMargin(halfMargin);
+ }
+
public void saveSpecs(QSTileHost host) {
List<String> newSpecs = new ArrayList<>();
clearAccessibilityState();
@@ -596,7 +607,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mDrawable = context.getDrawable(R.drawable.qs_customize_tile_decoration);
}
-
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
super.onDraw(c, parent, state);
@@ -607,6 +617,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final ViewHolder holder = parent.getChildViewHolder(child);
+ // Do not draw background for the holder that's currently being dragged
+ if (holder == mCurrentDrag) {
+ continue;
+ }
+ // Do not draw background for holders before the edit index (header and current
+ // tiles)
if (holder.getAdapterPosition() == 0 ||
holder.getAdapterPosition() < mEditIndex && !(child instanceof TextView)) {
continue;
@@ -624,6 +640,25 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
}
}
+ private static class MarginTileDecoration extends ItemDecoration {
+ private int mHalfMargin;
+
+ public void setHalfMargin(int halfMargin) {
+ mHalfMargin = halfMargin;
+ }
+
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+ @NonNull RecyclerView parent, @NonNull State state) {
+ if (view instanceof TextView) {
+ super.getItemOffsets(outRect, view, parent, state);
+ } else {
+ outRect.left = mHalfMargin;
+ outRect.right = mHalfMargin;
+ }
+ }
+ }
+
private final ItemTouchHelper.Callback mCallbacks = new ItemTouchHelper.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 73c6504b9983..b795a5f5ea19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.customize;
import android.Manifest.permission;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -40,6 +39,7 @@ import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -58,16 +58,18 @@ public class TileQueryHelper {
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final Context mContext;
+ private final UserTracker mUserTracker;
private TileStateListener mListener;
private boolean mFinished;
@Inject
- public TileQueryHelper(Context context,
+ public TileQueryHelper(Context context, UserTracker userTracker,
@Main Executor mainExecutor, @Background Executor bgExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
+ mUserTracker = userTracker;
}
public void setListener(TileStateListener listener) {
@@ -207,7 +209,7 @@ public class TileQueryHelper {
Collection<QSTile> params = host.getTiles();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+ new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
for (ResolveInfo info : services) {
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 19c7b6cefc5d..6e28cd89d43a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -18,7 +18,6 @@ package com.android.systemui.qs.external;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -304,8 +303,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
}
private Intent resolveIntent(Intent i) {
- ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0,
- ActivityManager.getCurrentUser());
+ ResolveInfo result = mContext.getPackageManager().resolveActivityAsUser(i, 0, mUser);
return result != null ? new Intent(TileService.ACTION_QS_TILE_PREFERENCES)
.setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index cfa8fb6373a1..7e76e57f4802 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,7 +15,6 @@
*/
package com.android.systemui.qs.external;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -26,7 +25,6 @@ import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
-import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
@@ -36,6 +34,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
import java.util.Objects;
@@ -60,6 +59,7 @@ public class TileServiceManager {
private final TileServices mServices;
private final TileLifecycleManager mStateManager;
private final Handler mHandler;
+ private final UserTracker mUserTracker;
private boolean mBindRequested;
private boolean mBindAllowed;
private boolean mBound;
@@ -73,25 +73,26 @@ public class TileServiceManager {
private boolean mStarted = false;
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
- Tile tile, BroadcastDispatcher broadcastDispatcher) {
- this(tileServices, handler, new TileLifecycleManager(handler,
+ Tile tile, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+ this(tileServices, handler, userTracker, new TileLifecycleManager(handler,
tileServices.getContext(), tileServices, tile, new Intent().setComponent(component),
- new UserHandle(ActivityManager.getCurrentUser()), broadcastDispatcher));
+ userTracker.getUserHandle(), broadcastDispatcher));
}
@VisibleForTesting
- TileServiceManager(TileServices tileServices, Handler handler,
+ TileServiceManager(TileServices tileServices, Handler handler, UserTracker userTracker,
TileLifecycleManager tileLifecycleManager) {
mServices = tileServices;
mHandler = handler;
mStateManager = tileLifecycleManager;
+ mUserTracker = userTracker;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
Context context = mServices.getContext();
- context.registerReceiverAsUser(mUninstallReceiver,
- new UserHandle(ActivityManager.getCurrentUser()), filter, null, mHandler);
+ context.registerReceiverAsUser(mUninstallReceiver, userTracker.getUserHandle(), filter,
+ null, mHandler);
}
boolean isLifecycleStarted() {
@@ -279,7 +280,7 @@ public class TileServiceManager {
queryIntent.setPackage(pkgName);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- queryIntent, 0, ActivityManager.getCurrentUser());
+ queryIntent, 0, mUserTracker.getUserId());
for (ResolveInfo info : services) {
if (Objects.equals(info.serviceInfo.packageName, component.getPackageName())
&& Objects.equals(info.serviceInfo.name, component.getClassName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 2863d08a75dc..35cf2a12745e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,6 +39,7 @@ import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -62,15 +63,18 @@ public class TileServices extends IQSService.Stub {
private final Handler mMainHandler;
private final QSTileHost mHost;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private int mMaxBound = DEFAULT_MAX_BOUND;
- public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher) {
+ public TileServices(QSTileHost host, Looper looper, BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mHost = host;
mContext = mHost.getContext();
mBroadcastDispatcher = broadcastDispatcher;
mHandler = new Handler(looper);
mMainHandler = new Handler(Looper.getMainLooper());
+ mUserTracker = userTracker;
mBroadcastDispatcher.registerReceiver(
mRequestListeningReceiver,
new IntentFilter(TileService.ACTION_REQUEST_LISTENING),
@@ -104,7 +108,7 @@ public class TileServices extends IQSService.Stub {
protected TileServiceManager onCreateTileService(ComponentName component, Tile tile,
BroadcastDispatcher broadcastDispatcher) {
return new TileServiceManager(this, mHandler, component, tile,
- broadcastDispatcher);
+ broadcastDispatcher, mUserTracker);
}
public void freeService(CustomTile tile, TileServiceManager service) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index dfd7e2c8fdb7..5a81676567a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -31,7 +31,6 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.annotation.CallSuper;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -521,9 +520,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
- userRestriction, ActivityManager.getCurrentUser());
+ userRestriction, mHost.getUserId());
if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
- userRestriction, ActivityManager.getCurrentUser())) {
+ userRestriction, mHost.getUserId())) {
state.disabledByPolicy = true;
mEnforcedAdmin = admin;
} 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 3495d3065fe9..becbfd57c14b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -52,6 +52,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -73,6 +74,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
private final Callback mCallback = new Callback();
private Dialog mDialog;
private boolean mWifiConnected;
+ private boolean mHotspotConnected;
private static final String WFD_ENABLE = "persist.debug.wfd.enable";
@Inject
@@ -86,7 +88,8 @@ public class CastTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
CastController castController,
KeyguardStateController keyguardStateController,
- NetworkController networkController
+ NetworkController networkController,
+ HotspotController hotspotController
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
@@ -97,6 +100,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
mController.observe(this, mCallback);
mKeyguard.observe(this, mCallback);
mNetworkController.observe(this, mSignalCallback);
+ hotspotController.observe(this, mHotspotCallback);
}
@Override
@@ -224,7 +228,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
}
state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected
: R.drawable.ic_cast);
- if (mWifiConnected || state.value) {
+ if (canCastToWifi() || state.value) {
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
if (!state.value) {
state.secondaryLabel = "";
@@ -260,6 +264,10 @@ public class CastTile extends QSTileImpl<BooleanState> {
: mContext.getString(R.string.quick_settings_cast_device_default_name);
}
+ private boolean canCastToWifi() {
+ return mWifiConnected || mHotspotConnected;
+ }
+
private final NetworkController.SignalCallback mSignalCallback =
new NetworkController.SignalCallback() {
@Override
@@ -277,6 +285,24 @@ public class CastTile extends QSTileImpl<BooleanState> {
boolean enabledAndConnected = enabled && qsIcon.visible;
if (enabledAndConnected != mWifiConnected) {
mWifiConnected = enabledAndConnected;
+ // Hotspot is not connected, so changes here should update
+ if (!mHotspotConnected) {
+ refreshState();
+ }
+ }
+ }
+ }
+ };
+
+ private final HotspotController.Callback mHotspotCallback =
+ new HotspotController.Callback() {
+ @Override
+ public void onHotspotChanged(boolean enabled, int numDevices) {
+ boolean enabledAndConnected = enabled && numDevices > 0;
+ if (enabledAndConnected != mHotspotConnected) {
+ mHotspotConnected = enabledAndConnected;
+ // Wifi is not connected, so changes here should update
+ if (!mWifiConnected) {
refreshState();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 347ef45824c9..98782f7c8b55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
@@ -61,13 +62,14 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
- QSLogger qsLogger
+ QSLogger qsLogger,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mSetting = new SecureSetting(mContext, mainHandler,
- Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
+ Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
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 ec8b1435e201..2076cbffa425 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,7 +19,6 @@ package com.android.systemui.qs.tiles;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
-import android.app.ActivityManager;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -203,7 +202,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
break;
default:
Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration,
- ActivityManager.getCurrentUser(), true).id;
+ mHost.getUserId(), true).id;
mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 201ed9c9ebec..8ddd4c9816cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -155,7 +155,7 @@ public class UserDetailView extends PseudoGridView {
}
view.setActivated(true);
}
- switchTo(tag);
+ onUserListItemClicked(tag);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 47002683c6b9..bbeff6ece902 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -16,30 +16,17 @@
package com.android.systemui.recents;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.trust.TrustManager;
import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
-import android.view.Display;
-import android.widget.Toast;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.wm.shell.splitscreen.SplitScreen;
import java.util.Optional;
@@ -56,7 +43,6 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
private final static String TAG = "OverviewProxyRecentsImpl";
@Nullable
private final Lazy<StatusBar> mStatusBarLazy;
- private final Optional<SplitScreen> mSplitScreenOptional;
private Context mContext;
private Handler mHandler;
@@ -65,10 +51,8 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
- public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,
- Optional<SplitScreen> splitScreenOptional) {
+ public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy) {
mStatusBarLazy = statusBarLazy.orElse(null);
- mSplitScreenOptional = splitScreenOptional;
}
@Override
@@ -140,42 +124,4 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
// Do nothing
}
}
-
- @Override
- public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- Point realSize = new Point();
- if (initialBounds == null) {
- mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
- .getRealSize(realSize);
- initialBounds = new Rect(0, 0, realSize.x, realSize.y);
- }
-
- ActivityManager.RunningTaskInfo runningTask =
- ActivityManagerWrapper.getInstance().getRunningTask();
- final int activityType = runningTask != null
- ? runningTask.configuration.windowConfiguration.getActivityType()
- : ACTIVITY_TYPE_UNDEFINED;
- boolean screenPinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
- boolean isRunningTaskInHomeOrRecentsStack =
- activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
- if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
- if (runningTask.supportsSplitScreenMultiWindow) {
- if (ActivityManagerWrapper.getInstance().setTaskWindowingModeSplitScreenPrimary(
- runningTask.id, stackCreateMode, initialBounds)) {
- mSplitScreenOptional.ifPresent(splitScreen -> {
- splitScreen.onDockedTopTask();
- // The overview service is handling split screen, so just skip the wait
- // for the first draw and notify the divider to start animating now
- splitScreen.onRecentsDrawn();
- });
- return true;
- }
- } else {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- }
- }
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 2a976f546ba4..5279a20a67a7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents;
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -36,12 +35,14 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_T
import android.annotation.FloatRange;
import android.app.ActivityTaskManager;
+import android.app.PictureInPictureParams;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -65,6 +66,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
@@ -75,8 +77,6 @@ import com.android.systemui.navigationbar.NavigationBar;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipAnimationController;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -93,6 +93,9 @@ import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -101,11 +104,13 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import javax.inject.Inject;
import dagger.Lazy;
+
/**
* Class to send information from overview to launcher with a binder.
*/
@@ -141,6 +146,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private Region mActiveNavBarRegion;
+ private IPinnedStackAnimationListener mIPinnedStackAnimationListener;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
@@ -155,14 +161,14 @@ public class OverviewProxyService extends CurrentUserTracker implements
private boolean mSupportsRoundedCornersOnWindows;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
- private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
-
+ @VisibleForTesting
+ public ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {
if (!verifyCaller("startScreenPinning")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(
@@ -179,7 +185,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("stopScreenPinning")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
@@ -199,7 +205,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onStatusBarMotionEvent")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
// TODO move this logic to message queue
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
@@ -230,26 +236,11 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public void onSplitScreenInvoked() {
- if (!verifyCaller("onSplitScreenInvoked")) {
- return;
- }
- long token = Binder.clearCallingIdentity();
- try {
- mSplitScreenOptional.ifPresent(splitScreen -> {
- splitScreen.onDockedFirstAnimationFrame();
- });
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- @Override
public void onOverviewShown(boolean fromHome) {
if (!verifyCaller("onOverviewShown")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
@@ -266,7 +257,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("getNonMinimizedSplitScreenSecondaryBounds")) {
return null;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
return mSplitScreenOptional.map(splitScreen ->
splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
@@ -281,7 +272,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("setNavBarButtonAlpha")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mNavBarButtonAlpha = alpha;
mHandler.post(() -> notifyNavBarButtonAlphaChanged(alpha, animate));
@@ -300,7 +291,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onAssistantProgress")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantProgress(progress));
} finally {
@@ -313,7 +304,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onAssistantGestureCompletion")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyAssistantGestureCompletion(velocity));
} finally {
@@ -326,7 +317,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("startAssistant")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyStartAssistant(bundle));
} finally {
@@ -339,7 +330,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("monitorGestureInput")) {
return null;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
final InputMonitor monitor =
InputManager.getInstance().monitorGestureInput(name, displayId);
@@ -357,7 +348,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("notifyAccessibilityButtonClicked")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
AccessibilityManager.getInstance(mContext)
.notifyAccessibilityButtonClicked(displayId);
@@ -371,7 +362,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("notifyAccessibilityButtonLongClicked")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
final Intent intent =
new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
@@ -391,7 +382,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
"ByPass setShelfHeight, FEATURE_PICTURE_IN_PICTURE:" + mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setShelfHeight(visible, shelfHeight));
@@ -419,7 +410,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
+ mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
pip -> pip.setPinnedStackAnimationType(
@@ -436,10 +427,11 @@ public class OverviewProxyService extends CurrentUserTracker implements
+ mHasPipFeature);
return;
}
- long token = Binder.clearCallingIdentity();
+ mIPinnedStackAnimationListener = listener;
+ final long token = Binder.clearCallingIdentity();
try {
mPipOptional.ifPresent(
- pip -> pip.setPinnedStackAnimationListener(listener));
+ pip -> pip.setPinnedStackAnimationListener(mPinnedStackAnimationCallback));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -450,7 +442,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("onQuickSwitchToNewTask")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> notifyQuickSwitchToNewTask(rotation));
} finally {
@@ -463,7 +455,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("startOneHandedMode")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.startOneHanded());
} finally {
@@ -476,7 +468,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("stopOneHandedMode")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.stopOneHanded(
OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_GESTURE_OUT));
@@ -505,7 +497,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
if (!verifyCaller("expandNotificationPanel")) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN);
} finally {
@@ -513,6 +505,38 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, int shelfHeight) {
+ if (!verifyCaller("startSwipePipToHome") || !mHasPipFeature) {
+ return null;
+ }
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ return mPipOptional.map(pip ->
+ pip.startSwipePipToHome(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, shelfHeight))
+ .orElse(null);
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
+ @Override
+ public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+ if (!verifyCaller("stopSwipePipToHome") || !mHasPipFeature) {
+ return;
+ }
+ final long binderToken = Binder.clearCallingIdentity();
+ try {
+ mPipOptional.ifPresent(pip -> pip.stopSwipePipToHome(
+ componentName, destinationBounds));
+ } finally {
+ Binder.restoreCallingIdentity(binderToken);
+ }
+ }
+
private boolean verifyCaller(String reason) {
final int callerId = Binder.getCallingUserHandle().getIdentifier();
if (callerId != mCurrentBoundedUserId) {
@@ -605,6 +629,8 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final StatusBarWindowCallback mStatusBarWindowCallback = this::onStatusBarStateChanged;
private final BiConsumer<Rect, Rect> mSplitScreenBoundsChangeListener =
this::notifySplitScreenBoundsChanged;
+ private final Consumer<Boolean> mPinnedStackAnimationCallback =
+ this::notifyPinnedStackAnimationStarted;
// This is the death handler for the binder from the launcher service
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
@@ -624,7 +650,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
super(broadcastDispatcher);
mContext = context;
mPipOptional = pipOptional;
- mHasPipFeature = mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
+ mHasPipFeature = PipUtils.hasSystemFeature(mContext);
mStatusBarOptionalLazy = statusBarOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
@@ -734,6 +760,17 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
+ private void notifyPinnedStackAnimationStarted(Boolean isAnimationStarted) {
+ if (mIPinnedStackAnimationListener == null) {
+ return;
+ }
+ try {
+ mIPinnedStackAnimationListener.onPinnedStackAnimationStarted();
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onPinnedStackAnimationStarted()", e);
+ }
+ }
+
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
boolean bouncerShowing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
@@ -764,7 +801,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
public void cleanupAfterDeath() {
if (mInputFocusTransferStarted) {
- mHandler.post(()-> {
+ mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
mInputFocusTransferStarted = false;
statusBarLazy.get().onInputFocusTransfer(false, true /* cancel */,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index df61fd19ad45..6f6dd9cb22c7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -19,7 +19,6 @@ package com.android.systemui.recents;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.provider.Settings;
import com.android.systemui.SystemUI;
@@ -120,17 +119,6 @@ public class Recents extends SystemUI implements CommandQueue.Callbacks {
mImpl.cancelPreloadRecentApps();
}
- public boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- // Ensure the device has been provisioned before allowing the user to interact with
- // recents
- if (!isUserSetup()) {
- return false;
- }
-
- return mImpl.splitPrimaryTask(stackCreateMode, initialBounds, metricsDockAction);
- }
-
/**
* @return whether this device is provisioned and the current user is set up.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
index a641730ac64e..8848dbbda5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplementation.java
@@ -17,7 +17,6 @@ package com.android.systemui.recents;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Rect;
import java.io.PrintWriter;
@@ -35,10 +34,6 @@ public interface RecentsImplementation {
default void showRecentApps(boolean triggeredFromAltTab) {}
default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {}
default void toggleRecentApps() {}
- default boolean splitPrimaryTask(int stackCreateMode, Rect initialBounds,
- int metricsDockAction) {
- return false;
- }
default void dump(PrintWriter pw) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index 9c5a3de4523a..ccf2598e4f18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -67,6 +67,7 @@ import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
import java.util.Collections;
@@ -365,7 +366,7 @@ public class RecentsOnboarding {
mOverviewProxyListenerRegistered = true;
}
if (!mTaskListenerRegistered) {
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskListener);
mTaskListenerRegistered = true;
}
}
@@ -377,7 +378,7 @@ public class RecentsOnboarding {
mOverviewProxyListenerRegistered = false;
}
if (mTaskListenerRegistered) {
- ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener);
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskListener);
mTaskListenerRegistered = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
index df03c3e08f08..0aa9d4d662a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenInternalAudioRecorder.java
@@ -48,6 +48,7 @@ public class ScreenInternalAudioRecorder {
private long mTotalBytes;
private MediaMuxer mMuxer;
private boolean mMic;
+ private boolean mStarted;
private int mTrackId = -1;
@@ -263,10 +264,14 @@ public class ScreenInternalAudioRecorder {
* start recording
* @throws IllegalStateException if recording fails to initialize
*/
- public void start() throws IllegalStateException {
- if (mThread != null) {
- Log.e(TAG, "a recording is being done in parallel or stop is not called");
+ public synchronized void start() throws IllegalStateException {
+ if (mStarted) {
+ if (mThread == null) {
+ throw new IllegalStateException("Recording stopped and can't restart (single use)");
+ }
+ throw new IllegalStateException("Recording already started");
}
+ mStarted = true;
mAudioRecord.startRecording();
if (mMic) mAudioRecordMic.startRecording();
Log.d(TAG, "channel count " + mAudioRecord.getChannelCount());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 2b4fa2a23a07..aaa335c25d5d 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.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
@@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
public Bitmap image;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
- public int errorMsgResId;
void clearImage() {
image = null;
@@ -184,6 +184,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private final WindowManager.LayoutParams mWindowLayoutParams;
private final Display mDisplay;
private final DisplayMetrics mDisplayMetrics;
+ private final AccessibilityManager mAccessibilityManager;
private View mScreenshotLayout;
private ScreenshotSelectorView mScreenshotSelectorView;
@@ -242,6 +243,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mUiEventLogger = uiEventLogger;
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
reloadAssets();
Configuration config = mContext.getResources().getConfiguration();
@@ -319,8 +321,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
Consumer<Uri> finisher, Runnable onComplete) {
// TODO: use task Id, userId, topComponent for smart handler
-
mOnCompleteRunnable = onComplete;
+
+ if (screenshot == null) {
+ Log.e(TAG, "Got null bitmap from screenshot message");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
} else {
@@ -567,12 +578,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
.build();
final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
SurfaceControl.captureDisplay(captureArgs);
- final Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+ Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+
+ if (screenshot == null) {
+ Log.e(TAG, "Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
}
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, boolean showFlash) {
+ if (mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event =
+ new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ event.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
if (mScreenshotLayout.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
@@ -583,14 +612,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenBitmap = screenshot;
- if (mScreenBitmap == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- finisher.accept(null);
- mOnCompleteRunnable.run();
- return;
- }
-
if (!isUserSetupComplete()) {
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
// and sharing shouldn't be exposed to the user.
@@ -632,7 +653,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (imageData.uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
+ R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
@@ -752,7 +773,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (imageData.uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
+ R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index df1d78953f46..f0ea597c458d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -217,13 +217,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(mImageData.uri);
mParams.image = null;
- mParams.errorMsgResId = 0;
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is
// not mounted
Slog.e(TAG, "unable to save screenshot", e);
mParams.clearImage();
- mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
mImageData.reset();
mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(null);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index 26d408fe4ab7..c7a8fa22d1af 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -40,6 +40,11 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
val userHandle: UserHandle
/**
+ * [UserInfo] for current user
+ */
+ val userInfo: UserInfo
+
+ /**
* List of profiles associated with the current user.
*/
val userProfiles: List<UserInfo>
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 4cc0eeee712c..049685f3dda4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -82,6 +82,12 @@ class UserTrackerImpl internal constructor(
override val userContentResolver: ContentResolver
get() = userContext.contentResolver
+ override val userInfo: UserInfo
+ get() {
+ val user = userId
+ return userProfiles.first { it.id == user }
+ }
+
/**
* Returns a [List<UserInfo>] of all profiles associated with the current user.
*
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index b9b4f42a66e1..6202057b9ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,9 +16,6 @@
package com.android.systemui.shortcut;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
-
import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
@@ -29,7 +26,6 @@ import android.view.WindowManagerGlobal;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.recents.Recents;
import com.android.wm.shell.splitscreen.DividerView;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -46,7 +42,6 @@ public class ShortcutKeyDispatcher extends SystemUI
private static final String TAG = "ShortcutKeyDispatcher";
private final Optional<SplitScreen> mSplitScreenOptional;
- private final Recents mRecents;
private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -60,11 +55,9 @@ public class ShortcutKeyDispatcher extends SystemUI
protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
@Inject
- public ShortcutKeyDispatcher(Context context,
- Optional<SplitScreen> splitScreenOptional, Recents recents) {
+ public ShortcutKeyDispatcher(Context context, Optional<SplitScreen> splitScreenOptional) {
super(context);
mSplitScreenOptional = splitScreenOptional;
- mRecents = recents;
}
/**
@@ -96,8 +89,7 @@ public class ShortcutKeyDispatcher extends SystemUI
}
private void handleDockKey(long shortcutCode) {
- if (mSplitScreenOptional.isPresent()) {
- SplitScreen splitScreen = mSplitScreenOptional.get();
+ mSplitScreenOptional.ifPresent(splitScreen -> {
if (splitScreen.isDividerVisible()) {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = splitScreen.getDividerView();
@@ -112,12 +104,9 @@ public class ShortcutKeyDispatcher extends SystemUI
dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
true /* logMetrics */);
return;
+ } else {
+ splitScreen.splitPrimaryTask();
}
- }
-
- // Split the screen
- mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 37ae791bf172..dcee9fa9e648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -58,12 +58,14 @@ import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.tracing.ProtoTracer;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
/**
* This class takes the functions from IStatusBar that come in on
@@ -138,6 +140,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
private static final int MSG_SUPPRESS_AMBIENT_DISPLAY = 56 << MSG_SHIFT;
private static final int MSG_REQUEST_WINDOW_MAGNIFICATION_CONNECTION = 57 << MSG_SHIFT;
private static final int MSG_HANDLE_WINDOW_MANAGER_LOGGING_COMMAND = 58 << MSG_SHIFT;
+ //TODO(b/169175022) Update name and when feature name is locked.
+ private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 59 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -159,6 +163,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
*/
private int mLastUpdatedImeDisplayId = INVALID_DISPLAY;
private ProtoTracer mProtoTracer;
+ private final @Nullable CommandRegistry mRegistry;
/**
* These methods are called back on the main thread.
@@ -255,6 +260,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAssistDisclosure() { }
default void startAssist(Bundle args) { }
default void onCameraLaunchGestureDetected(int source) { }
+
+ /**
+ * Notifies SysUI that the emergency action gesture was detected.
+ */
+ default void onEmergencyActionLaunchGestureDetected() { }
default void showPictureInPictureMenu() { }
default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
@@ -368,11 +378,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
public CommandQueue(Context context) {
- this(context, null);
+ this(context, null, null);
}
- public CommandQueue(Context context, ProtoTracer protoTracer) {
+ public CommandQueue(Context context, ProtoTracer protoTracer, CommandRegistry registry) {
mProtoTracer = protoTracer;
+ mRegistry = registry;
context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler);
// We always have default display.
setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE);
@@ -726,6 +737,14 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
@Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_EMERGENCY_ACTION_LAUNCH_GESTURE);
+ mHandler.obtainMessage(MSG_EMERGENCY_ACTION_LAUNCH_GESTURE).sendToTarget();
+ }
+ }
+
+ @Override
public void addQsTile(ComponentName tile) {
synchronized (mLock) {
mHandler.obtainMessage(MSG_ADD_QS_TILE, tile).sendToTarget();
@@ -1013,6 +1032,34 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
}
}
+ @Override
+ public void passThroughShellCommand(String[] args, ParcelFileDescriptor pfd) {
+ final FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
+ final PrintWriter pw = new PrintWriter(fos);
+ // This is mimicking Binder#dumpAsync, but on this side of the binder. Might be possible
+ // to just throw this work onto the handler just like the other messages
+ Thread thr = new Thread("Sysui.passThroughShellCommand") {
+ public void run() {
+ try {
+ if (mRegistry == null) {
+ return;
+ }
+
+ // Registry blocks this thread until finished
+ mRegistry.onShellCommand(pw, args);
+ } finally {
+ pw.flush();
+ try {
+ // Close the file descriptor so the TransferPipe finishes its thread
+ pfd.close();
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+ thr.start();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1154,6 +1201,10 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
mCallbacks.get(i).onCameraLaunchGestureDetected(msg.arg1);
}
break;
+ case MSG_EMERGENCY_ACTION_LAUNCH_GESTURE:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onEmergencyActionLaunchGestureDetected();
+ }
case MSG_SHOW_PICTURE_IN_PICTURE_MENU:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showPictureInPictureMenu();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
index ac3523b2fffd..1b1a51b8a57b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -36,10 +35,9 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,7 +49,7 @@ import java.util.List;
*/
public class MediaTransferManager {
private final Context mContext;
- private final ActivityStarter mActivityStarter;
+ private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private MediaDevice mDevice;
private List<View> mViews = new ArrayList<>();
private LocalMediaManager mLocalMediaManager;
@@ -74,12 +72,7 @@ public class MediaTransferManager {
ViewParent parent = view.getParent();
StatusBarNotification statusBarNotification =
getRowForParent(parent).getEntry().getSbn();
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- statusBarNotification.getPackageName());
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mMediaOutputDialogFactory.create(statusBarNotification.getPackageName(), true);
return true;
}
};
@@ -107,7 +100,7 @@ public class MediaTransferManager {
public MediaTransferManager(Context context) {
mContext = context;
- mActivityStarter = Dependency.get(ActivityStarter.class);
+ mMediaOutputDialogFactory = Dependency.get(MediaOutputDialogFactory.class);
LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class);
InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 8bf134d9c5b9..bb76ac0b26bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -40,6 +40,9 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
@@ -52,13 +55,18 @@ import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.media.MediaData;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
@@ -77,6 +85,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import dagger.Lazy;
@@ -106,6 +115,9 @@ public class NotificationMediaManager implements Dumpable {
private final NotificationEntryManager mEntryManager;
private final MediaDataManager mMediaDataManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final boolean mUsingNotifPipeline;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -189,6 +201,9 @@ public class NotificationMediaManager implements Dumpable {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfig,
MediaDataManager mediaDataManager) {
@@ -206,17 +221,87 @@ public class NotificationMediaManager implements Dumpable {
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
- notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
+ if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
+ setupNEM();
+ mUsingNotifPipeline = false;
+ } else {
+ setupNotifPipeline();
+ mUsingNotifPipeline = true;
+ }
+
+ mShowCompactMediaSeekbar = "true".equals(
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
+
+ deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mContext.getMainExecutor(),
+ mPropertiesChangedListener);
+ }
+
+ private void setupNotifPipeline() {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(@NonNull NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ }
+
+ @Override
+ public void onEntryBind(NotificationEntry entry, StatusBarNotification sbn) {
+ findAndUpdateMediaNotifications();
+ }
+
+ @Override
+ public void onEntryRemoved(@NonNull NotificationEntry entry, int reason) {
+ removeEntry(entry);
+ }
+
+ @Override
+ public void onEntryCleanUp(@NonNull NotificationEntry entry) {
+ removeEntry(entry);
+ }
+ });
+
+ mMediaDataManager.addListener(new MediaDataManager.Listener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key,
+ @Nullable String oldKey, @NonNull MediaData data) {
+ }
+
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ mNotifPipeline.getAllNotifs()
+ .stream()
+ .filter(entry -> Objects.equals(entry.getKey(), key))
+ .findAny()
+ .ifPresent(entry -> {
+ // TODO(b/160713608): "removing" this notification won't happen and
+ // won't send the 'deleteIntent' if the notification is ongoing.
+ mNotifCollection.dismissNotification(entry,
+ getDismissedByUserStats(entry));
+ });
+ }
+ });
+ }
+
+ private void setupNEM() {
+ mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
- mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
public void onPreEntryUpdated(NotificationEntry entry) {
- mediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
+ mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
}
@Override
@@ -231,8 +316,8 @@ public class NotificationMediaManager implements Dumpable {
@Override
public void onEntryRemoved(
- NotificationEntry entry,
- NotificationVisibility visibility,
+ @NonNull NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
boolean removedByUser,
int reason) {
removeEntry(entry);
@@ -242,20 +327,49 @@ public class NotificationMediaManager implements Dumpable {
// Pending entries are never inflated, and will never generate a call to onEntryRemoved().
// This can happen when notifications are added and canceled before inflation. Add this
// separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
- notificationEntryManager.addCollectionListener(new NotifCollectionListener() {
+ mEntryManager.addCollectionListener(new NotifCollectionListener() {
@Override
public void onEntryCleanUp(@NonNull NotificationEntry entry) {
removeEntry(entry);
}
});
- mShowCompactMediaSeekbar = "true".equals(
- DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.COMPACT_MEDIA_SEEKBAR_ENABLED));
+ mMediaDataManager.addListener(new MediaDataManager.Listener() {
+ @Override
+ public void onMediaDataLoaded(@NonNull String key,
+ @Nullable String oldKey, @NonNull MediaData data) {
+ }
- deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mContext.getMainExecutor(),
- mPropertiesChangedListener);
+ @Override
+ public void onMediaDataRemoved(@NonNull String key) {
+ NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
+ if (entry != null) {
+ // TODO(b/160713608): "removing" this notification won't happen and
+ // won't send the 'deleteIntent' if the notification is ongoing.
+ mEntryManager.performRemoveNotification(entry.getSbn(),
+ getDismissedByUserStats(entry),
+ NotificationListenerService.REASON_CANCEL);
+ }
+ }
+ });
+ }
+
+ private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
+ final int activeNotificationsCount;
+ if (mUsingNotifPipeline) {
+ activeNotificationsCount = mNotifPipeline.getShadeListCount();
+ } else {
+ activeNotificationsCount = mEntryManager.getActiveNotificationsCount();
+ }
+ return new DismissedByUserStats(
+ NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
+ NotificationStats.DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ activeNotificationsCount,
+ /* visible= */ true,
+ NotificationLogger.getNotificationLocation(entry)));
}
private void removeEntry(NotificationEntry entry) {
@@ -299,14 +413,24 @@ public class NotificationMediaManager implements Dumpable {
if (mMediaNotificationKey == null) {
return null;
}
- synchronized (mEntryManager) {
- NotificationEntry entry = mEntryManager
+ if (mUsingNotifPipeline) {
+ // TODO(b/169655596): Either add O(1) lookup, or cache this icon?
+ return mNotifPipeline.getAllNotifs().stream()
+ .filter(entry -> Objects.equals(entry.getKey(), mMediaNotificationKey))
+ .findAny()
+ .map(entry -> entry.getIcons().getShelfIcon())
+ .map(StatusBarIconView::getSourceIcon)
+ .orElse(null);
+ } else {
+ synchronized (mEntryManager) {
+ NotificationEntry entry = mEntryManager
.getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry == null || entry.getIcons().getShelfIcon() == null) {
- return null;
- }
+ if (entry == null || entry.getIcons().getShelfIcon() == null) {
+ return null;
+ }
- return entry.getIcons().getShelfIcon().getSourceIcon();
+ return entry.getIcons().getShelfIcon().getSourceIcon();
+ }
}
}
@@ -321,94 +445,110 @@ public class NotificationMediaManager implements Dumpable {
}
public void findAndUpdateMediaNotifications() {
+ boolean metaDataChanged;
+ if (mUsingNotifPipeline) {
+ // TODO(b/169655907): get the semi-filtered notifications for current user
+ Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
+ } else {
+ synchronized (mEntryManager) {
+ Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
+ }
+
+ if (metaDataChanged) {
+ mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
+ }
+
+ }
+ dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+ }
+
+ /**
+ * Find a notification and media controller associated with the playing media session, and
+ * update this manager's internal state.
+ * @return whether the current MediaMetadata changed (and needs to be announced to listeners).
+ */
+ private boolean findPlayingMediaNotification(
+ @NonNull Collection<NotificationEntry> allNotifications) {
boolean metaDataChanged = false;
+ // Promote the media notification with a controller in 'playing' state, if any.
+ NotificationEntry mediaNotification = null;
+ MediaController controller = null;
+ for (NotificationEntry entry : allNotifications) {
+ if (entry.isMediaNotification()) {
+ final MediaSession.Token token =
+ entry.getSbn().getNotification().extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION);
+ if (token != null) {
+ MediaController aController = new MediaController(mContext, token);
+ if (PlaybackState.STATE_PLAYING
+ == getMediaControllerPlaybackState(aController)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ + entry.getSbn().getKey());
+ }
+ mediaNotification = entry;
+ controller = aController;
+ break;
+ }
+ }
+ }
+ }
+ if (mediaNotification == null) {
+ // Still nothing? OK, let's just look for live media sessions and see if they match
+ // one of our notifications. This will catch apps that aren't (yet!) using media
+ // notifications.
+
+ if (mMediaSessionManager != null) {
+ // TODO: Should this really be for all users? It appears that inactive users
+ // can't have active sessions, which would mean it is fine.
+ final List<MediaController> sessions =
+ mMediaSessionManager.getActiveSessionsForUser(null, UserHandle.USER_ALL);
- synchronized (mEntryManager) {
- Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
-
- // Promote the media notification with a controller in 'playing' state, if any.
- NotificationEntry mediaNotification = null;
- MediaController controller = null;
- for (NotificationEntry entry : allNotifications) {
- if (entry.isMediaNotification()) {
- final MediaSession.Token token =
- entry.getSbn().getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
+ for (MediaController aController : sessions) {
+ // now to see if we have one like this
+ final String pkg = aController.getPackageName();
+
+ for (NotificationEntry entry : allNotifications) {
+ if (entry.getSbn().getPackageName().equals(pkg)) {
if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+ entry.getSbn().getKey());
}
- mediaNotification = entry;
controller = aController;
+ mediaNotification = entry;
break;
}
}
}
}
- if (mediaNotification == null) {
- // Still nothing? OK, let's just look for live media sessions and see if they match
- // one of our notifications. This will catch apps that aren't (yet!) using media
- // notifications.
-
- if (mMediaSessionManager != null) {
- // TODO: Should this really be for all users?
- final List<MediaController> sessions
- = mMediaSessionManager.getActiveSessionsForUser(
- null,
- UserHandle.USER_ALL);
-
- for (MediaController aController : sessions) {
- // now to see if we have one like this
- final String pkg = aController.getPackageName();
-
- for (NotificationEntry entry : allNotifications) {
- if (entry.getSbn().getPackageName().equals(pkg)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.getSbn().getKey());
- }
- controller = aController;
- mediaNotification = entry;
- break;
- }
- }
- }
- }
- }
-
- if (controller != null && !sameSessions(mMediaController, controller)) {
- // We have a new media session
- clearCurrentMediaNotificationSession();
- mMediaController = controller;
- mMediaController.registerCallback(mMediaListener);
- mMediaMetadata = mMediaController.getMetadata();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
- + mMediaController + ", receive metadata: " + mMediaMetadata);
- }
+ }
- metaDataChanged = true;
+ if (controller != null && !sameSessions(mMediaController, controller)) {
+ // We have a new media session
+ clearCurrentMediaNotificationSession();
+ mMediaController = controller;
+ mMediaController.registerCallback(mMediaListener);
+ mMediaMetadata = mMediaController.getMetadata();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
+ + mMediaController + ", receive metadata: " + mMediaMetadata);
}
- if (mediaNotification != null
- && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) {
- mMediaNotificationKey = mediaNotification.getSbn().getKey();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
- + mMediaNotificationKey);
- }
- }
+ metaDataChanged = true;
}
- if (metaDataChanged) {
- mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
+ if (mediaNotification != null
+ && !mediaNotification.getSbn().getKey().equals(mMediaNotificationKey)) {
+ mMediaNotificationKey = mediaNotification.getSbn().getKey();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ + mMediaNotificationKey);
+ }
}
- dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+ return metaDataChanged;
}
public void clearCurrentMediaNotification() {
@@ -428,7 +568,7 @@ public class NotificationMediaManager implements Dumpable {
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.print(" mMediaSessionManager=");
pw.println(mMediaSessionManager);
pw.print(" mMediaNotificationKey=");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index c1196d65b702..01aa53f14550 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,7 +189,11 @@ class NotificationShadeDepthController @Inject constructor(
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
val zoomOut = blurUtils.ratioOfBlurRadius(blur)
try {
- wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ if (root.isAttachedToWindow) {
+ wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
+ } else {
+ Log.i(TAG, "Won't set zoom. Window not attached $root")
+ }
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Can't set zoom. Window is gone: ${root.windowToken}", e)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 38c7e5c50f63..53179ba4be90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -26,7 +26,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -47,6 +47,7 @@ import com.android.systemui.util.Assert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Optional;
import java.util.Stack;
/**
@@ -84,7 +85,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
* possible.
*/
private final boolean mAlwaysExpandNonGroupedNotification;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final DynamicPrivacyController mDynamicPrivacyController;
private final KeyguardBypassController mBypassController;
private final ForegroundServiceSectionController mFgsSectionController;
@@ -112,7 +113,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
KeyguardBypassController bypassController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController,
@@ -130,7 +131,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mDynamicPrivacyController = privacyController;
mDynamicChildBindController = dynamicChildBindController;
mLowPriorityInflationHelper = lowPriorityInflationHelper;
@@ -157,8 +158,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
+ final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(ent);
if (ent.isRowDismissed() || ent.isRowRemoved()
- || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)
+ || isBubbleNotificationSuppressedFromShade
|| mFgsSectionController.hasEntry(ent)) {
// we don't want to update removed notifications because they could
// temporarily become children if they were isolated before.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
new file mode 100644
index 000000000000..ce0a08cd4ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.statusbar.commandline
+
+import android.content.Context
+
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+
+import java.io.PrintWriter
+import java.lang.IllegalStateException
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+
+import javax.inject.Inject
+
+/**
+ * Registry / dispatcher for incoming shell commands. See [StatusBarManagerService] and
+ * [StatusBarShellCommand] for how things are set up. Commands come in here by way of the service
+ * like so:
+ *
+ * `adb shell cmd statusbar <command>`
+ *
+ * Where `cmd statusbar` send the shell command through to StatusBarManagerService, and
+ * <command> is either processed in system server, or sent through to IStatusBar (CommandQueue)
+ */
+@SysUISingleton
+class CommandRegistry @Inject constructor(
+ val context: Context,
+ @Main val mainExecutor: Executor
+) {
+ // To keep the command line parser hermetic, create a new one for every shell command
+ private val commandMap = mutableMapOf<String, CommandWrapper>()
+ private var initialized = false
+
+ /**
+ * Register a [Command] for a given name. The name here is the top-level namespace for
+ * the registered command. A command could look like this for instance:
+ *
+ * `adb shell cmd statusbar notifications list`
+ *
+ * Where `notifications` is the command that signifies which receiver to send the remaining args
+ * to.
+ *
+ * @param command String name of the command to register. Currently does not support aliases
+ * @param receiverFactory Creates an instance of the receiver on every command
+ * @param executor Pass an executor to offload your `receive` to another thread
+ */
+ @Synchronized
+ fun registerCommand(
+ name: String,
+ commandFactory: () -> Command,
+ executor: Executor
+ ) {
+ if (commandMap[name] != null) {
+ throw IllegalStateException("A command is already registered for ($name)")
+ }
+ commandMap[name] = CommandWrapper(commandFactory, executor)
+ }
+
+ /**
+ * Register a [Command] for a given name, to be executed on the main thread.
+ */
+ @Synchronized
+ fun registerCommand(name: String, commandFactory: () -> Command) {
+ registerCommand(name, commandFactory, mainExecutor)
+ }
+
+ /** Unregister a receiver */
+ @Synchronized
+ fun unregisterCommand(command: String) {
+ commandMap.remove(command)
+ }
+
+ private fun initializeCommands() {
+ initialized = true
+ // TODO: Might want a dedicated place for commands without a home. Currently
+ // this is here because Prefs.java is just an interface
+ registerCommand("prefs") { PrefsCommand(context) }
+ }
+
+ /**
+ * Receive a shell command and dispatch to the appropriate [Command]. Blocks until finished.
+ */
+ fun onShellCommand(pw: PrintWriter, args: Array<String>) {
+ if (!initialized) initializeCommands()
+
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val commandName = args[0]
+ val wrapper = commandMap[commandName]
+
+ if (wrapper == null) {
+ help(pw)
+ return
+ }
+
+ // Create a new instance of the command
+ val command = wrapper.commandFactory()
+
+ // Wrap the receive command in a task so that we can wait for its completion
+ val task = FutureTask<Unit> {
+ command.execute(pw, args.drop(1))
+ }
+
+ wrapper.executor.execute {
+ task.run()
+ }
+
+ // Wait for the future to complete
+ task.get()
+ }
+
+ private fun help(pw: PrintWriter) {
+ pw.println("Usage: adb shell cmd statusbar <command>")
+ pw.println(" known commands:")
+ for (k in commandMap.keys) {
+ pw.println(" $k")
+ }
+ }
+}
+
+private const val TAG = "CommandRegistry"
+
+interface Command {
+ fun execute(pw: PrintWriter, args: List<String>)
+ fun help(pw: PrintWriter)
+}
+
+// Wrap commands in an executor package
+private data class CommandWrapper(val commandFactory: () -> Command, val executor: Executor)
+
+// Commands can go here for now, but they should move outside
+
+private class PrefsCommand(val context: Context) : Command {
+ override fun help(pw: PrintWriter) {
+ pw.println("usage: prefs <command> [args]")
+ pw.println("Available commands:")
+ pw.println(" list-prefs")
+ pw.println(" set-pref <pref name> <value>")
+ }
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
+ val topLevel = args[0]
+
+ when (topLevel) {
+ "list-prefs" -> listPrefs(pw)
+ "set-pref" -> setPref(pw, args.drop(1))
+ else -> help(pw)
+ }
+ }
+
+ private fun listPrefs(pw: PrintWriter) {
+ pw.println("Available keys:")
+ for (field in Prefs.Key::class.java.declaredFields) {
+ pw.print(" ")
+ pw.println(field.get(Prefs.Key::class.java))
+ }
+ }
+
+ /**
+ * Sets a preference from [Prefs]
+ */
+ private fun setPref(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ pw.println("invalid arguments: $args")
+ return
+ }
+ val pref = args[0]
+
+ when (pref) {
+ Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING -> {
+ val value = Integer.parseInt(args[1])
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, value != 0)
+ }
+ else -> {
+ pw.println("Cannot set pref ($pref)")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index d15b8476b3c5..cee9c70f53eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,13 +21,14 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.MediaArtworkProcessor;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationListener;
@@ -39,10 +40,13 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -59,6 +63,8 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import java.util.Optional;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -108,6 +114,9 @@ public interface StatusBarDependenciesModule {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ FeatureFlags featureFlags,
@Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfigProxy,
MediaDataManager mediaDataManager) {
@@ -118,6 +127,9 @@ public interface StatusBarDependenciesModule {
notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
+ notifPipeline,
+ notifCollection,
+ featureFlags,
mainExecutor,
deviceConfigProxy,
mediaDataManager);
@@ -162,7 +174,7 @@ public interface StatusBarDependenciesModule {
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
KeyguardBypassController bypassController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController,
@@ -177,7 +189,7 @@ public interface StatusBarDependenciesModule {
statusBarStateController,
notificationEntryManager,
bypassController,
- bubbleController,
+ bubblesOptional,
privacyController,
fgsSectionController,
dynamicChildBindController,
@@ -190,8 +202,11 @@ public interface StatusBarDependenciesModule {
*/
@Provides
@SysUISingleton
- static CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) {
- return new CommandQueue(context, protoTracer);
+ static CommandQueue provideCommandQueue(
+ Context context,
+ ProtoTracer protoTracer,
+ CommandRegistry registry) {
+ return new CommandQueue(context, protoTracer, registry);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 433c8b0d361d..ddfa18e65ee0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -27,10 +27,10 @@ import com.android.internal.widget.ConversationLayout
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@@ -85,38 +85,43 @@ class ConversationNotificationManager @Inject constructor(
for (entry in activeConversationEntries) {
if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
val important = ranking.channel.isImportantConversation
- val layouts = entry.row?.layouts?.asSequence()
+ var changed = false
+ entry.row?.layouts?.asSequence()
?.flatMap(::getLayouts)
?.mapNotNull { it as? ConversationLayout }
- ?: emptySequence()
- var changed = false
- for (layout in layouts) {
- if (important == layout.isImportantConversation) {
- continue
- }
- changed = true
- if (important && entry.isMarkedForUserTriggeredMovement) {
- // delay this so that it doesn't animate in until after
- // the notif has been moved in the shade
- mainHandler.postDelayed({
- layout.setIsImportantConversation(
- important, true /* animate */)
- }, IMPORTANCE_ANIMATION_DELAY.toLong())
- } else {
- layout.setIsImportantConversation(important)
- }
- }
+ ?.filterNot { it.isImportantConversation == important }
+ ?.forEach { layout ->
+ changed = true
+ if (important && entry.isMarkedForUserTriggeredMovement) {
+ // delay this so that it doesn't animate in until after
+ // the notif has been moved in the shade
+ mainHandler.postDelayed(
+ {
+ layout.setIsImportantConversation(
+ important,
+ true)
+ },
+ IMPORTANCE_ANIMATION_DELAY.toLong())
+ } else {
+ layout.setIsImportantConversation(important, false)
+ }
+ }
if (changed) {
notificationGroupManager.updateIsolation(entry)
+ // ensure that the conversation icon isn't hidden
+ // (ex: if it was showing in the shelf)
+ entry.row?.updateIconVisibilities()
}
}
}
}
override fun onEntryInflated(entry: NotificationEntry) {
- if (!entry.ranking.isConversation) return
+ if (!entry.ranking.isConversation) {
+ return
+ }
fun updateCount(isExpanded: Boolean) {
- if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded())) {
+ if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded)) {
resetCount(entry.key)
entry.row?.let(::resetBadgeUi)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index d364689a65d4..7d8979ca1129 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -22,7 +22,7 @@ import android.util.Log;
import android.view.View;
import com.android.systemui.DejankUtils;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -38,19 +38,19 @@ import javax.inject.Inject;
public final class NotificationClicker implements View.OnClickListener {
private static final String TAG = "NotificationClicker";
- private final BubbleController mBubbleController;
private final NotificationClickerLogger mLogger;
- private final Optional<StatusBar> mStatusBar;
+ private final Optional<StatusBar> mStatusBarOptional;
+ private final Optional<Bubbles> mBubblesOptional;
private final NotificationActivityStarter mNotificationActivityStarter;
private NotificationClicker(
- BubbleController bubbleController,
NotificationClickerLogger logger,
- Optional<StatusBar> statusBar,
+ Optional<StatusBar> statusBarOptional,
+ Optional<Bubbles> bubblesOptional,
NotificationActivityStarter notificationActivityStarter) {
- mBubbleController = bubbleController;
mLogger = logger;
- mStatusBar = statusBar;
+ mStatusBarOptional = statusBarOptional;
+ mBubblesOptional = bubblesOptional;
mNotificationActivityStarter = notificationActivityStarter;
}
@@ -61,7 +61,7 @@ public final class NotificationClicker implements View.OnClickListener {
return;
}
- mStatusBar.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
+ mStatusBarOptional.ifPresent(statusBar -> statusBar.wakeUpIfDozing(
SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -92,8 +92,8 @@ public final class NotificationClicker implements View.OnClickListener {
row.setJustClicked(true);
DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
- if (!row.getEntry().isBubble()) {
- mBubbleController.collapseStack();
+ if (!row.getEntry().isBubble() && mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().collapseStack();
}
mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
@@ -118,26 +118,23 @@ public final class NotificationClicker implements View.OnClickListener {
/** Daggerized builder for NotificationClicker. */
public static class Builder {
- private final BubbleController mBubbleController;
private final NotificationClickerLogger mLogger;
@Inject
- public Builder(
- BubbleController bubbleController,
- NotificationClickerLogger logger) {
- mBubbleController = bubbleController;
+ public Builder(NotificationClickerLogger logger) {
mLogger = logger;
}
/** Builds an instance. */
public NotificationClicker build(
- Optional<StatusBar> statusBar,
+ Optional<StatusBar> statusBarOptional,
+ Optional<Bubbles> bubblesOptional,
NotificationActivityStarter notificationActivityStarter
) {
return new NotificationClicker(
- mBubbleController,
mLogger,
- statusBar,
+ statusBarOptional,
+ bubblesOptional,
notificationActivityStarter);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index fdfd72489e93..d617dff372da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -291,6 +291,7 @@ public class NotifCollection implements Dumpable {
mLogger.logDismissAll(userId);
try {
+ // TODO(b/169585328): Do not clear media player notifications
mStatusBarService.onClearAllNotifications(userId);
} catch (RemoteException e) {
// system process is dead if we're here.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 4ddc1dc8498d..0455b0f18afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@@ -54,7 +56,7 @@ import javax.inject.Inject;
public class BubbleCoordinator implements Coordinator {
private static final String TAG = "BubbleCoordinator";
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final NotifCollection mNotifCollection;
private final Set<String> mInterceptedDismissalEntries = new HashSet<>();
private NotifPipeline mNotifPipeline;
@@ -62,9 +64,9 @@ public class BubbleCoordinator implements Coordinator {
@Inject
public BubbleCoordinator(
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
NotifCollection notifCollection) {
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mNotifCollection = notifCollection;
}
@@ -73,13 +75,17 @@ public class BubbleCoordinator implements Coordinator {
mNotifPipeline = pipeline;
mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
mNotifPipeline.addFinalizeFilter(mNotifFilter);
- mBubbleController.addNotifCallback(mNotifCallback);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().addNotifCallback(mNotifCallback);
+ }
+
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- return mBubbleController.isBubbleNotificationSuppressedFromShade(entry);
+ return mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(entry);
}
};
@@ -97,7 +103,8 @@ public class BubbleCoordinator implements Coordinator {
@Override
public boolean shouldInterceptDismissal(NotificationEntry entry) {
// for experimental bubbles
- if (mBubbleController.handleDismissalInterception(entry)) {
+ if (mBubblesOptional.isPresent()
+ && mBubblesOptional.get().handleDismissalInterception(entry)) {
mInterceptedDismissalEntries.add(entry.getKey());
return true;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 21d54c85160b..490989dbb39e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -21,9 +21,8 @@ import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -43,6 +42,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import javax.inject.Inject;
@@ -64,25 +64,20 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
new ArraySet<>();
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
+ private final Optional<Lazy<Bubbles>> mBubblesOptional;
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
private boolean mIsUpdatingUnchangedGroup;
- @Nullable private BubbleController mBubbleController = null;
@Inject
public NotificationGroupManagerLegacy(
StatusBarStateController statusBarStateController,
- Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier) {
+ Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
+ Optional<Lazy<Bubbles>> bubblesOptional) {
statusBarStateController.addCallback(this);
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
- }
-
- private BubbleController getBubbleController() {
- if (mBubbleController == null) {
- mBubbleController = Dependency.get(BubbleController.class);
- }
- return mBubbleController;
+ mBubblesOptional = bubblesOptional;
}
/**
@@ -247,7 +242,8 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
- if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) {
+ if (mBubblesOptional.isPresent() && !mBubblesOptional.get().get()
+ .isBubbleNotificationSuppressedFromShade(entry)) {
childCount++;
} else {
hasBubbles = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 498b8e884b17..1311e3e756dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.notification.collection.render
import android.annotation.StringRes
+import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
-import com.android.systemui.statusbar.notification.dagger.HeaderClick
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.notification.dagger.HeaderClickAction
import com.android.systemui.statusbar.notification.dagger.HeaderText
import com.android.systemui.statusbar.notification.dagger.NodeLabel
import com.android.systemui.statusbar.notification.dagger.SectionHeaderScope
@@ -39,11 +41,19 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor(
@NodeLabel override val nodeLabel: String,
private val layoutInflater: LayoutInflater,
@HeaderText @StringRes private val headerTextResId: Int,
- @HeaderClick private val onHeaderClickListener: View.OnClickListener
+ private val activityStarter: ActivityStarter,
+ @HeaderClickAction private val clickIntentAction: String
) : NodeController, SectionHeaderController {
private var _view: SectionHeaderView? = null
private var clearAllClickListener: View.OnClickListener? = null
+ private val onHeaderClickListener = View.OnClickListener {
+ activityStarter.startActivity(
+ Intent(clickIntentAction),
+ true /* onlyProvisioned */,
+ true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
override fun reinflateView(parent: ViewGroup) {
var oldPos = -1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 22ca4961320c..7babbb40b6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -172,14 +172,19 @@ class ShadeViewDiffer(
private fun treeToMap(tree: NodeSpec): Map<NodeController, NodeSpec> {
val map = mutableMapOf<NodeController, NodeSpec>()
- registerNodes(tree, map)
+ try {
+ registerNodes(tree, map)
+ } catch (ex: DuplicateNodeException) {
+ logger.logDuplicateNodeInTree(tree, ex)
+ throw ex
+ }
return map
}
private fun registerNodes(node: NodeSpec, map: MutableMap<NodeController, NodeSpec>) {
if (map.containsKey(node.controller)) {
- throw RuntimeException("Node ${node.controller.nodeLabel} appears more than once")
+ throw DuplicateNodeException("Node ${node.controller.nodeLabel} appears more than once")
}
map[node.controller] = node
@@ -191,6 +196,8 @@ class ShadeViewDiffer(
}
}
+private class DuplicateNodeException(message: String) : RuntimeException(message)
+
private class ShadeNode(
val controller: NodeController
) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 19e156f572d4..d27455004c01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import java.lang.RuntimeException
import javax.inject.Inject
class ShadeViewDifferLogger @Inject constructor(
@@ -67,6 +68,15 @@ class ShadeViewDifferLogger @Inject constructor(
"Moving child view $str1 in $str2 to index $int1"
})
}
+
+ fun logDuplicateNodeInTree(node: NodeSpec, ex: RuntimeException) {
+ buffer.log(TAG, LogLevel.ERROR, {
+ str1 = ex.toString()
+ str2 = treeSpecToStr(node)
+ }, {
+ "$str1 when mapping tree: $str2"
+ })
+ }
}
private const val TAG = "NotifViewManager" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 179d49cb55a1..2a9cfd034dce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.dagger
import android.annotation.StringRes
-import android.content.Intent
import android.provider.Settings
-import android.view.View
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderNodeControllerImpl
@@ -39,18 +36,6 @@ import javax.inject.Scope
object NotificationSectionHeadersModule {
@Provides
- @HeaderClick
- @JvmStatic fun providesOnHeaderClickListener(
- activityStarter: ActivityStarter
- ) = View.OnClickListener {
- activityStarter.startActivity(
- Intent(Settings.ACTION_NOTIFICATION_SETTINGS),
- true /* onlyProvisioned */,
- true /* dismissShade */,
- Intent.FLAG_ACTIVITY_SINGLE_TOP)
- }
-
- @Provides
@IncomingHeader
@SysUISingleton
@JvmStatic fun providesIncomingHeaderSubcomponent(
@@ -58,6 +43,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("incoming header")
.headerText(R.string.notification_section_header_incoming)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -68,6 +54,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("alerting header")
.headerText(R.string.notification_section_header_alerting)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -78,6 +65,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("people header")
.headerText(R.string.notification_section_header_conversations)
+ .clickIntentAction(Settings.ACTION_CONVERSATION_SETTINGS)
.build()
@Provides
@@ -88,6 +76,7 @@ object NotificationSectionHeadersModule {
) = builder.get()
.nodeLabel("silent header")
.headerText(R.string.notification_section_header_gentle)
+ .clickIntentAction(Settings.ACTION_NOTIFICATION_SETTINGS)
.build()
@Provides
@@ -151,6 +140,7 @@ interface SectionHeaderControllerSubcomponent {
fun build(): SectionHeaderControllerSubcomponent
@BindsInstance fun nodeLabel(@NodeLabel nodeLabel: String): Builder
@BindsInstance fun headerText(@HeaderText @StringRes headerText: Int): Builder
+ @BindsInstance fun clickIntentAction(@HeaderClickAction clickIntentAction: String): Builder
}
}
@@ -188,7 +178,7 @@ annotation class NodeLabel
@Qualifier
@Retention(AnnotationRetention.BINARY)
-annotation class HeaderClick
+annotation class HeaderClickAction
@Scope
@Retention(AnnotationRetention.BINARY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 01333f0a47d5..4fff99b482d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -132,7 +133,7 @@ public interface NotificationsModule {
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
return new NotificationGutsManager(
@@ -149,7 +150,7 @@ public interface NotificationsModule {
contextTracker,
builderProvider,
assistantFeedbackController,
- bubbleController,
+ bubblesOptional,
uiEventLogger,
onUserInteractionCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 9da8b8a3fd92..049b471aa7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.NotificationActivityStarter
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.phone.StatusBar
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.util.Optional
/**
* The master controller for all notifications-related work
@@ -35,6 +37,7 @@ import java.io.PrintWriter
interface NotificationsController {
fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 9fb292878553..45a5d1044b9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
@@ -75,6 +76,7 @@ class NotificationsControllerImpl @Inject constructor(
override fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
@@ -90,7 +92,8 @@ class NotificationsControllerImpl @Inject constructor(
listController.bind()
notificationRowBinder.setNotificationClicker(
- clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
+ clickerBuilder.build(
+ Optional.of(statusBar), bubblesOptional, notificationActivityStarter))
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index ded855dd84f9..7569c1bdbb73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
+import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
@@ -26,6 +27,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.phone.StatusBar
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.util.Optional
import javax.inject.Inject
/**
@@ -37,6 +39,7 @@ class NotificationsControllerStub @Inject constructor(
override fun initialize(
statusBar: StatusBar,
+ bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
notificationActivityStarter: NotificationActivityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index f1727ec91c72..094e8661d262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -124,6 +124,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private float mAppearAnimationTranslation;
private int mNormalColor;
private boolean mIsBelowSpeedBump;
+ private long mLastActionUpTime;
private float mNormalBackgroundVisibilityAmount;
private float mDimmedBackgroundFadeInAmount = -1;
@@ -225,6 +226,22 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
return super.onInterceptTouchEvent(ev);
}
+ /** Sets the last action up time this view was touched. */
+ void setLastActionUpTime(long eventTime) {
+ mLastActionUpTime = eventTime;
+ }
+
+ /**
+ * Returns the last action up time. The last time will also be cleared because the source of
+ * action is not only from touch event. That prevents the caller from utilizing the time with
+ * unrelated event. The time can be 0 if the event is unavailable.
+ */
+ public long getAndResetLastActionUpTime() {
+ long lastActionUpTime = mLastActionUpTime;
+ mLastActionUpTime = 0;
+ return lastActionUpTime;
+ }
+
protected boolean disallowSingleClick(MotionEvent ev) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index dd30c890e75b..41ce51c2762f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -92,6 +93,9 @@ public class ActivatableNotificationViewController {
mBlockNextTouch = false;
return true;
}
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ mView.setLastActionUpTime(SystemClock.uptimeMillis());
+ }
if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled()
&& mView.isInteractive()) {
if (mNeedsDimming && !mView.isDimmed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 811a72de093c..d8d412bf2d41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -72,7 +72,7 @@ import com.android.internal.widget.CachingIconView;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -1074,7 +1074,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return new View.OnClickListener() {
@Override
public void onClick(View v) {
- Dependency.get(BubbleController.class)
+ Dependency.get(Bubbles.class)
.onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
}
@@ -1478,8 +1478,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- private void updateIconVisibilities() {
- // The shelficon is never hidden for children in groups
+ /** Refreshes the visibility of notification icons */
+ public void updateIconVisibilities() {
+ // The shelf icon is never hidden for children in groups
boolean visible = !isChildInGroup() && mShelfIconVisible;
for (NotificationContentView l : mLayouts) {
l.setShelfIconVisible(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 1d72557c6a89..c995e324ecfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -81,21 +81,27 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
- public ExpandableNotificationRowController(ExpandableNotificationRow view,
+ public ExpandableNotificationRowController(
+ ExpandableNotificationRow view,
NotificationListContainer listContainer,
ActivatableNotificationViewController activatableNotificationViewController,
- NotificationMediaManager mediaManager, PluginManager pluginManager,
- SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
+ NotificationMediaManager mediaManager,
+ PluginManager pluginManager,
+ SystemClock clock,
+ @AppName String appName,
+ @NotificationKey String notificationKey,
KeyguardBypassController keyguardBypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
RowContentBindStage rowContentBindStage,
- NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
+ NotificationLogger notificationLogger,
+ HeadsUpManager headsUpManager,
ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
StatusBarStateController statusBarStateController,
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager,
+ OnUserInteractionCallback onUserInteractionCallback,
+ FalsingManager falsingManager,
PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
mListContainer = listContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 9bcac1163acc..c2c4590fa6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,17 +42,15 @@ import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import com.android.systemui.util.Assert;
import java.util.HashMap;
@@ -60,8 +58,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
@@ -76,27 +72,24 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mIsMediaInQS;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
- private final Lazy<SmartReplyConstants> mSmartReplyConstants;
- private final Lazy<SmartReplyController> mSmartReplyController;
private final ConversationNotificationProcessor mConversationProcessor;
private final Executor mBgExecutor;
+ private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater;
@Inject
NotificationContentInflater(
NotifRemoteViewCache remoteViewCache,
NotificationRemoteInputManager remoteInputManager,
- Lazy<SmartReplyConstants> smartReplyConstants,
- Lazy<SmartReplyController> smartReplyController,
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
+ mSmartRepliesAndActionsInflater = smartRepliesInflater;
}
@Override
@@ -132,8 +125,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
contentToBind,
mRemoteViewCache,
entry,
- mSmartReplyConstants.get(),
- mSmartReplyController.get(),
mConversationProcessor,
row,
bindParams.isLowPriority,
@@ -141,7 +132,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeadsUpHeight,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
- mIsMediaInQS);
+ mIsMediaInQS,
+ mSmartRepliesAndActionsInflater);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -157,17 +149,19 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
Notification.Builder builder,
- Context packageContext) {
+ Context packageContext,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
bindParams.isLowPriority,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
packageContext);
+
result = inflateSmartReplyViews(result, reInflateFlags, entry,
- row.getContext(), packageContext, row.getHeadsUpManager(),
- mSmartReplyConstants.get(), mSmartReplyController.get(),
- row.getExistingSmartRepliesAndActions());
+ row.getContext(), packageContext,
+ row.getExistingSmartRepliesAndActions(),
+ smartRepliesInflater);
apply(
mBgExecutor,
@@ -268,22 +262,21 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
}
- private static InflationProgress inflateSmartReplyViews(InflationProgress result,
- @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
- Context packageContext, HeadsUpManager headsUpManager,
- SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController,
- SmartRepliesAndActions previousSmartRepliesAndActions) {
+ private static InflationProgress inflateSmartReplyViews(
+ InflationProgress result,
+ @InflationFlag int reInflateFlags,
+ NotificationEntry entry,
+ Context context,
+ Context packageContext,
+ SmartRepliesAndActions previousSmartRepliesAndActions,
+ SmartRepliesAndActionsInflater inflater) {
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
- result.expandedInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.expandedInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
- result.headsUpInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.headsUpInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
return result;
}
@@ -709,8 +702,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
- private final SmartReplyConstants mSmartReplyConstants;
- private final SmartReplyController mSmartReplyController;
private final Executor mBgExecutor;
private ExpandableNotificationRow mRow;
private Exception mError;
@@ -718,6 +709,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private CancellationSignal mCancellationSignal;
private final ConversationNotificationProcessor mConversationProcessor;
private final boolean mIsMediaInQS;
+ private final SmartRepliesAndActionsInflater mSmartRepliesInflater;
private AsyncInflationTask(
Executor bgExecutor,
@@ -725,8 +717,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@InflationFlag int reInflateFlags,
NotifRemoteViewCache cache,
NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
ConversationNotificationProcessor conversationProcessor,
ExpandableNotificationRow row,
boolean isLowPriority,
@@ -734,15 +724,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler,
- boolean isMediaFlagEnabled) {
+ boolean isMediaFlagEnabled,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mEntry = entry;
mRow = row;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mBgExecutor = bgExecutor;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
mRemoteViewCache = cache;
+ mSmartRepliesInflater = smartRepliesInflater;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mUsesIncreasedHeight = usesIncreasedHeight;
@@ -786,10 +776,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
- mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
- mSmartReplyConstants, mSmartReplyController,
- mRow.getExistingSmartRepliesAndActions());
+ SmartRepliesAndActions repliesAndActions =
+ mRow.getExistingSmartRepliesAndActions();
+ return inflateSmartReplyViews(
+ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ repliesAndActions,
+ mSmartRepliesInflater);
} catch (Exception e) {
mError = e;
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 1de9308a40b1..8a644ed4d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.RemoteInputView;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyView;
@@ -1191,23 +1192,31 @@ public class NotificationContentView extends FrameLayout {
View bigContentView = mExpandedChild;
if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView, entry);
}
View smallContentView = mContractedChild;
if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView, entry);
}
}
+ /**
+ * Returns whether the {@link Notification} represented by entry has a free-form remote input.
+ * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
+ * through the remote input.
+ */
+ public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return null != notification.findRemoteInputActionPair(true /* freeform */);
+ }
+
private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
if (mRemoteInputController == null) {
return;
}
- applyRemoteInput(entry, InflatedSmartReplies.hasFreeformRemoteInput(entry));
+ applyRemoteInput(entry, hasFreeformRemoteInput(entry));
if (mExpandedInflatedSmartReplies == null && mHeadsUpInflatedSmartReplies == null) {
if (DEBUG) {
@@ -1438,7 +1447,8 @@ public class NotificationContentView extends FrameLayout {
}
LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
- if (!InflatedSmartReplies.shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
+ if (!SmartRepliesAndActionsInflaterKt
+ .shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
smartReplyContainer.setVisibility(View.GONE);
return null;
}
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 b19997d15664..07a4a188bc48 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
@@ -67,7 +67,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
@@ -75,6 +75,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.lang.annotation.Retention;
+import java.util.Optional;
import javax.inject.Provider;
@@ -93,7 +94,7 @@ public class NotificationConversationInfo extends LinearLayout implements
private OnUserInteractionCallback mOnUserInteractionCallback;
private Handler mMainHandler;
private Handler mBgHandler;
- private BubbleController mBubbleController;
+ private Optional<Bubbles> mBubblesOptional;
private String mPackageName;
private String mAppName;
private int mAppUid;
@@ -222,7 +223,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Main Handler mainHandler,
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
- BubbleController bubbleController) {
+ Optional<Bubbles> bubblesOptional) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -241,7 +242,7 @@ public class NotificationConversationInfo extends LinearLayout implements
mIconFactory = conversationIconFactory;
mUserContext = userContext;
mBubbleMetadata = bubbleMetadata;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mBuilderProvider = builderProvider;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
@@ -640,9 +641,11 @@ public class NotificationConversationInfo extends LinearLayout implements
mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
BUBBLE_PREFERENCE_SELECTED);
}
- post(() -> {
- mBubbleController.onUserChangedImportance(mEntry);
- });
+ if (mBubblesOptional.isPresent()) {
+ post(() -> {
+ mBubblesOptional.get().onUserChangedImportance(mEntry);
+ });
+ }
}
mChannelToUpdate.setImportance(Math.max(
mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
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 7d418f30e4c5..373f20e6ba96 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
@@ -47,7 +47,7 @@ import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -70,6 +70,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Optional;
import javax.inject.Provider;
@@ -116,7 +117,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final Lazy<StatusBar> mStatusBarLazy;
private final Handler mMainHandler;
private final Handler mBgHandler;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
private final LauncherApps mLauncherApps;
@@ -141,7 +142,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
mContext = context;
@@ -157,7 +158,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mBuilderProvider = builderProvider;
mChannelEditorDialogController = channelEditorDialogController;
mAssistantFeedbackController = assistantFeedbackController;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
}
@@ -490,7 +491,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mMainHandler,
mBgHandler,
onConversationSettingsListener,
- mBubbleController);
+ mBubblesOptional);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index fe70c818216e..17f326b69848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -147,6 +147,8 @@ class NotificationConversationTemplateViewWrapper constructor(
// hiding the conversationIcon will already do that via its listener.
return
}
+ } else {
+ conversationIconView.isForceHidden = false
}
super.setShelfIconVisible(visible)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 500de2d29d03..93204995c5b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
@@ -73,6 +74,7 @@ import android.widget.ScrollView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardSliceView;
import com.android.settingslib.Utils;
@@ -97,7 +99,6 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -260,6 +261,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mDismissAllInProgress;
private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
+ private boolean mFlingAfterUpEvent;
/**
* Was the scroller scrolled to the top when the down motion was observed?
@@ -3789,6 +3791,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
float currentOverScrollTop = getCurrentOverScrollAmount(true);
if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
+ mFlingAfterUpEvent = true;
+ setFinishScrollingCallback(() -> {
+ mFlingAfterUpEvent = false;
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ setFinishScrollingCallback(null);
+ });
fling(-initialVelocity);
} else {
onOverScrollFling(false, initialVelocity);
@@ -3840,6 +3849,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return true;
}
+ boolean isFlingAfterUpEvent() {
+ return mFlingAfterUpEvent;
+ }
+
@ShadeViewRefactor(RefactorComponent.INPUT)
protected boolean isInsideQsContainer(MotionEvent ev) {
return ev.getY() < mQsContainer.getBottom();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 703c214ed3ac..7698133e1521 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack;
import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
@@ -47,6 +48,7 @@ import android.view.WindowInsets;
import android.widget.FrameLayout;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -1301,7 +1303,8 @@ public class NotificationStackScrollLayoutController {
mView.clearNotifications(ROWS_GENTLE, closeShade);
}
- private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, int selectedRows) {
+ private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
+ @SelectedRows int selectedRows) {
if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
@@ -1334,6 +1337,7 @@ public class NotificationStackScrollLayoutController {
}
if (selectedRows == ROWS_ALL) {
try {
+ // TODO(b/169585328): Do not clear media player notifications
mIStatusBarService.onClearAllNotifications(
mLockscreenUserManager.getCurrentUserId());
} catch (Exception ignored) {
@@ -1556,6 +1560,14 @@ public class NotificationStackScrollLayoutController {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mView.setCheckForLeaveBehind(true);
}
+
+ // When swiping directly on the NSSL, this would only get an onTouchEvent.
+ // We log any touches other than down, which will be captured by onTouchEvent.
+ // In the intercept we only start tracing when it's not a down (otherwise that down
+ // would be duplicated when intercepted).
+ if (scrollWantsIt && ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
return swipeWantsIt || scrollWantsIt || expandWantsIt;
}
@@ -1611,7 +1623,32 @@ public class NotificationStackScrollLayoutController {
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mView.setCheckForLeaveBehind(true);
}
+ traceJankOnTouchEvent(ev.getActionMasked(), scrollerWantsIt);
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt;
}
+
+ private void traceJankOnTouchEvent(int action, boolean scrollerWantsIt) {
+ // Handle interaction jank monitor cases.
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ if (scrollerWantsIt) {
+ InteractionJankMonitor.getInstance()
+ .begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (scrollerWantsIt && !mView.isFlingAfterUpEvent()) {
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (scrollerWantsIt) {
+ InteractionJankMonitor.getInstance()
+ .cancel(CUJ_NOTIFICATION_SHADE_SCROLL_FLING);
+ }
+ break;
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 95edfe37fee7..d7a8202d7a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,7 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
@@ -687,15 +688,27 @@ public class StackScrollAlgorithm {
AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
float childrenOnTop = 0.0f;
+
+ int topHunIndex = -1;
+ for (int i = 0; i < childCount; i++) {
+ ExpandableView child = algorithmState.visibleChildren.get(i);
+ if (child instanceof ActivatableNotificationView
+ && (child.isAboveShelf() || child.showingPulsing())) {
+ topHunIndex = i;
+ break;
+ }
+ }
+
for (int i = childCount - 1; i >= 0; i--) {
childrenOnTop = updateChildZValue(i, childrenOnTop,
- algorithmState, ambientState);
+ algorithmState, ambientState, i == topHunIndex);
}
}
protected float updateChildZValue(int i, float childrenOnTop,
StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState,
+ boolean shouldElevateHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
@@ -713,8 +726,7 @@ public class StackScrollAlgorithm {
}
childViewState.zTranslation = baseZ
+ childrenOnTop * zDistanceBetweenElements;
- } else if (child == ambientState.getTrackedHeadsUpRow()
- || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) {
+ } else if (shouldElevateHun) {
// In case this is a new view that has never been measured before, we don't want to
// elevate if we are currently expanded more then the notification
int shelfHeight = ambientState.getShelf() == null ? 0 :
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index efd6767e66a7..76c5baf6e9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -169,7 +169,8 @@ public class AutoTileManager implements UserAwareController {
String setting = split[0];
String spec = split[1];
// Populate all the settings. As they may not have been added in other users
- AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec);
+ AutoAddSetting s = new AutoAddSetting(
+ mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec);
mAutoAddSettingList.add(s);
} else {
Log.w(TAG, "Malformed item in array: " + tile);
@@ -319,8 +320,14 @@ public class AutoTileManager implements UserAwareController {
private class AutoAddSetting extends SecureSetting {
private final String mSpec;
- AutoAddSetting(Context context, Handler handler, String setting, String tileSpec) {
- super(context, handler, setting);
+ AutoAddSetting(
+ Context context,
+ Handler handler,
+ String setting,
+ int userId,
+ String tileSpec
+ ) {
+ super(context, handler, setting, userId);
mSpec = tileSpec;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index b2c1900bf1dc..780e54d56821 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -458,6 +458,7 @@ public final class DozeServiceHost implements DozeHost {
return;
}
mSuppressed = suppressed;
+ mDozeLog.traceDozingSuppressed(mSuppressed);
for (Callback callback : mCallbacks) {
callback.onDozeSuppressedChanged(suppressed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index d7c176207f5e..bf1118253f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -86,8 +86,6 @@ import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
import com.android.systemui.tuner.TunerService;
-import java.util.concurrent.Executor;
-
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
@@ -565,7 +563,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
if (!mKeyguardStateController.canDismissLockScreen()) {
- Dependency.get(Executor.class).execute(runnable);
+ Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
} else {
boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
&& Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a3f14ba28dcb..1fdf631a858d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.util.MathUtils;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -122,6 +123,8 @@ public class KeyguardClockPositionAlgorithm {
*/
private int mUnlockedStackScrollerPadding;
+ private int mLockScreenMode;
+
/**
* Refreshes the dimension values.
*/
@@ -171,6 +174,13 @@ public class KeyguardClockPositionAlgorithm {
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
}
+ /**
+ * Update lock screen mode for testing different layouts
+ */
+ public void onLockScreenModeChanged(int mode) {
+ mLockScreenMode = mode;
+ }
+
public float getMinStackScrollerPadding() {
return mBypassEnabled ? mUnlockedStackScrollerPadding
: mMinTopMargin + mKeyguardStatusHeight + mClockNotificationsMargin;
@@ -185,6 +195,9 @@ public class KeyguardClockPositionAlgorithm {
}
private int getExpandedPreferredClockY() {
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ return mMinTopMargin;
+ }
return (mHasCustomClock && (!mHasVisibleNotifs || mBypassEnabled)) ? getPreferredClockY()
: getExpandedClockPosition();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 3d51854a348c..54fb863b5de7 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -168,7 +168,7 @@ public class LockIcon extends KeyguardAffordanceView {
int iconRes = isAnim ? getThemedAnimationResId(lockAnimIndex) : getIconForState(newState);
if (!mDrawableCache.contains(iconRes)) {
- mDrawableCache.put(iconRes, getResources().getDrawable(iconRes));
+ mDrawableCache.put(iconRes, getContext().getDrawable(iconRes));
}
return mDrawableCache.get(iconRes);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index bda35fb0a48e..d1c83555c062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -19,7 +19,7 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Function;
import javax.inject.Inject;
@@ -65,7 +66,7 @@ public class NotificationIconAreaController implements
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final KeyguardBypassController mBypassController;
private final DozeParameters mDozeParameters;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final StatusBarWindowController mStatusBarWindowController;
private int mIconSize;
@@ -114,7 +115,7 @@ public class NotificationIconAreaController implements
NotificationMediaManager notificationMediaManager,
NotificationListener notificationListener,
DozeParameters dozeParameters,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
DemoModeController demoModeController,
DarkIconDispatcher darkIconDispatcher,
StatusBarWindowController statusBarWindowController) {
@@ -127,7 +128,7 @@ public class NotificationIconAreaController implements
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mDemoModeController = demoModeController;
mDemoModeController.addCallback(this);
mStatusBarWindowController = statusBarWindowController;
@@ -298,7 +299,7 @@ public class NotificationIconAreaController implements
|| !entry.isPulseSuppressed())) {
return false;
}
- if (mBubbleController.isBubbleExpanded(entry)) {
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isBubbleExpanded(entry)) {
return false;
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index cd9cc0775c66..86d4ac1cb443 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
import static android.view.View.GONE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
@@ -62,6 +64,7 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
@@ -218,6 +221,11 @@ public class NotificationPanelViewController extends PanelViewController {
new KeyguardUpdateMonitorCallback() {
@Override
+ public void onLockScreenModeChanged(int mode) {
+ mClockPositionAlgorithm.onLockScreenModeChanged(mode);
+ }
+
+ @Override
public void onBiometricAuthenticated(int userId,
BiometricSourceType biometricSourceType,
boolean isStrongBiometric) {
@@ -1139,6 +1147,7 @@ public class NotificationPanelViewController extends PanelViewController {
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mNotificationStackScrollLayoutController.cancelLongPress();
}
break;
@@ -1170,6 +1179,7 @@ public class NotificationPanelViewController extends PanelViewController {
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
mView.getParent().requestDisallowInterceptTouchEvent(true);
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
onQsExpansionStarted();
notifyExpandingFinished();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -1202,6 +1212,19 @@ public class NotificationPanelViewController extends PanelViewController {
&& x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth();
}
+ private void traceQsJank(boolean startTracing, boolean wasCancelled) {
+ InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+ if (startTracing) {
+ monitor.begin(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ if (wasCancelled) {
+ monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ } else {
+ monitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
+ }
+ }
+ }
+
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mOnlyAffordanceInThisMotion = false;
@@ -1315,9 +1338,9 @@ public class NotificationPanelViewController extends PanelViewController {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
&& mBarState != KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
-
// Down in the empty area while fully expanded - go to QS.
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mConflictingQsExpansionGesture = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -1405,6 +1428,7 @@ public class NotificationPanelViewController extends PanelViewController {
return;
}
mExpectingSynthesizedDown = true;
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
onTrackingStarted();
updatePanelExpanded();
}
@@ -1474,6 +1498,7 @@ public class NotificationPanelViewController extends PanelViewController {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mQsTracking = true;
+ traceQsJank(true /* startTracing */, false /* wasCancelled */);
mInitialTouchY = y;
mInitialTouchX = x;
onQsExpansionStarted();
@@ -1513,6 +1538,9 @@ public class NotificationPanelViewController extends PanelViewController {
if (fraction != 0f || y >= mInitialTouchY) {
flingQsWithCurrentVelocity(y,
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ } else {
+ traceQsJank(false /* startTracing */,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
}
if (mQsVelocityTracker != null) {
mQsVelocityTracker.recycle();
@@ -1893,7 +1921,7 @@ public class NotificationPanelViewController extends PanelViewController {
* @see #flingSettings(float, int, Runnable, boolean)
*/
public void flingSettings(float vel, int type) {
- flingSettings(vel, type, null, false /* isClick */);
+ flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
/**
@@ -1923,6 +1951,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
+ traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */);
return;
}
@@ -1947,12 +1976,18 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansion((Float) animation.getAnimatedValue());
});
animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mIsCanceled;
@Override
public void onAnimationStart(Animator animation) {
notifyExpandingStarted();
}
@Override
+ public void onAnimationCancel(Animator animation) {
+ mIsCanceled = true;
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
mAnimatingQS = false;
notifyExpandingFinished();
@@ -1961,6 +1996,7 @@ public class NotificationPanelViewController extends PanelViewController {
if (onFinishRunnable != null) {
onFinishRunnable.run();
}
+ traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */);
}
});
// Let's note that we're animating QS. Moving the animator here will cancel it immediately,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
index bc80a1a5137d..a4fc3a39c706 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
@@ -49,6 +49,7 @@ import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.widget.FrameLayout;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.FloatingToolbar;
import com.android.systemui.R;
@@ -145,6 +146,7 @@ public class NotificationShadeWindowView extends FrameLayout {
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setWillNotDraw(!DEBUG);
+ InteractionJankMonitor.getInstance().init(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 271650fabc4b..06fd6564854b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
@@ -40,6 +41,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.DejankUtils;
@@ -110,6 +112,7 @@ public abstract class PanelViewController {
private boolean mMotionAborted;
private boolean mUpwardsWhenThresholdReached;
private boolean mAnimatingOnDown;
+ private boolean mHandlingPointerUp;
private ValueAnimator mHeightAnimator;
private ObjectAnimator mPeekAnimator;
@@ -364,6 +367,9 @@ public abstract class PanelViewController {
protected void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
+ if (!mHandlingPointerUp) {
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
mInitialOffsetOnTouch = expandedHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
@@ -579,6 +585,7 @@ public abstract class PanelViewController {
target = getMaxPanelHeight() - getClearAllHeightWithPadding();
}
if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
+ InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
notifyExpandingFinished();
return;
}
@@ -640,7 +647,12 @@ public abstract class PanelViewController {
}
setAnimator(null);
if (!mCancelled) {
+ InteractionJankMonitor.getInstance()
+ .end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
notifyExpandingFinished();
+ } else {
+ InteractionJankMonitor.getInstance()
+ .cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
}
notifyBarPanelExpansionChanged();
}
@@ -1290,7 +1302,9 @@ public abstract class PanelViewController {
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
+ mHandlingPointerUp = true;
startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ mHandlingPointerUp = false;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -1348,6 +1362,12 @@ public abstract class PanelViewController {
case MotionEvent.ACTION_CANCEL:
addMovement(event);
endMotionEvent(event, x, y, false /* forceCancel */);
+ InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ } else {
+ monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+ }
break;
}
return !mGestureWaitForTouchSlop || mTracking;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e95cf2806691..4af27877c201 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -34,6 +34,8 @@ import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
@@ -139,6 +141,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private ScrimView mScrimInFront;
private ScrimView mScrimBehind;
+ @Nullable
private ScrimView mScrimForBubble;
private Runnable mScrimBehindChangeRunnable;
@@ -238,7 +241,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
* Attach the controller to the supplied views.
*/
public void attachViews(
- ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble) {
+ ScrimView scrimBehind, ScrimView scrimInFront, @Nullable ScrimView scrimForBubble) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mScrimForBubble = scrimForBubble;
@@ -258,7 +261,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mScrimBehind.setDefaultFocusHighlightEnabled(false);
mScrimInFront.setDefaultFocusHighlightEnabled(false);
- mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+ if (mScrimForBubble != null) {
+ mScrimForBubble.setDefaultFocusHighlightEnabled(false);
+ }
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
}
@@ -455,7 +460,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
}
- private void setOrAdaptCurrentAnimation(View scrim) {
+ private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
+ if (scrim == null) {
+ return;
+ }
+
float alpha = getCurrentScrimAlpha(scrim);
if (isAnimating(scrim)) {
// Adapt current animation.
@@ -606,11 +615,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
// Only animate scrim color if the scrim view is actually visible
boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
- boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
mScrimInFront.setColors(mColors, animateScrimInFront);
mScrimBehind.setColors(mColors, animateScrimBehind);
- mScrimForBubble.setColors(mColors, animateScrimForBubble);
// Calculate minimum scrim opacity for white or black text.
int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -632,7 +639,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
}
setScrimAlpha(mScrimInFront, mInFrontAlpha);
setScrimAlpha(mScrimBehind, mBehindAlpha);
- setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+
+ if (mScrimForBubble != null) {
+ boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+ mScrimForBubble.setColors(mColors, animateScrimForBubble);
+ setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+ }
// The animation could have all already finished, let's call onFinished just in case
onFinished();
dispatchScrimsVisible();
@@ -828,12 +840,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mBubbleTint = Color.TRANSPARENT;
updateScrimColor(mScrimInFront, mInFrontAlpha, mInFrontTint);
updateScrimColor(mScrimBehind, mBehindAlpha, mBehindTint);
- updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, mBubbleAlpha, mBubbleTint);
+ }
}
}
- private boolean isAnimating(View scrim) {
- return scrim.getTag(TAG_KEY_ANIM) != null;
+ private boolean isAnimating(@Nullable View scrim) {
+ return scrim != null && scrim.getTag(TAG_KEY_ANIM) != null;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 2db36f4a62f8..fc91c16f1a48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone;
import android.graphics.Color;
import android.os.Trace;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -212,7 +214,9 @@ public enum ScrimState {
// Set all scrims black, before they fade transparent.
updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
- updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ }
// Scrims should still be black at the end of the transition.
mFrontTint = Color.BLACK;
@@ -258,7 +262,7 @@ public enum ScrimState {
float mDefaultScrimAlpha;
ScrimView mScrimInFront;
ScrimView mScrimBehind;
- ScrimView mScrimForBubble;
+ @Nullable ScrimView mScrimForBubble;
DozeParameters mDozeParameters;
DockManager mDockManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 1ce22194878f..af2f3e55c9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -22,7 +22,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -31,6 +31,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import java.util.ArrayList;
+import java.util.Optional;
import javax.inject.Inject;
@@ -50,7 +51,7 @@ public class ShadeControllerImpl implements ShadeController {
private final int mDisplayId;
protected final Lazy<StatusBar> mStatusBarLazy;
private final Lazy<AssistManager> mAssistManagerLazy;
- private final Lazy<BubbleController> mBubbleControllerLazy;
+ private final Optional<Lazy<Bubbles>> mBubblesOptional;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
@@ -63,7 +64,7 @@ public class ShadeControllerImpl implements ShadeController {
WindowManager windowManager,
Lazy<StatusBar> statusBarLazy,
Lazy<AssistManager> assistManagerLazy,
- Lazy<BubbleController> bubbleControllerLazy
+ Optional<Lazy<Bubbles>> bubblesOptional
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
@@ -73,7 +74,7 @@ public class ShadeControllerImpl implements ShadeController {
// TODO: Remove circular reference to StatusBar when possible.
mStatusBarLazy = statusBarLazy;
mAssistManagerLazy = assistManagerLazy;
- mBubbleControllerLazy = bubbleControllerLazy;
+ mBubblesOptional = bubblesOptional;
}
@Override
@@ -133,8 +134,8 @@ public class ShadeControllerImpl implements ShadeController {
getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
- } else {
- mBubbleControllerLazy.get().collapseStack();
+ } else if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().get().collapseStack();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8d5e05f8a894..28a22cd6a194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.WindowType;
@@ -37,7 +35,6 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASL
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
-import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_LEFT;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -72,6 +69,7 @@ import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.PointF;
@@ -147,6 +145,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -154,6 +153,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -173,7 +173,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -649,7 +648,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
private ActivityIntentHelper mActivityIntentHelper;
@@ -698,7 +697,7 @@ public class StatusBar extends SystemUI implements DemoMode,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
@@ -717,7 +716,6 @@ public class StatusBar extends SystemUI implements DemoMode,
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Optional<Recents> recentsOptional,
Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
PluginManager pluginManager,
Optional<SplitScreen> splitScreenOptional,
@@ -778,7 +776,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mVibratorHelper = vibratorHelper;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mNavigationBarController = navigationBarController;
@@ -798,7 +796,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
mVolumeComponent = volumeComponent;
mCommandQueue = commandQueue;
- mRecentsOptional = recentsOptional;
mStatusBarComponentBuilder = statusBarComponentBuilder;
mPluginManager = pluginManager;
mSplitScreenOptional = splitScreenOptional;
@@ -824,7 +821,7 @@ public class StatusBar extends SystemUI implements DemoMode,
updateScrimController();
};
-
+ mActivityIntentHelper = new ActivityIntentHelper(mContext);
DateTimeView.setReceiverHandler(timeTickHandler);
}
@@ -834,8 +831,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mBypassHeadsUpNotifier.setUp();
- mBubbleController.setExpandListener(mBubbleExpandListener);
- mActivityIntentHelper = new ActivityIntentHelper(mContext);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().setExpandListener(mBubbleExpandListener);
+ }
mColorExtractor.addOnColorsChangedListener(this);
mStatusBarStateController.addCallback(this,
@@ -1145,7 +1143,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
- ScrimView scrimForBubble = mBubbleController.getScrimForBubble();
+ ScrimView scrimForBubble = mBubblesOptional.isPresent()
+ ? mBubblesOptional.get().getScrimForBubble() : null;
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1341,6 +1340,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationsController.initialize(
this,
+ mBubblesOptional,
mPresenter,
mStackScrollerController.getNotificationListContainer(),
mNotificationActivityStarter,
@@ -1542,35 +1542,37 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public boolean toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
- if (!mRecentsOptional.isPresent()) {
+ if (!mSplitScreenOptional.isPresent()) {
return false;
}
- if (mSplitScreenOptional.isPresent()) {
- SplitScreen splitScreen = mSplitScreenOptional.get();
- if (splitScreen.isDividerVisible()) {
- if (splitScreen.isMinimized()
- && !splitScreen.isHomeStackResizable()) {
- // Undocking from the minimized state is not supported
- return false;
- } else {
- splitScreen.onUndockingTask();
- if (metricsUndockAction != -1) {
- mMetricsLogger.action(metricsUndockAction);
- }
- }
- return true;
+ final SplitScreen splitScreen = mSplitScreenOptional.get();
+ if (splitScreen.isDividerVisible()) {
+ if (splitScreen.isMinimized() && !splitScreen.isHomeStackResizable()) {
+ // Undocking from the minimized state is not supported
+ return false;
+ }
+
+ splitScreen.onUndockingTask();
+ if (metricsUndockAction != -1) {
+ mMetricsLogger.action(metricsUndockAction);
}
+ return true;
}
final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
if (navbarPos == NAV_BAR_POS_INVALID) {
return false;
}
- int createMode = navbarPos == NAV_BAR_POS_LEFT
- ? SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT
- : SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
- return mRecentsOptional.get().splitPrimaryTask(createMode, null, metricsDockAction);
+
+ if (splitScreen.splitPrimaryTask()) {
+ if (metricsDockAction != -1) {
+ mMetricsLogger.action(metricsDockAction);
+ }
+ return true;
+ }
+
+ return false;
}
/**
@@ -2491,10 +2493,12 @@ public class StatusBar extends SystemUI implements DemoMode,
/** Temporarily hides Bubbles if the status bar is hidden. */
private void updateBubblesVisibility() {
- mBubbleController.onStatusBarVisibilityChanged(
- mStatusBarMode != MODE_LIGHTS_OUT
- && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
- && !mStatusBarWindowHidden);
+ if (mBubblesOptional.isPresent()) {
+ mBubblesOptional.get().onStatusBarVisibilityChanged(
+ mStatusBarMode != MODE_LIGHTS_OUT
+ && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
+ && !mStatusBarWindowHidden);
+ }
}
void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2817,8 +2821,8 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mRemoteInputManager.getController() != null) {
mRemoteInputManager.getController().closeRemoteInputs();
}
- if (mBubbleController.isStackExpanded()) {
- mBubbleController.collapseStack();
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
+ mBubblesOptional.get().collapseStack();
}
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
@@ -2833,9 +2837,9 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mNotificationShadeWindowController != null) {
mNotificationShadeWindowController.setNotTouchable(false);
}
- if (mBubbleController.isStackExpanded()) {
+ if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
// Post to main thread handler, since updating the UI.
- mMainThreadHandler.post(() -> mBubbleController.collapseStack());
+ mMainThreadHandler.post(() -> mBubblesOptional.get().collapseStack());
}
finishBarAnimations();
resetUserExpandedStates();
@@ -3535,8 +3539,6 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
- } else {
- mBubbleController.performBackPressIfNeeded();
}
return true;
}
@@ -3979,6 +3981,27 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ @Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ // TODO (b/169793384) Polish the panic gesture to be just like its older brother, camera.
+ Intent emergencyIntent = new Intent(EmergencyGesture.ACTION_LAUNCH_EMERGENCY);
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
+ if (resolveInfo == null) {
+ Log.wtf(TAG, "Couldn't find an app to process the emergency intent.");
+ return;
+ }
+
+ if (mVibrator != null && mVibrator.hasVibrator()) {
+ mVibrator.vibrate(500L);
+ }
+
+ emergencyIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name));
+ emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(emergencyIntent, /*dismissShade=*/true);
+ }
+
boolean isCameraAllowedByAdmin() {
if (mDevicePolicyManager.getCameraDisabled(null,
mLockscreenUserManager.getCurrentUserId())) {
@@ -4055,7 +4078,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- } else if (mBubbleController.isStackExpanded()) {
+ } else if (mBubblesOptional.isPresent() && mBubblesOptional.get().isStackExpanded()) {
mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED, mUnlockScrimCallback);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
@@ -4114,8 +4137,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected Display mDisplay;
private int mDisplayId;
- private final Optional<Recents> mRecentsOptional;
-
protected NotificationShelfController mNotificationShelfController;
private final Lazy<AssistManager> mAssistManagerLazy;
@@ -4283,6 +4304,19 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) {
+ return getDefaultActivityOptions(animationAdapter).toBundle();
+ }
+
+ public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter,
+ boolean isKeyguardShowing, long eventTime) {
+ ActivityOptions options = getDefaultActivityOptions(animationAdapter);
+ options.setSourceInfo(isKeyguardShowing ? ActivityOptions.SourceInfo.TYPE_LOCKSCREEN
+ : ActivityOptions.SourceInfo.TYPE_NOTIFICATION, eventTime);
+ return options.toBundle();
+ }
+
+ public static ActivityOptions getDefaultActivityOptions(
+ @Nullable RemoteAnimationAdapter animationAdapter) {
ActivityOptions options;
if (animationAdapter != null) {
options = ActivityOptions.makeRemoteAnimation(animationAdapter);
@@ -4292,7 +4326,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// Anything launched from the notification shade should always go into the secondary
// split-screen windowing mode.
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
- return options.toBundle();
+ return options;
}
void visibilityChanged(boolean visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 737cdeba797a..256ee2081f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -30,6 +30,7 @@ import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -40,7 +41,6 @@ import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
import android.view.RemoteAnimationAdapter;
-import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
@@ -48,7 +48,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -77,6 +77,7 @@ import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -103,7 +104,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -141,7 +142,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -175,7 +176,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -386,11 +387,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
private void expandBubbleStackOnMainThread(NotificationEntry entry) {
+ if (!mBubblesOptional.isPresent()) {
+ return;
+ }
+
if (Looper.getMainLooper().isCurrentThread()) {
- mBubbleController.expandStackAndSelectBubble(entry);
+ mBubblesOptional.get().expandStackAndSelectBubble(entry);
} else {
- mMainThreadHandler.post(
- () -> mBubbleController.expandStackAndSelectBubble(entry));
+ mMainThreadHandler.post(() -> mBubblesOptional.get().expandStackAndSelectBubble(entry));
}
}
@@ -398,7 +402,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
PendingIntent intent,
Intent fillInIntent,
NotificationEntry entry,
- View row,
+ ExpandableNotificationRow row,
boolean wasOccluded,
boolean isActivityIntent) {
RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
@@ -410,8 +414,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
.registerRemoteAnimationForNextActivityStart(
intent.getCreatorPackage(), adapter);
}
+ long eventTime = row.getAndResetLastActionUpTime();
+ Bundle options = eventTime > 0 ? getActivityOptions(adapter,
+ mKeyguardStateController.isShowing(), eventTime) : getActivityOptions(adapter);
int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
- null, null, getActivityOptions(adapter));
+ null, null, options);
mMainThreadHandler.post(() -> {
mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
});
@@ -602,7 +609,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final BubbleController mBubbleController;
+ private final Optional<Bubbles> mBubblesOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -639,7 +646,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -669,7 +676,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubbleController = bubbleController;
+ mBubblesOptional = bubblesOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -725,7 +732,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager,
mKeyguardManager,
mDreamManager,
- mBubbleController,
+ mBubblesOptional,
mAssistManagerLazy,
mRemoteInputManager,
mGroupMembershipManager,
@@ -736,12 +743,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mLockPatternUtils,
mRemoteInputCallback,
mActivityIntentHelper,
-
mFeatureFlags,
mMetricsLogger,
mLogger,
mOnUserInteractionCallback,
-
mStatusBar,
mNotificationPresenter,
mNotificationPanelViewController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b7f83145f477..6d4099b656cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -31,7 +31,7 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -43,7 +43,6 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginDependencyProvider;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
@@ -156,7 +155,7 @@ public interface StatusBarPhoneModule {
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
- BubbleController bubbleController,
+ Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
@@ -175,7 +174,6 @@ public interface StatusBarPhoneModule {
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- Optional<Recents> recentsOptional,
Provider<StatusBarComponent.Builder> statusBarComponentBuilder,
PluginManager pluginManager,
Optional<SplitScreen> splitScreenOptional,
@@ -235,7 +233,7 @@ public interface StatusBarPhoneModule {
wakefulnessLifecycle,
statusBarStateController,
vibratorHelper,
- bubbleController,
+ bubblesOptional,
visualStabilityManager,
deviceProvisionedController,
navigationBarController,
@@ -254,7 +252,6 @@ public interface StatusBarPhoneModule {
dozeScrimController,
volumeComponent,
commandQueue,
- recentsOptional,
statusBarComponentBuilder,
pluginManager,
splitScreenOptional,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index c43ad36d4462..82ad00ad7c6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -359,6 +360,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
return false;
}
+ private static boolean isOngoingCallNotif(NotificationEntry entry) {
+ return entry.getSbn().isOngoing() && Notification.CATEGORY_CALL.equals(
+ entry.getSbn().getNotification().category);
+ }
+
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
@@ -391,6 +397,15 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
return 1;
}
+ boolean selfCall = isOngoingCallNotif(mEntry);
+ boolean otherCall = isOngoingCallNotif(headsUpEntry.mEntry);
+
+ if (selfCall && !otherCall) {
+ return -1;
+ } else if (!selfCall && otherCall) {
+ return 1;
+ }
+
if (remoteInputActive && !headsUpEntry.remoteInputActive) {
return -1;
} else if (!remoteInputActive && headsUpEntry.remoteInputActive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index c6ae669d5d08..cbc8405cc057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -19,27 +19,8 @@ package com.android.systemui.statusbar.policy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.util.Log;
-import android.util.Pair;
import android.widget.Button;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -48,13 +29,11 @@ import java.util.List;
* thread, to later be accessed and modified on the (performance critical) UI thread.
*/
public class InflatedSmartReplies {
- private static final String TAG = "InflatedSmartReplies";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@Nullable private final SmartReplyView mSmartReplyView;
@Nullable private final List<Button> mSmartSuggestionButtons;
@NonNull private final SmartRepliesAndActions mSmartRepliesAndActions;
- private InflatedSmartReplies(
+ public InflatedSmartReplies(
@Nullable SmartReplyView smartReplyView,
@Nullable List<Button> smartSuggestionButtons,
@NonNull SmartRepliesAndActions smartRepliesAndActions) {
@@ -76,206 +55,6 @@ public class InflatedSmartReplies {
}
/**
- * Inflate a SmartReplyView and its smart suggestions.
- */
- public static InflatedSmartReplies inflate(
- Context context,
- Context packageContext,
- NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
- HeadsUpManager headsUpManager,
- SmartRepliesAndActions existingSmartRepliesAndActions) {
- SmartRepliesAndActions newSmartRepliesAndActions =
- chooseSmartRepliesAndActions(smartReplyConstants, entry);
- if (!shouldShowSmartReplyView(entry, newSmartRepliesAndActions)) {
- return new InflatedSmartReplies(null /* smartReplyView */,
- null /* smartSuggestionButtons */, newSmartRepliesAndActions);
- }
-
- // Only block clicks if the smart buttons are different from the previous set - to avoid
- // scenarios where a user incorrectly cannot click smart buttons because the notification is
- // updated.
- boolean delayOnClickListener =
- !areSuggestionsSimilar(existingSmartRepliesAndActions, newSmartRepliesAndActions);
-
- SmartReplyView smartReplyView = SmartReplyView.inflate(context);
-
- List<Button> suggestionButtons = new ArrayList<>();
- if (newSmartRepliesAndActions.smartReplies != null) {
- suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
- newSmartRepliesAndActions.smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- if (newSmartRepliesAndActions.smartActions != null) {
- suggestionButtons.addAll(
- smartReplyView.inflateSmartActions(packageContext,
- newSmartRepliesAndActions.smartActions, smartReplyController, entry,
- headsUpManager, delayOnClickListener));
- }
-
- return new InflatedSmartReplies(smartReplyView, suggestionButtons,
- newSmartRepliesAndActions);
- }
-
- @VisibleForTesting
- static boolean areSuggestionsSimilar(
- SmartRepliesAndActions left, SmartRepliesAndActions right) {
- if (left == right) return true;
- if (left == null || right == null) return false;
-
- if (!left.getSmartReplies().equals(right.getSmartReplies())) {
- return false;
- }
-
- return !NotificationUiAdjustment.areDifferent(
- left.getSmartActions(), right.getSmartActions());
- }
-
- /**
- * Returns whether we should show the smart reply view and its smart suggestions.
- */
- public static boolean shouldShowSmartReplyView(
- NotificationEntry entry,
- SmartRepliesAndActions smartRepliesAndActions) {
- if (smartRepliesAndActions.smartReplies == null
- && smartRepliesAndActions.smartActions == null) {
- // There are no smart replies and no smart actions.
- return false;
- }
- // If we are showing the spinner we don't want to add the buttons.
- boolean showingSpinner = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- if (showingSpinner) {
- return false;
- }
- // If we are keeping the notification around while sending we don't want to add the buttons.
- boolean hideSmartReplies = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false);
- if (hideSmartReplies) {
- return false;
- }
- return true;
- }
-
- /**
- * Chose what smart replies and smart actions to display. App generated suggestions take
- * precedence. So if the app provides any smart replies, we don't show any
- * replies or actions generated by the NotificationAssistantService (NAS), and if the app
- * provides any smart actions we also don't show any NAS-generated replies or actions.
- */
- @NonNull
- public static SmartRepliesAndActions chooseSmartRepliesAndActions(
- SmartReplyConstants smartReplyConstants,
- final NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(false /* freeform */);
- Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair =
- notification.findRemoteInputActionPair(true /* freeform */);
-
- if (!smartReplyConstants.isEnabled()) {
- if (DEBUG) {
- Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
- + entry.getSbn().getKey());
- }
- return new SmartRepliesAndActions(null, null);
- }
- // Only use smart replies from the app if they target P or above. We have this check because
- // the smart reply API has been used for other things (Wearables) in the past. The API to
- // add smart actions is new in Q so it doesn't require a target-sdk check.
- boolean enableAppGeneratedSmartReplies = (!smartReplyConstants.requiresTargetingP()
- || entry.targetSdk >= Build.VERSION_CODES.P);
-
- boolean appGeneratedSmartRepliesExist =
- enableAppGeneratedSmartReplies
- && remoteInputActionPair != null
- && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices())
- && remoteInputActionPair.second.actionIntent != null;
-
- List<Notification.Action> appGeneratedSmartActions = notification.getContextualActions();
- boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty();
-
- SmartReplyView.SmartReplies smartReplies = null;
- SmartReplyView.SmartActions smartActions = null;
- if (appGeneratedSmartRepliesExist) {
- smartReplies = new SmartReplyView.SmartReplies(
- Arrays.asList(remoteInputActionPair.first.getChoices()),
- remoteInputActionPair.first,
- remoteInputActionPair.second.actionIntent,
- false /* fromAssistant */);
- }
- if (appGeneratedSmartActionsExist) {
- smartActions = new SmartReplyView.SmartActions(appGeneratedSmartActions,
- false /* fromAssistant */);
- }
- // Apps didn't provide any smart replies / actions, use those from NAS (if any).
- if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
- boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.getSmartReplies())
- && freeformRemoteInputActionPair != null
- && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
- && freeformRemoteInputActionPair.second.actionIntent != null;
- if (useGeneratedReplies) {
- smartReplies = new SmartReplyView.SmartReplies(
- entry.getSmartReplies(),
- freeformRemoteInputActionPair.first,
- freeformRemoteInputActionPair.second.actionIntent,
- true /* fromAssistant */);
- }
- boolean useSmartActions = !ArrayUtils.isEmpty(entry.getSmartActions())
- && notification.getAllowSystemGeneratedContextualActions();
- if (useSmartActions) {
- List<Notification.Action> systemGeneratedActions =
- entry.getSmartActions();
- // Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
- // since notifications aren't shown there anyway.
- ActivityManagerWrapper activityManagerWrapper =
- Dependency.get(ActivityManagerWrapper.class);
- if (activityManagerWrapper.isLockTaskKioskModeActive()) {
- systemGeneratedActions = filterWhiteListedLockTaskApps(systemGeneratedActions);
- }
- smartActions = new SmartReplyView.SmartActions(
- systemGeneratedActions, true /* fromAssistant */);
- }
- }
- return new SmartRepliesAndActions(smartReplies, smartActions);
- }
-
- /**
- * Filter actions so that only actions pointing to whitelisted apps are allowed.
- * This filtering is only meaningful when in lock-task mode.
- */
- private static List<Notification.Action> filterWhiteListedLockTaskApps(
- List<Notification.Action> actions) {
- PackageManagerWrapper packageManagerWrapper = Dependency.get(PackageManagerWrapper.class);
- DevicePolicyManagerWrapper devicePolicyManagerWrapper =
- Dependency.get(DevicePolicyManagerWrapper.class);
- List<Notification.Action> filteredActions = new ArrayList<>();
- for (Notification.Action action : actions) {
- if (action.actionIntent == null) continue;
- Intent intent = action.actionIntent.getIntent();
- // Only allow actions that are explicit (implicit intents are not handled in lock-task
- // mode), and link to whitelisted apps.
- ResolveInfo resolveInfo = packageManagerWrapper.resolveActivity(intent, 0 /* flags */);
- if (resolveInfo != null && devicePolicyManagerWrapper.isLockTaskPermitted(
- resolveInfo.activityInfo.packageName)) {
- filteredActions.add(action);
- }
- }
- return filteredActions;
- }
-
- /**
- * Returns whether the {@link Notification} represented by entry has a free-form remote input.
- * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
- * through the remote input.
- */
- public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return null != notification.findRemoteInputActionPair(true /* freeform */);
- }
-
- /**
* A storage for smart replies and smart action.
*/
public static class SmartRepliesAndActions {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index f52a6e0191a1..f45178cd2230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -342,7 +342,7 @@ public class KeyguardUserSwitcher {
}
v.setActivated(true);
}
- switchTo(user);
+ onUserListItemClicked(user);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 0fdc80b3d97a..8e8a33fd0d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -20,7 +20,6 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static com.android.settingslib.Utils.updateLocationEnabled;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -60,6 +60,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private final Context mContext;
private final AppOpsController mAppOpsController;
private final BootCompleteCache mBootCompleteCache;
+ private final UserTracker mUserTracker;
private final H mHandler;
@@ -68,11 +69,13 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
@Inject
public LocationControllerImpl(Context context, AppOpsController appOpsController,
@Main Looper mainLooper, @Background Handler backgroundHandler,
- BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) {
+ BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache,
+ UserTracker userTracker) {
mContext = context;
mAppOpsController = appOpsController;
mBootCompleteCache = bootCompleteCache;
mHandler = new H(mainLooper);
+ mUserTracker = userTracker;
// Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
@@ -113,7 +116,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
public boolean setLocationEnabled(boolean enabled) {
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
- int currentUserId = ActivityManager.getCurrentUser();
+ int currentUserId = mUserTracker.getUserId();
if (isUserLocationRestricted(currentUserId)) {
return false;
}
@@ -134,7 +137,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
LocationManager locationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser(
- UserHandle.of(ActivityManager.getCurrentUser()));
+ mUserTracker.getUserHandle());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
new file mode 100644
index 000000000000..6a3a69c0419e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
@@ -0,0 +1,464 @@
+/*
+ * 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.statusbar.policy
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.RemoteInput
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.SystemClock
+import android.util.Log
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
+import com.android.systemui.R
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.DevicePolicyManagerWrapper
+import com.android.systemui.shared.system.PackageManagerWrapper
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.NotificationUiAdjustment
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
+import javax.inject.Inject
+
+/** Returns whether we should show the smart reply view and its smart suggestions. */
+fun shouldShowSmartReplyView(
+ entry: NotificationEntry,
+ smartRepliesAndActions: SmartRepliesAndActions
+): Boolean {
+ if (smartRepliesAndActions.smartReplies == null
+ && smartRepliesAndActions.smartActions == null) {
+ // There are no smart replies and no smart actions.
+ return false
+ }
+ // If we are showing the spinner we don't want to add the buttons.
+ val showingSpinner = entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)
+ if (showingSpinner) {
+ return false
+ }
+ // If we are keeping the notification around while sending we don't want to add the buttons.
+ return !entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
+}
+
+/** Determines if two [SmartRepliesAndActions] are visually similar. */
+fun areSuggestionsSimilar(
+ left: SmartRepliesAndActions?,
+ right: SmartRepliesAndActions?
+): Boolean = when {
+ left === right -> true
+ left == null || right == null -> false
+ left.getSmartReplies() != right.getSmartReplies() -> false
+ else -> !NotificationUiAdjustment.areDifferent(left.getSmartActions(), right.getSmartActions())
+}
+
+interface SmartRepliesAndActionsInflater {
+ fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions?
+ ): InflatedSmartReplies
+}
+
+/*internal*/ class SmartRepliesAndActionsInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val activityManagerWrapper: ActivityManagerWrapper,
+ private val packageManagerWrapper: PackageManagerWrapper,
+ private val devicePolicyManagerWrapper: DevicePolicyManagerWrapper,
+ private val smartRepliesInflater: SmartReplyInflater,
+ private val smartActionsInflater: SmartActionInflater
+) : SmartRepliesAndActionsInflater {
+
+ override fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions?
+ ): InflatedSmartReplies {
+ val newRepliesAndActions = chooseSmartRepliesAndActions(entry)
+ if (!shouldShowSmartReplyView(entry, newRepliesAndActions)) {
+ return InflatedSmartReplies(
+ null /* smartReplyView */,
+ null /* smartSuggestionButtons */,
+ newRepliesAndActions)
+ }
+
+ // Only block clicks if the smart buttons are different from the previous set - to avoid
+ // scenarios where a user incorrectly cannot click smart buttons because the
+ // notification is updated.
+ val delayOnClickListener =
+ !areSuggestionsSimilar(existingRepliesAndAction, newRepliesAndActions)
+
+ val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
+
+ val smartReplies = newRepliesAndActions.smartReplies
+ smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
+ val smartReplyButtons = smartReplies?.let {
+ smartReplies.choices.asSequence().mapIndexed { index, choice ->
+ smartRepliesInflater.inflateReplyButton(
+ smartReplyView,
+ entry,
+ smartReplies,
+ index,
+ choice,
+ delayOnClickListener)
+ }
+ } ?: emptySequence()
+
+ val smartActionButtons = newRepliesAndActions.smartActions?.let { smartActions ->
+ val themedPackageContext =
+ ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
+ smartActions.actions.asSequence()
+ .filter { it.actionIntent != null }
+ .mapIndexed { index, action ->
+ smartActionsInflater.inflateActionButton(
+ smartReplyView,
+ entry,
+ smartActions,
+ index,
+ action,
+ delayOnClickListener,
+ themedPackageContext)
+ }
+ } ?: emptySequence()
+
+ return InflatedSmartReplies(
+ smartReplyView,
+ (smartReplyButtons + smartActionButtons).toList(),
+ newRepliesAndActions)
+ }
+
+ /**
+ * Chose what smart replies and smart actions to display. App generated suggestions take
+ * precedence. So if the app provides any smart replies, we don't show any
+ * replies or actions generated by the NotificationAssistantService (NAS), and if the app
+ * provides any smart actions we also don't show any NAS-generated replies or actions.
+ */
+ fun chooseSmartRepliesAndActions(entry: NotificationEntry): SmartRepliesAndActions {
+ val notification = entry.sbn.notification
+ val remoteInputActionPair = notification.findRemoteInputActionPair(false /* freeform */)
+ val freeformRemoteInputActionPair =
+ notification.findRemoteInputActionPair(true /* freeform */)
+ if (!constants.isEnabled) {
+ if (DEBUG) {
+ Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
+ + entry.sbn.key)
+ }
+ return SmartRepliesAndActions(null, null)
+ }
+ // Only use smart replies from the app if they target P or above. We have this check because
+ // the smart reply API has been used for other things (Wearables) in the past. The API to
+ // add smart actions is new in Q so it doesn't require a target-sdk check.
+ val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP()
+ || entry.targetSdk >= Build.VERSION_CODES.P)
+ val appGeneratedSmartActions = notification.contextualActions
+
+ var smartReplies: SmartReplies? = when {
+ enableAppGeneratedSmartReplies -> remoteInputActionPair?.let { pair ->
+ pair.second.actionIntent?.let { actionIntent ->
+ if (pair.first.choices?.isNotEmpty() == true)
+ SmartReplies(
+ pair.first.choices.asList(),
+ pair.first,
+ actionIntent,
+ false /* fromAssistant */)
+ else null
+ }
+ }
+ else -> null
+ }
+ var smartActions: SmartActions? = when {
+ appGeneratedSmartActions.isNotEmpty() ->
+ SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
+ else -> null
+ }
+ // Apps didn't provide any smart replies / actions, use those from NAS (if any).
+ if (smartReplies == null && smartActions == null) {
+ val entryReplies = entry.smartReplies
+ val entryActions = entry.smartActions
+ if (entryReplies.isNotEmpty()
+ && freeformRemoteInputActionPair != null
+ && freeformRemoteInputActionPair.second.allowGeneratedReplies
+ && freeformRemoteInputActionPair.second.actionIntent != null) {
+ smartReplies = SmartReplies(
+ entryReplies,
+ freeformRemoteInputActionPair.first,
+ freeformRemoteInputActionPair.second.actionIntent,
+ true /* fromAssistant */)
+ }
+ if (entryActions.isNotEmpty()
+ && notification.allowSystemGeneratedContextualActions) {
+ val systemGeneratedActions: List<Notification.Action> = when {
+ activityManagerWrapper.isLockTaskKioskModeActive ->
+ // Filter actions if we're in kiosk-mode - we don't care about screen
+ // pinning mode, since notifications aren't shown there anyway.
+ filterAllowlistedLockTaskApps(entryActions)
+ else -> entryActions
+ }
+ smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
+ }
+ }
+ return SmartRepliesAndActions(smartReplies, smartActions)
+ }
+
+ /**
+ * Filter actions so that only actions pointing to allowlisted apps are permitted.
+ * This filtering is only meaningful when in lock-task mode.
+ */
+ private fun filterAllowlistedLockTaskApps(
+ actions: List<Notification.Action>
+ ): List<Notification.Action> = actions.filter { action ->
+ // Only allow actions that are explicit (implicit intents are not handled in lock-task
+ // mode), and link to allowlisted apps.
+ action.actionIntent?.intent?.let { intent ->
+ packageManagerWrapper.resolveActivity(intent, 0 /* flags */)
+ }?.let { resolveInfo ->
+ devicePolicyManagerWrapper.isLockTaskPermitted(resolveInfo.activityInfo.packageName)
+ } ?: false
+ }
+}
+
+interface SmartActionInflater {
+ fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button
+}
+
+/* internal */ class SmartActionInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val activityStarter: ActivityStarter,
+ private val smartReplyController: SmartReplyController,
+ private val headsUpManager: HeadsUpManager
+) : SmartActionInflater {
+
+ override fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_action_button, parent, false) as Button
+ ).apply {
+ text = action.title
+
+ // We received the Icon from the application - so use the Context of the application to
+ // reference icon resources.
+ val iconDrawable = action.getIcon().loadDrawable(packageContext)
+ .apply {
+ val newIconSize: Int = context.resources.getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_size)
+ setBounds(0, 0, newIconSize, newIconSize)
+ }
+ // Add the action icon to the Smart Action button.
+ setCompoundDrawables(iconDrawable, null, null, null)
+
+ val onClickListener = View.OnClickListener {
+ onSmartActionClick(entry, smartActions, actionIndex, action)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+
+ // Mark this as an Action button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
+ }
+
+ private fun onSmartActionClick(
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action
+ ) =
+ activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
+ smartReplyController
+ .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
+ }
+}
+
+interface SmartReplyInflater {
+ fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button
+}
+
+class SmartReplyInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val smartReplyController: SmartReplyController,
+ private val context: Context
+) : SmartReplyInflater {
+
+ override fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_reply_button, parent, false) as Button
+ ).apply {
+ text = choice
+ val onClickListener = View.OnClickListener {
+ onSmartReplyClick(
+ entry,
+ smartReplies,
+ replyIndex,
+ parent,
+ this,
+ choice)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+ accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val label = parent.resources
+ .getString(R.string.accessibility_send_smart_reply)
+ val action = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
+ info.addAction(action)
+ }
+ }
+ // TODO: probably shouldn't do this here, bad API
+ // Mark this as a Reply button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
+ }
+
+ private fun onSmartReplyClick(
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ smartReplyView: SmartReplyView,
+ button: Button,
+ choice: CharSequence
+ ) = keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
+ val canEditBeforeSend = constants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.editChoicesBeforeSending)
+ if (canEditBeforeSend) {
+ remoteInputManager.activateRemoteInput(
+ button,
+ arrayOf(smartReplies.remoteInput),
+ smartReplies.remoteInput,
+ smartReplies.pendingIntent,
+ NotificationEntry.EditedSuggestionInfo(choice, replyIndex))
+ } else {
+ smartReplyController.smartReplySent(
+ entry,
+ replyIndex,
+ button.text,
+ NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
+ false /* modifiedBeforeSending */)
+ entry.setHasSentReply()
+ try {
+ val intent = createRemoteInputIntent(smartReplies, choice)
+ smartReplies.pendingIntent.send(context, 0, intent)
+ } catch (e: PendingIntent.CanceledException) {
+ Log.w(TAG, "Unable to send smart reply", e)
+ }
+ smartReplyView.hideSmartSuggestions()
+ }
+ false // do not defer
+ }
+
+ private fun createRemoteInputIntent(smartReplies: SmartReplies, choice: CharSequence): Intent {
+ val results = Bundle()
+ results.putString(smartReplies.remoteInput.resultKey, choice.toString())
+ val intent = Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ RemoteInput.addResultsToIntent(arrayOf(smartReplies.remoteInput), intent, results)
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE)
+ return intent
+ }
+}
+
+/**
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
+ * time.
+ */
+private class DelayedOnClickListener(
+ private val mActualListener: View.OnClickListener,
+ private val mInitDelayMs: Long
+) : View.OnClickListener {
+
+ private val mInitTimeMs = SystemClock.elapsedRealtime()
+
+ override fun onClick(v: View) {
+ if (hasFinishedInitialization()) {
+ mActualListener.onClick(v)
+ } else {
+ Log.i(TAG, "Accidental Smart Suggestion click registered, delay: $mInitDelayMs")
+ }
+ }
+
+ private fun hasFinishedInitialization(): Boolean =
+ SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
+}
+
+private const val TAG = "SmartReplyViewInflater"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun KeyguardDismissUtil.executeWhenUnlocked(
+ requiresShadeOpen: Boolean,
+ onDismissAction: () -> Boolean
+) = executeWhenUnlocked(onDismissAction, requiresShadeOpen)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun ActivityStarter.startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ associatedView: View?,
+ runnable: () -> Unit
+) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 949ac4df88ba..e7f84a55eb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -6,7 +6,6 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -15,34 +14,20 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
import java.util.ArrayList;
@@ -64,10 +49,6 @@ public class SmartReplyView extends ViewGroup {
private static final int SQUEEZE_FAILED = -1;
- private final SmartReplyConstants mConstants;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
- private final NotificationRemoteInputManager mRemoteInputManager;
-
/**
* The upper bound for the height of this view in pixels. Notifications are automatically
* recreated on density or font size changes so caching this should be fine.
@@ -98,30 +79,25 @@ public class SmartReplyView extends ViewGroup {
*/
private boolean mSmartRepliesGeneratedByAssistant = false;
- @ColorInt
- private int mCurrentBackgroundColor;
- @ColorInt
- private final int mDefaultBackgroundColor;
- @ColorInt
- private final int mDefaultStrokeColor;
- @ColorInt
- private final int mDefaultTextColor;
- @ColorInt
- private final int mDefaultTextColorDarkBg;
- @ColorInt
- private final int mRippleColorDarkBg;
- @ColorInt
- private final int mRippleColor;
+ @ColorInt private int mCurrentBackgroundColor;
+ @ColorInt private final int mDefaultBackgroundColor;
+ @ColorInt private final int mDefaultStrokeColor;
+ @ColorInt private final int mDefaultTextColor;
+ @ColorInt private final int mDefaultTextColorDarkBg;
+ @ColorInt private final int mRippleColorDarkBg;
+ @ColorInt private final int mRippleColor;
private final int mStrokeWidth;
private final double mMinStrokeContrast;
- private ActivityStarter mActivityStarter;
+ @ColorInt private int mCurrentStrokeColor;
+ @ColorInt private int mCurrentTextColor;
+ @ColorInt private int mCurrentRippleColor;
+ private int mMaxSqueezeRemeasureAttempts;
+ private int mMaxNumActions;
+ private int mMinNumSystemGeneratedReplies;
public SmartReplyView(Context context, AttributeSet attrs) {
super(context, attrs);
- mConstants = Dependency.get(SmartReplyConstants.class);
- mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.smart_reply_button_max_height);
@@ -172,6 +148,18 @@ public class SmartReplyView extends ViewGroup {
}
/**
+ * Inflate an instance of this class.
+ */
+ public static SmartReplyView inflate(Context context, SmartReplyConstants constants) {
+ SmartReplyView view = (SmartReplyView) LayoutInflater.from(context).inflate(
+ R.layout.smart_reply_view, null /* root */);
+ view.setMaxNumActions(constants.getMaxNumActions());
+ view.setMaxSqueezeRemeasureAttempts(constants.getMaxSqueezeRemeasureAttempts());
+ view.setMinNumSystemGeneratedReplies(constants.getMinNumSystemGeneratedReplies());
+ return view;
+ }
+
+ /**
* Returns an upper bound for the height of this view in pixels. This method is intended to be
* invoked before onMeasure, so it doesn't do any analysis on the contents of the buttons.
*/
@@ -197,174 +185,25 @@ public class SmartReplyView extends ViewGroup {
mCurrentBackgroundColor = mDefaultBackgroundColor;
}
- /**
- * Add buttons to the {@link SmartReplyView} - these buttons must have been preinflated using
- * one of the methods in this class.
- */
+ /** Add buttons to the {@link SmartReplyView} */
public void addPreInflatedButtons(List<Button> smartSuggestionButtons) {
for (Button button : smartSuggestionButtons) {
addView(button);
+ setButtonColors(button);
}
reallocateCandidateButtonQueueForSqueezing();
}
- /**
- * Add smart replies to this view, using the provided {@link RemoteInput} and
- * {@link PendingIntent} to respond when the user taps a smart reply. Only the replies that fit
- * into the notification are shown.
- */
- public List<Button> inflateRepliesFromRemoteInput(
- @NonNull SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationEntry entry,
- boolean delayOnClickListener) {
- List<Button> buttons = new ArrayList<>();
-
- if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
- if (smartReplies.choices != null) {
- for (int i = 0; i < smartReplies.choices.size(); ++i) {
- buttons.add(inflateReplyButton(
- this, getContext(), i, smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
- }
- }
- return buttons;
- }
-
- /**
- * Add smart actions to be shown next to smart replies. Only the actions that fit into the
- * notification are shown.
- */
- public List<Button> inflateSmartActions(Context packageContext,
- @NonNull SmartActions smartActions, SmartReplyController smartReplyController,
- NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
- Context themedPackageContext = new ContextThemeWrapper(packageContext, mContext.getTheme());
- List<Button> buttons = new ArrayList<>();
- int numSmartActions = smartActions.actions.size();
- for (int n = 0; n < numSmartActions; n++) {
- Notification.Action action = smartActions.actions.get(n);
- if (action.actionIntent != null) {
- buttons.add(inflateActionButton(
- this, getContext(), themedPackageContext, n, smartActions,
- smartReplyController,
- entry, headsUpManager, delayOnClickListener));
- }
- }
- return buttons;
- }
-
- /**
- * Inflate an instance of this class.
- */
- public static SmartReplyView inflate(Context context) {
- return (SmartReplyView) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_view, null /* root */);
+ public void setMaxNumActions(int maxNumActions) {
+ mMaxNumActions = maxNumActions;
}
- @VisibleForTesting
- static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
- int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationEntry entry, boolean useDelayedOnClickListener) {
- Button b = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_button, smartReplyView, false);
- CharSequence choice = smartReplies.choices.get(replyIndex);
- b.setText(choice);
-
- OnDismissAction action = () -> {
- if (smartReplyView.mConstants.getEffectiveEditChoicesBeforeSending(
- smartReplies.remoteInput.getEditChoicesBeforeSending())) {
- EditedSuggestionInfo editedSuggestionInfo =
- new EditedSuggestionInfo(choice, replyIndex);
- smartReplyView.mRemoteInputManager.activateRemoteInput(b,
- new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
- smartReplies.pendingIntent, editedSuggestionInfo);
- return false;
- }
-
- smartReplyController.smartReplySent(entry, replyIndex, b.getText(),
- NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
- false /* modifiedBeforeSending */);
- Bundle results = new Bundle();
- results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
- Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent,
- results);
- RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
- entry.setHasSentReply();
- try {
- smartReplies.pendingIntent.send(context, 0, intent);
- } catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Unable to send smart reply", e);
- }
- // Note that as inflateReplyButton is called mSmartReplyContainer is null, but when the
- // reply Button is added to the SmartReplyView mSmartReplyContainer will be set. So, it
- // will not be possible for a user to trigger this on-click-listener without
- // mSmartReplyContainer being set.
- smartReplyView.mSmartReplyContainer.setVisibility(View.GONE);
- return false; // do not defer
- };
-
- OnClickListener onClickListener = view ->
- smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action, !entry.isRowPinned());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- b.setOnClickListener(onClickListener);
-
- b.setAccessibilityDelegate(new AccessibilityDelegate() {
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = smartReplyView.getResources().getString(
- R.string.accessibility_send_smart_reply);
- info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label));
- }
- });
-
- SmartReplyView.setButtonColors(b, smartReplyView.mCurrentBackgroundColor,
- smartReplyView.mDefaultStrokeColor, smartReplyView.mDefaultTextColor,
- smartReplyView.mRippleColor, smartReplyView.mStrokeWidth);
- return b;
+ public void setMinNumSystemGeneratedReplies(int minNumSystemGeneratedReplies) {
+ mMinNumSystemGeneratedReplies = minNumSystemGeneratedReplies;
}
- @VisibleForTesting
- static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
- Context packageContext, int actionIndex, SmartActions smartActions,
- SmartReplyController smartReplyController, NotificationEntry entry,
- HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
- Notification.Action action = smartActions.actions.get(actionIndex);
- Button button = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_action_button, smartReplyView, false);
- button.setText(action.title);
-
- // We received the Icon from the application - so use the Context of the application to
- // reference icon resources.
- Drawable iconDrawable = action.getIcon().loadDrawable(packageContext);
- // Add the action icon to the Smart Action button.
- int newIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.smart_action_button_icon_size);
- iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
- button.setCompoundDrawables(iconDrawable, null, null, null);
-
- OnClickListener onClickListener = view ->
- smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
- action.actionIntent,
- () -> {
- smartReplyController.smartActionClicked(
- entry, actionIndex, action, smartActions.fromAssistant);
- headsUpManager.removeNotification(entry.getKey(), true);
- }, entry.getRow());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- button.setOnClickListener(onClickListener);
-
- // Mark this as an Action button
- final LayoutParams lp = (LayoutParams) button.getLayoutParams();
- lp.buttonType = SmartButtonType.ACTION;
- return button;
+ public void setMaxSqueezeRemeasureAttempts(int maxSqueezeRemeasureAttempts) {
+ mMaxSqueezeRemeasureAttempts = maxSqueezeRemeasureAttempts;
}
@Override
@@ -416,13 +255,13 @@ public class SmartReplyView extends ViewGroup {
// reply button is added.
SmartSuggestionMeasures actionsMeasures = null;
- final int maxNumActions = mConstants.getMaxNumActions();
+ final int maxNumActions = mMaxNumActions;
int numShownActions = 0;
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (maxNumActions != -1 // -1 means 'no limit'
- && lp.buttonType == SmartButtonType.ACTION
+ && lp.mButtonType == SmartButtonType.ACTION
&& numShownActions >= maxNumActions) {
// We've reached the maximum number of actions, don't add another one!
continue;
@@ -446,7 +285,7 @@ public class SmartReplyView extends ViewGroup {
// Remember the current measurements in case the current button doesn't fit in.
SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
- if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ if (actionsMeasures == null && lp.mButtonType == SmartButtonType.REPLY) {
// We've added all actions (we go through actions first), now add their
// measurements.
actionsMeasures = accumulatedMeasures.clone();
@@ -510,7 +349,7 @@ public class SmartReplyView extends ViewGroup {
lp.show = true;
displayedChildCount++;
- if (lp.buttonType == SmartButtonType.ACTION) {
+ if (lp.mButtonType == SmartButtonType.ACTION) {
numShownActions++;
}
}
@@ -551,6 +390,19 @@ public class SmartReplyView extends ViewGroup {
resolveSize(buttonHeight, heightMeasureSpec));
}
+ // TODO: this should be replaced, and instead, setMinSystemGenerated... should be invoked
+ // with MAX_VALUE if mSmartRepliesGeneratedByAssistant would be false (essentially, this is a
+ // ViewModel decision, as opposed to a View decision)
+ void setSmartRepliesGeneratedByAssistant(boolean fromAssistant) {
+ mSmartRepliesGeneratedByAssistant = fromAssistant;
+ }
+
+ void hideSmartSuggestions() {
+ if (mSmartReplyContainer != null) {
+ mSmartReplyContainer.setVisibility(View.GONE);
+ }
+ }
+
/**
* Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
* on which suggestions are added.
@@ -577,6 +429,7 @@ public class SmartReplyView extends ViewGroup {
* Returns whether our notification contains at least N smart replies (or 0) where N is
* determined by {@link SmartReplyConstants}.
*/
+ // TODO: we probably sholdn't make this deliberation in the View
private boolean gotEnoughSmartReplies(List<View> smartReplies) {
int numShownReplies = 0;
for (View smartReplyButton : smartReplies) {
@@ -585,8 +438,7 @@ public class SmartReplyView extends ViewGroup {
numShownReplies++;
}
}
- if (numShownReplies == 0
- || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ if (numShownReplies == 0 || numShownReplies >= mMinNumSystemGeneratedReplies) {
// We have enough replies, yay!
return true;
}
@@ -602,7 +454,7 @@ public class SmartReplyView extends ViewGroup {
if (child.getVisibility() != View.VISIBLE || !(child instanceof Button)) {
continue;
}
- if (lp.buttonType == buttonType) {
+ if (lp.mButtonType == buttonType) {
actions.add(child);
}
}
@@ -656,7 +508,7 @@ public class SmartReplyView extends ViewGroup {
// See if there's a better line-break point (leading to a more narrow button) in
// either left or right direction.
final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
- final int maxSqueezeRemeasureAttempts = mConstants.getMaxSqueezeRemeasureAttempts();
+ final int maxSqueezeRemeasureAttempts = mMaxSqueezeRemeasureAttempts;
for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
final int newPosition =
moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
@@ -833,41 +685,38 @@ public class SmartReplyView extends ViewGroup {
final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
- int textColor = ContrastColorUtil.ensureTextContrast(
+ mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
backgroundColor | 0xff000000, dark);
- int strokeColor = ContrastColorUtil.ensureContrast(
+ mCurrentStrokeColor = ContrastColorUtil.ensureContrast(
mDefaultStrokeColor, backgroundColor | 0xff000000, dark, mMinStrokeContrast);
- int rippleColor = dark ? mRippleColorDarkBg : mRippleColor;
+ mCurrentRippleColor = dark ? mRippleColorDarkBg : mRippleColor;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- final Button child = (Button) getChildAt(i);
- setButtonColors(child, backgroundColor, strokeColor, textColor, rippleColor,
- mStrokeWidth);
+ setButtonColors((Button) getChildAt(i));
}
}
- private static void setButtonColors(Button button, int backgroundColor, int strokeColor,
- int textColor, int rippleColor, int strokeWidth) {
+ private void setButtonColors(Button button) {
Drawable drawable = button.getBackground();
if (drawable instanceof RippleDrawable) {
// Mutate in case other notifications are using this drawable.
drawable = drawable.mutate();
RippleDrawable ripple = (RippleDrawable) drawable;
- ripple.setColor(ColorStateList.valueOf(rippleColor));
+ ripple.setColor(ColorStateList.valueOf(mCurrentRippleColor));
Drawable inset = ripple.getDrawable(0);
if (inset instanceof InsetDrawable) {
Drawable background = ((InsetDrawable) inset).getDrawable();
if (background instanceof GradientDrawable) {
GradientDrawable gradientDrawable = (GradientDrawable) background;
- gradientDrawable.setColor(backgroundColor);
- gradientDrawable.setStroke(strokeWidth, strokeColor);
+ gradientDrawable.setColor(mCurrentBackgroundColor);
+ gradientDrawable.setStroke(mStrokeWidth, mCurrentStrokeColor);
}
}
button.setBackground(drawable);
}
- button.setTextColor(textColor);
+ button.setTextColor(mCurrentTextColor);
}
private void setCornerRadius(Button button, float radius) {
@@ -887,14 +736,7 @@ public class SmartReplyView extends ViewGroup {
}
}
- private ActivityStarter getActivityStarter() {
- if (mActivityStarter == null) {
- mActivityStarter = Dependency.get(ActivityStarter.class);
- }
- return mActivityStarter;
- }
-
- private enum SmartButtonType {
+ enum SmartButtonType {
REPLY,
ACTION
}
@@ -924,7 +766,7 @@ public class SmartReplyView extends ViewGroup {
private boolean show = false;
private int squeezeStatus = SQUEEZE_STATUS_NONE;
- private SmartButtonType buttonType = SmartButtonType.REPLY;
+ SmartButtonType mButtonType = SmartButtonType.REPLY;
private LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
@@ -975,32 +817,4 @@ public class SmartReplyView extends ViewGroup {
this.fromAssistant = fromAssistant;
}
}
-
- /**
- * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
- * time.
- */
- private static class DelayedOnClickListener implements OnClickListener {
- private final OnClickListener mActualListener;
- private final long mInitDelayMs;
- private final long mInitTimeMs;
-
- DelayedOnClickListener(OnClickListener actualOnClickListener, long initDelayMs) {
- mActualListener = actualOnClickListener;
- mInitDelayMs = initDelayMs;
- mInitTimeMs = SystemClock.elapsedRealtime();
- }
-
- public void onClick(View v) {
- if (hasFinishedInitialization()) {
- mActualListener.onClick(v);
- } else {
- Log.i(TAG, "Accidental Smart Suggestion click registered, delay: " + mInitDelayMs);
- }
- }
-
- private boolean hasFinishedInitialization() {
- return SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 17fcb1dd6f1a..72e8e38735e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -23,6 +23,7 @@ import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.IActivityTaskManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -53,7 +54,6 @@ import android.widget.BaseAdapter;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.UserIcons;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.systemui.Dumpable;
import com.android.systemui.GuestResumeSessionReceiver;
@@ -69,6 +69,7 @@ import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.user.CreateUserActivity;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -104,6 +105,7 @@ public class UserSwitcherController implements Dumpable {
protected final Handler mHandler;
private final ActivityStarter mActivityStarter;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final IActivityTaskManager mActivityTaskManager;
private ArrayList<UserRecord> mUsers = new ArrayList<>();
private Dialog mExitGuestDialog;
@@ -121,9 +123,11 @@ public class UserSwitcherController implements Dumpable {
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@Main Handler handler, ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger) {
+ BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger,
+ IActivityTaskManager activityTaskManager) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mActivityTaskManager = activityTaskManager;
mUiEventLogger = uiEventLogger;
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
@@ -363,7 +367,7 @@ public class UserSwitcherController implements Dumpable {
}
}
- public void switchTo(UserRecord record) {
+ private void onUserListItemClicked(UserRecord record) {
int id;
if (record.isGuest && record.info == null) {
// No guest user. Create one.
@@ -408,19 +412,6 @@ public class UserSwitcherController implements Dumpable {
switchToUserId(id);
}
- public void switchTo(int userId) {
- final int count = mUsers.size();
- for (int i = 0; i < count; ++i) {
- UserRecord record = mUsers.get(i);
- if (record.info != null && record.info.id == userId) {
- switchTo(record);
- return;
- }
- }
-
- Log.e(TAG, "Couldn't switch to user, id=" + userId);
- }
-
protected void switchToUserId(int id) {
try {
pauseRefreshUsers();
@@ -666,8 +657,11 @@ public class UserSwitcherController implements Dumpable {
return position;
}
- public void switchTo(UserRecord record) {
- mController.switchTo(record);
+ /**
+ * It handles click events on user list items.
+ */
+ public void onUserListItemClicked(UserRecord record) {
+ mController.onUserListItemClicked(record);
}
public String getName(Context context, UserRecord item) {
@@ -924,18 +918,33 @@ public class UserSwitcherController implements Dumpable {
if (ActivityManager.isUserAMonkey()) {
return;
}
- UserInfo user = mUserManager.createUser(
- mContext.getString(R.string.user_new_user_name), 0 /* flags */);
- if (user == null) {
- // Couldn't create user, most likely because there are too many, but we haven't
- // been able to reload the list yet.
- return;
+ Intent intent = CreateUserActivity.createIntentForStart(getContext());
+
+ // There are some differences between ActivityStarter and ActivityTaskManager in
+ // terms of how they start an activity. ActivityStarter hides the notification bar
+ // before starting the activity to make sure nothing is in front of the new
+ // activity. ActivityStarter also tries to unlock the device if it's locked.
+ // When locked with PIN/pattern/password then it shows the prompt, if there are no
+ // security steps then it dismisses the keyguard and then starts the activity.
+ // ActivityTaskManager doesn't hide the notification bar or unlocks the device, but
+ // it can start an activity on top of the locked screen.
+ if (!mKeyguardStateController.isUnlocked()
+ && !mKeyguardStateController.canDismissLockScreen()) {
+ // Device is locked and can't be unlocked without a PIN/pattern/password so we
+ // need to use ActivityTaskManager to start the activity on top of the locked
+ // screen.
+ try {
+ mActivityTaskManager.startActivity(null,
+ mContext.getBasePackageName(), mContext.getAttributionTag(), intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()), null,
+ null, 0, 0, null, null);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ Log.e(TAG, "Couldn't start create user activity", e);
+ }
+ } else {
+ mActivityStarter.startActivity(intent, true);
}
- int id = user.id;
- Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- mContext.getResources(), id, /* light= */ false));
- mUserManager.setUserIcon(id, icon);
- switchToUserId(id);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
new file mode 100644
index 000000000000..803d26ec3286
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.policy.dagger
+
+import com.android.systemui.statusbar.policy.SmartActionInflater
+import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterImpl
+import com.android.systemui.statusbar.policy.SmartReplyInflater
+import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface SmartRepliesInflationModule {
+ @Binds fun bindSmartActionsInflater(impl: SmartActionInflaterImpl): SmartActionInflater
+ @Binds fun bindSmartReplyInflater(impl: SmartReplyInflaterImpl): SmartReplyInflater
+ @Binds fun bindsInflatedSmartRepliesProvider(
+ impl: SmartRepliesAndActionsInflaterImpl
+ ): SmartRepliesAndActionsInflater
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
index 7a78c157e5b4..0bd36240a366 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvNotificationPanel.java
@@ -58,6 +58,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
if (!mNotificationHandlerPackage.isEmpty()) {
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_TOGGLE_NOTIFICATION_HANDLER_PANEL));
+ } else {
+ Log.w(TAG,
+ "Not toggling notification panel: config_notificationHandlerPackage is "
+ + "empty");
}
}
@@ -66,6 +70,10 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
if (!mNotificationHandlerPackage.isEmpty()) {
startNotificationHandlerActivity(
new Intent(NotificationManager.ACTION_OPEN_NOTIFICATION_HANDLER_PANEL));
+ } else {
+ Log.w(TAG,
+ "Not expanding notification panel: config_notificationHandlerPackage is "
+ + "empty");
}
}
@@ -77,6 +85,9 @@ public class TvNotificationPanel extends SystemUI implements CommandQueue.Callba
NotificationManager.ACTION_CLOSE_NOTIFICATION_HANDLER_PANEL);
closeNotificationIntent.setPackage(mNotificationHandlerPackage);
mContext.sendBroadcastAsUser(closeNotificationIntent, UserHandle.CURRENT);
+ } else {
+ Log.w(TAG,
+ "Not closing notification panel: config_notificationHandlerPackage is empty");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
index a29db4d98329..c9d1b71bca77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.UiThread;
@@ -36,7 +35,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.tv.TvStatusBar;
@@ -83,19 +81,14 @@ public class AudioRecordingDisclosureBar implements
private static final int STATE_SHOWN = 2;
private static final int STATE_DISAPPEARING = 3;
- private static final int ANIMATION_DURATION = 600;
+ private static final int ANIMATION_DURATION_MS = 200;
private final Context mContext;
private boolean mIsEnabled;
private View mIndicatorView;
- private View mIconTextsContainer;
- private View mIconContainerBg;
- private View mIcon;
- private View mBgEnd;
- private View mTextsContainers;
- private TextView mTextView;
- private boolean mIsLtr;
+ private boolean mViewAndWindowAdded;
+ private ObjectAnimator mAnimator;
@State private int mState = STATE_STOPPED;
@@ -190,7 +183,7 @@ public class AudioRecordingDisclosureBar implements
}
if (active) {
- showIfNotShown();
+ showIfNeeded();
} else {
hideIndicatorIfNeeded();
}
@@ -198,152 +191,145 @@ public class AudioRecordingDisclosureBar implements
@UiThread
private void hideIndicatorIfNeeded() {
- // If not STATE_APPEARING, will check whether the indicator should be hidden when the
- // indicator comes to the STATE_SHOWN.
- // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here.
- if (mState != STATE_SHOWN) return;
-
- // If is in the STATE_SHOWN and there are no active recorders - hide.
- if (!hasActiveRecorders()) {
- hide();
+ // If STOPPED, NOT_SHOWN or DISAPPEARING - nothing else for us to do here.
+ if (mState != STATE_SHOWN && mState != STATE_APPEARING) return;
+
+ if (hasActiveRecorders()) {
+ return;
+ }
+
+ if (mViewAndWindowAdded) {
+ mState = STATE_DISAPPEARING;
+ animateDisappearance();
+ } else {
+ // Appearing animation has not started yet, as we were still waiting for the View to be
+ // laid out.
+ mState = STATE_NOT_SHOWN;
+ removeIndicatorView();
}
}
@UiThread
- private void showIfNotShown() {
- if (mState != STATE_NOT_SHOWN) return;
+ private void showIfNeeded() {
+ // If STOPPED, SHOWN or APPEARING - nothing else for us to do here.
+ if (mState != STATE_NOT_SHOWN && mState != STATE_DISAPPEARING) return;
+
if (DEBUG) Log.d(TAG, "Showing indicator");
- mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
+ final int prevState = mState;
+ mState = STATE_APPEARING;
+
+ if (prevState == STATE_DISAPPEARING) {
+ animateAppearance();
+ return;
+ }
// Inflate the indicator view
mIndicatorView = LayoutInflater.from(mContext).inflate(
- R.layout.tv_audio_recording_indicator,
- null);
- mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
- mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
- mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
- mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
- mTextView = mTextsContainers.findViewById(R.id.text);
- mBgEnd = mIndicatorView.findViewById(R.id.bg_end);
-
- mTextsContainers.setVisibility(View.GONE);
- mIconContainerBg.setVisibility(View.GONE);
- mTextView.setVisibility(View.GONE);
- mBgEnd.setVisibility(View.GONE);
- mTextsContainers = null;
- mIconContainerBg = null;
- mTextView = null;
- mBgEnd = null;
-
- // Initially change the visibility to INVISIBLE, wait until and receives the size and
- // then animate it moving from "off" the screen correctly
- mIndicatorView.setVisibility(View.INVISIBLE);
+ R.layout.tv_audio_recording_indicator, null);
+
+ // 1. Set alpha to 0.
+ // 2. Wait until the window is shown and the view is laid out.
+ // 3. Start a "fade in" (alpha) animation.
+ mIndicatorView.setAlpha(0f);
mIndicatorView
.getViewTreeObserver()
.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (mState == STATE_STOPPED) {
- return;
- }
+ // State could have changed to NOT_SHOWN (if all the recorders are
+ // already gone) to STOPPED (if the indicator was disabled)
+ if (mState != STATE_APPEARING) return;
+ mViewAndWindowAdded = true;
// Remove the observer
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
this);
- // Now that the width of the indicator has been assigned, we can
- // move it in from off the screen.
- final int initialOffset =
- (mIsLtr ? 1 : -1) * mIndicatorView.getWidth();
- final AnimatorSet set = new AnimatorSet();
- set.setDuration(ANIMATION_DURATION);
- set.playTogether(
- ObjectAnimator.ofFloat(mIndicatorView,
- View.TRANSLATION_X, initialOffset, 0),
- ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
- 1f));
- set.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation,
- boolean isReverse) {
- if (mState == STATE_STOPPED) return;
-
- // Indicator is INVISIBLE at the moment, change it.
- mIndicatorView.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- onAppeared();
- }
- });
- set.start();
+ animateAppearance();
}
});
+ final boolean isLtr = mContext.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WRAP_CONTENT,
WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
- layoutParams.gravity = Gravity.TOP | (mIsLtr ? Gravity.RIGHT : Gravity.LEFT);
+ layoutParams.gravity = Gravity.TOP | (isLtr ? Gravity.RIGHT : Gravity.LEFT);
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
layoutParams.packageName = mContext.getPackageName();
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
windowManager.addView(mIndicatorView, layoutParams);
-
- mState = STATE_APPEARING;
}
- @UiThread
- private void hide() {
- if (DEBUG) Log.d(TAG, "Hide indicator");
-
- final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
- - (int) mIconTextsContainer.getTranslationX());
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(
- ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
- ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
- set.setDuration(ANIMATION_DURATION);
- set.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- onHidden();
- }
- });
- set.start();
- mState = STATE_DISAPPEARING;
+ private void animateAppearance() {
+ animateAlphaTo(1f);
+ }
+
+ private void animateDisappearance() {
+ animateAlphaTo(0f);
}
+ private void animateAlphaTo(final float endValue) {
+ if (mAnimator == null) {
+ if (DEBUG) Log.d(TAG, "set up animator");
- @UiThread
- private void onAppeared() {
- if (mState == STATE_STOPPED) return;
+ mAnimator = new ObjectAnimator();
+ mAnimator.setTarget(mIndicatorView);
+ mAnimator.setProperty(View.ALPHA);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ boolean mCancelled;
- mState = STATE_SHOWN;
+ @Override
+ public void onAnimationStart(Animator animation, boolean isReverse) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationStart");
+ mCancelled = false;
+ }
- hideIndicatorIfNeeded();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationCancel");
+ mCancelled = true;
+ }
- @UiThread
- private void onHidden() {
- if (mState == STATE_STOPPED) return;
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (DEBUG) Log.d(TAG, "AnimatorListenerAdapter#onAnimationEnd");
+ // When ValueAnimator#cancel() is called it always calls onAnimationCancel(...)
+ // and then onAnimationEnd(...). We, however, only want to proceed here if the
+ // animation ended "naturally".
+ if (!mCancelled) {
+ onAnimationFinished();
+ }
+ }
+ });
+ } else if (mAnimator.isRunning()) {
+ if (DEBUG) Log.d(TAG, "cancel running animation");
+ mAnimator.cancel();
+ }
- removeIndicatorView();
- mState = STATE_NOT_SHOWN;
+ final float currentValue = mIndicatorView.getAlpha();
+ if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);
- if (hasActiveRecorders()) {
- // Got new recorders, show again.
- showIfNotShown();
+ mAnimator.setDuration((int) (Math.abs(currentValue - endValue) * ANIMATION_DURATION_MS));
+ mAnimator.setFloatValues(endValue);
+ mAnimator.start();
+ }
+
+ private void onAnimationFinished() {
+ if (DEBUG) Log.d(TAG, "onAnimationFinished");
+
+ if (mState == STATE_APPEARING) {
+ mState = STATE_SHOWN;
+ } else if (mState == STATE_DISAPPEARING) {
+ removeIndicatorView();
+ mState = STATE_NOT_SHOWN;
}
}
@@ -358,17 +344,16 @@ public class AudioRecordingDisclosureBar implements
}
private void removeIndicatorView() {
+ if (DEBUG) Log.d(TAG, "removeIndicatorView");
+
final WindowManager windowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
windowManager.removeView(mIndicatorView);
mIndicatorView = null;
- mIconTextsContainer = null;
- mIconContainerBg = null;
- mIcon = null;
- mTextsContainers = null;
- mTextView = null;
- mBgEnd = null;
+ mAnimator = null;
+
+ mViewAndWindowAdded = false;
}
private static List<String> splitByComma(String string) {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
new file mode 100644
index 000000000000..e9fcf1aa9598
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -0,0 +1,139 @@
+/*
+ * 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.toast;
+
+import android.animation.Animator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.View;
+import android.widget.ToastPresenter;
+
+import com.android.internal.R;
+import com.android.systemui.plugins.ToastPlugin;
+
+/**
+ * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class
+ * directly. Instead, use {@link ToastFactory#createToast}.
+ */
+public class SystemUIToast implements ToastPlugin.Toast {
+ final Context mContext;
+ final CharSequence mText;
+ final ToastPlugin.Toast mPluginToast;
+
+ final int mDefaultGravity;
+ final int mDefaultY;
+ final int mDefaultX = 0;
+ final int mDefaultHorizontalMargin = 0;
+ final int mDefaultVerticalMargin = 0;
+
+ SystemUIToast(Context context, CharSequence text) {
+ this(context, text, null);
+ }
+
+ SystemUIToast(Context context, CharSequence text, ToastPlugin.Toast pluginToast) {
+ mContext = context;
+ mText = text;
+ mPluginToast = pluginToast;
+
+ mDefaultGravity = context.getResources().getInteger(R.integer.config_toastDefaultGravity);
+ mDefaultY = context.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+ }
+
+ @Override
+ @NonNull
+ public Integer getGravity() {
+ if (isPluginToast() && mPluginToast.getGravity() != null) {
+ return mPluginToast.getGravity();
+ }
+ return mDefaultGravity;
+ }
+
+ @Override
+ @NonNull
+ public Integer getXOffset() {
+ if (isPluginToast() && mPluginToast.getXOffset() != null) {
+ return mPluginToast.getXOffset();
+ }
+ return mDefaultX;
+ }
+
+ @Override
+ @NonNull
+ public Integer getYOffset() {
+ if (isPluginToast() && mPluginToast.getYOffset() != null) {
+ return mPluginToast.getYOffset();
+ }
+ return mDefaultY;
+ }
+
+ @Override
+ @NonNull
+ public Integer getHorizontalMargin() {
+ if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) {
+ return mPluginToast.getHorizontalMargin();
+ }
+ return mDefaultHorizontalMargin;
+ }
+
+ @Override
+ @NonNull
+ public Integer getVerticalMargin() {
+ if (isPluginToast() && mPluginToast.getVerticalMargin() != null) {
+ return mPluginToast.getVerticalMargin();
+ }
+ return mDefaultVerticalMargin;
+ }
+
+ @Override
+ @NonNull
+ public View getView() {
+ if (isPluginToast() && mPluginToast.getView() != null) {
+ return mPluginToast.getView();
+ }
+ return ToastPresenter.getTextToastView(mContext, mText);
+ }
+
+ @Override
+ @Nullable
+ public Animator getInAnimation() {
+ if (isPluginToast() && mPluginToast.getInAnimation() != null) {
+ return mPluginToast.getInAnimation();
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Animator getOutAnimation() {
+ if (isPluginToast() && mPluginToast.getOutAnimation() != null) {
+ return mPluginToast.getOutAnimation();
+ }
+ return null;
+ }
+
+ /**
+ * Whether this toast has a custom animation.
+ */
+ public boolean hasCustomAnimation() {
+ return getInAnimation() != null || getOutAnimation() != null;
+ }
+
+ private boolean isPluginToast() {
+ return mPluginToast != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
new file mode 100644
index 000000000000..d8cb61c6b349
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -0,0 +1,83 @@
+/*
+ * 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.toast;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.ToastPlugin;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+
+/**
+ * Factory for creating toasts to be shown by ToastUI.
+ * These toasts can be customized by {@link ToastPlugin}.
+ */
+@SysUISingleton
+public class ToastFactory implements Dumpable {
+ // only one ToastPlugin can be connected at a time.
+ private ToastPlugin mPlugin;
+
+ @Inject
+ public ToastFactory(PluginManager pluginManager, DumpManager dumpManager) {
+ dumpManager.registerDumpable("ToastFactory", this);
+ pluginManager.addPluginListener(
+ new PluginListener<ToastPlugin>() {
+ @Override
+ public void onPluginConnected(ToastPlugin plugin, Context pluginContext) {
+ mPlugin = plugin;
+ }
+
+ @Override
+ public void onPluginDisconnected(ToastPlugin plugin) {
+ if (plugin.equals(mPlugin)) {
+ mPlugin = null;
+ }
+ }
+ }, ToastPlugin.class, false /* Allow multiple plugins */);
+ }
+
+ /**
+ * Create a toast to be shown by ToastUI.
+ */
+ public SystemUIToast createToast(Context context, CharSequence text, String packageName,
+ int userId) {
+ if (isPluginAvailable()) {
+ return new SystemUIToast(context, text, mPlugin.createToast(text, packageName, userId));
+ }
+ return new SystemUIToast(context, text);
+ }
+
+ private boolean isPluginAvailable() {
+ return mPlugin != null;
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("ToastFactory:");
+ pw.println(" mAttachedPlugin=" + mPlugin);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
new file mode 100644
index 000000000000..78173cf62a93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.toast
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.ToastLog
+import javax.inject.Inject
+
+private const val TAG = "ToastLog"
+
+class ToastLogger @Inject constructor(
+ @ToastLog private val buffer: LogBuffer
+) {
+
+ fun logOnShowToast(uid: Int, packageName: String, text: String, token: String) {
+ log(DEBUG, {
+ int1 = uid
+ str1 = packageName
+ str2 = text
+ str3 = token
+ }, {
+ "[$str3] Show toast for ($str1, $int1). msg=\'$str2\'"
+ })
+ }
+
+ fun logOnHideToast(packageName: String, token: String) {
+ log(DEBUG, {
+ str1 = packageName
+ str2 = token
+ }, {
+ "[$str2] Hide toast for [$str1]"
+ })
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index a2203732c47c..1c682e3bb7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -16,25 +16,27 @@
package com.android.systemui.toast;
+import android.animation.Animator;
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.ITransientNotificationCallback;
import android.content.Context;
-import android.content.res.Resources;
import android.os.IBinder;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
-import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
+import android.widget.Toast;
import android.widget.ToastPresenter;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.Objects;
@@ -45,35 +47,53 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
+ // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY
+ private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds
+ private static final int TOAST_SHORT_TIME = 2000; // 2 seconds
+
private static final String TAG = "ToastUI";
private final CommandQueue mCommandQueue;
private final INotificationManager mNotificationManager;
- private final IAccessibilityManager mAccessibilityManager;
- private final int mGravity;
- private final int mY;
+ private final IAccessibilityManager mIAccessibilityManager;
+ private final AccessibilityManager mAccessibilityManager;
+ private final ToastFactory mToastFactory;
+ private final DelayableExecutor mMainExecutor;
+ private final ToastLogger mToastLogger;
+ private SystemUIToast mToast;
@Nullable private ToastPresenter mPresenter;
@Nullable private ITransientNotificationCallback mCallback;
@Inject
- public ToastUI(Context context, CommandQueue commandQueue) {
+ public ToastUI(
+ Context context,
+ CommandQueue commandQueue,
+ ToastFactory toastFactory,
+ @Main DelayableExecutor mainExecutor,
+ ToastLogger toastLogger) {
this(context, commandQueue,
INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)));
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)),
+ toastFactory,
+ mainExecutor,
+ toastLogger);
}
@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
- @Nullable IAccessibilityManager accessibilityManager) {
+ @Nullable IAccessibilityManager accessibilityManager,
+ ToastFactory toastFactory, DelayableExecutor mainExecutor, ToastLogger toastLogger
+ ) {
super(context);
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
- mAccessibilityManager = accessibilityManager;
- Resources resources = mContext.getResources();
- mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
- mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
+ mIAccessibilityManager = accessibilityManager;
+ mToastFactory = toastFactory;
+ mMainExecutor = mainExecutor;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+ mToastLogger = toastLogger;
}
@Override
@@ -88,12 +108,31 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
if (mPresenter != null) {
hideCurrentToast();
}
- Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);
- View view = ToastPresenter.getTextToastView(context, text);
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+ Context context = mContext.createContextAsUser(userHandle, 0);
+ mToast = mToastFactory.createToast(context, text, packageName, userHandle.getIdentifier());
+
+ if (mToast.hasCustomAnimation()) {
+ if (mToast.getInAnimation() != null) {
+ mToast.getInAnimation().start();
+ }
+ final Animator hideAnimator = mToast.getOutAnimation();
+ if (hideAnimator != null) {
+ final long durationMillis = duration == Toast.LENGTH_LONG
+ ? TOAST_LONG_TIME : TOAST_SHORT_TIME;
+ final long updatedDuration = mAccessibilityManager.getRecommendedTimeoutMillis(
+ (int) durationMillis, AccessibilityManager.FLAG_CONTENT_TEXT);
+ mMainExecutor.executeDelayed(() -> hideAnimator.start(),
+ updatedDuration - hideAnimator.getTotalDuration());
+ }
+ }
mCallback = callback;
- mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,
+ mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
packageName);
- mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
+ mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
+ mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
+ mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
+ mToast.getVerticalMargin(), mCallback, mToast.hasCustomAnimation());
}
@Override
@@ -104,6 +143,7 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
return;
}
+ mToastLogger.logOnHideToast(packageName, token.toString());
hideCurrentToast();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 0070dcf9a604..4c724aeea9ae 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,6 +15,7 @@
*/
package com.android.systemui.tuner;
+import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -22,6 +23,7 @@ import android.content.DialogInterface;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
@@ -122,7 +124,8 @@ public class TunerFragment extends PreferenceFragment {
getActivity().finish();
return true;
case MENU_REMOVE:
- TunerService.showResetRequest(getContext(), new Runnable() {
+ UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
+ TunerService.showResetRequest(getContext(), user, new Runnable() {
@Override
public void run() {
if (getActivity() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 338e1781abd0..70bba263ab90 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -14,7 +14,6 @@
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -51,25 +50,24 @@ public abstract class TunerService {
void onTuningChanged(String key, String newValue);
}
- private static Context userContext(Context context) {
+ private static Context userContext(Context context, UserHandle user) {
try {
- return context.createPackageContextAsUser(context.getPackageName(), 0,
- new UserHandle(ActivityManager.getCurrentUser()));
+ return context.createPackageContextAsUser(context.getPackageName(), 0, user);
} catch (NameNotFoundException e) {
return context;
}
}
- public static final void setTunerEnabled(Context context, boolean enabled) {
- userContext(context).getPackageManager().setComponentEnabledSetting(
+ public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
+ userContext(context, user).getPackageManager().setComponentEnabledSetting(
new ComponentName(context, TunerActivity.class),
enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
- public static final boolean isTunerEnabled(Context context) {
- return userContext(context).getPackageManager().getComponentEnabledSetting(
+ public static final boolean isTunerEnabled(Context context, UserHandle user) {
+ return userContext(context, user).getPackageManager().getComponentEnabledSetting(
new ComponentName(context, TunerActivity.class))
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
@@ -83,7 +81,8 @@ public abstract class TunerService {
}
}
- public static final void showResetRequest(final Context context, final Runnable onDisabled) {
+ public static final void showResetRequest(final Context context, UserHandle user,
+ final Runnable onDisabled) {
SystemUIDialog dialog = new SystemUIDialog(context);
dialog.setShowForAllUsers(true);
dialog.setMessage(R.string.remove_from_settings_prompt);
@@ -91,20 +90,20 @@ public abstract class TunerService {
(OnClickListener) null);
dialog.setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.guest_exit_guest_dialog_remove), new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // Tell the tuner (in main SysUI process) to clear all its settings.
- context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
- // Disable access to tuner.
- TunerService.setTunerEnabled(context, false);
- // Make them sit through the warning dialog again.
- Settings.Secure.putInt(context.getContentResolver(),
- TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
- if (onDisabled != null) {
- onDisabled.run();
- }
- }
- });
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Tell the tuner (in main SysUI process) to clear all its settings.
+ context.sendBroadcast(new Intent(TunerService.ACTION_CLEAR));
+ // Disable access to tuner.
+ TunerService.setTunerEnabled(context, user, false);
+ // Make them sit through the warning dialog again.
+ Settings.Secure.putInt(context.getContentResolver(),
+ TunerFragment.SETTING_SEEN_TUNER_WARNING, 0);
+ if (onDisabled != null) {
+ onDisabled.run();
+ }
+ }
+ });
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index d9727a73f651..22f03e074b06 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -15,13 +15,13 @@
*/
package com.android.systemui.tuner;
-import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -37,7 +37,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSTileHost;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.util.leak.LeakDetector;
@@ -81,7 +81,8 @@ public class TunerServiceImpl extends TunerService {
private ContentResolver mContentResolver;
private int mCurrentUser;
- private CurrentUserTracker mUserTracker;
+ private UserTracker.Callback mCurrentUserTracker;
+ private UserTracker mUserTracker;
/**
*/
@@ -91,11 +92,13 @@ public class TunerServiceImpl extends TunerService {
@Main Handler mainHandler,
LeakDetector leakDetector,
DemoModeController demoModeController,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mLeakDetector = leakDetector;
mDemoModeController = demoModeController;
+ mUserTracker = userTracker;
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
@@ -104,21 +107,22 @@ public class TunerServiceImpl extends TunerService {
}
}
- mCurrentUser = ActivityManager.getCurrentUser();
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
+ mCurrentUser = mUserTracker.getUserId();
+ mCurrentUserTracker = new UserTracker.Callback() {
@Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
+ public void onUserChanged(int newUser, Context userContext) {
+ mCurrentUser = newUser;
reloadAll();
reregisterAll();
}
};
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mCurrentUserTracker,
+ new HandlerExecutor(mainHandler));
}
@Override
public void destroy() {
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mCurrentUserTracker);
}
private void upgradeTuner(int oldVersion, int newVersion, Handler mainHandler) {
@@ -137,7 +141,7 @@ public class TunerServiceImpl extends TunerService {
}
}
if (oldVersion < 2) {
- setTunerEnabled(mContext, false);
+ setTunerEnabled(mContext, mUserTracker.getUserHandle(), false);
}
// 3 Removed because of a revert.
if (oldVersion < 4) {
@@ -272,7 +276,7 @@ public class TunerServiceImpl extends TunerService {
@Override
public void onChange(boolean selfChange, java.util.Collection<Uri> uris,
int flags, int userId) {
- if (userId == ActivityManager.getCurrentUser()) {
+ if (userId == mUserTracker.getUserId()) {
for (Uri u : uris) {
reloadSetting(u);
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index 22fa0106795a..bde88b1b5533 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -17,11 +17,12 @@
package com.android.systemui.tv;
import com.android.systemui.dagger.GlobalRootComponent;
+import com.android.systemui.wmshell.TvPipModule;
import dagger.Binds;
import dagger.Module;
-@Module()
+@Module(includes = TvPipModule.class)
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
new file mode 100644
index 000000000000..890ee5f45309
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -0,0 +1,161 @@
+/*
+ * 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.user;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.users.EditUserInfoController;
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * This screen shows a Dialog for choosing nickname and photo for a new user, and then delegates the
+ * user creation to a UserCreator.
+ */
+public class CreateUserActivity extends Activity {
+
+ /**
+ * Creates an intent to start this activity.
+ */
+ public static Intent createIntentForStart(Context context) {
+ return new Intent(context, CreateUserActivity.class);
+ }
+
+ private static final String TAG = "CreateUserActivity";
+ private static final String DIALOG_STATE_KEY = "create_user_dialog_state";
+
+ private final UserCreator mUserCreator;
+ private final EditUserInfoController mEditUserInfoController;
+ private final IActivityManager mActivityManager;
+
+ private Dialog mSetupUserDialog;
+
+ @Inject
+ public CreateUserActivity(UserCreator userCreator,
+ EditUserInfoController editUserInfoController, IActivityManager activityManager) {
+ mUserCreator = userCreator;
+ mEditUserInfoController = editUserInfoController;
+ mActivityManager = activityManager;
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setShowWhenLocked(true);
+ setContentView(R.layout.activity_create_new_user);
+
+ if (savedInstanceState != null) {
+ mEditUserInfoController.onRestoreInstanceState(savedInstanceState);
+ }
+
+ mSetupUserDialog = createDialog();
+ mSetupUserDialog.show();
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ if (mSetupUserDialog != null && mSetupUserDialog.isShowing()) {
+ outState.putBundle(DIALOG_STATE_KEY, mSetupUserDialog.onSaveInstanceState());
+ }
+
+ mEditUserInfoController.onSaveInstanceState(outState);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ Bundle savedDialogState = savedInstanceState.getBundle(DIALOG_STATE_KEY);
+ if (savedDialogState != null && mSetupUserDialog != null) {
+ mSetupUserDialog.onRestoreInstanceState(savedDialogState);
+ }
+ }
+
+ private Dialog createDialog() {
+ String defaultUserName = getString(com.android.settingslib.R.string.user_new_user_name);
+
+ return mEditUserInfoController.createDialog(
+ this,
+ (intent, requestCode) -> {
+ mEditUserInfoController.startingActivityForResult();
+ startActivityForResult(intent, requestCode);
+ },
+ null,
+ defaultUserName,
+ getString(R.string.user_add_user),
+ this::addUserNow,
+ this::finish
+ );
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ if (mSetupUserDialog != null) {
+ mSetupUserDialog.dismiss();
+ }
+ }
+
+ private void addUserNow(String userName, Drawable userIcon) {
+ mSetupUserDialog.dismiss();
+
+ userName = (userName == null || userName.trim().isEmpty())
+ ? getString(R.string.user_new_user_name)
+ : userName;
+
+ mUserCreator.createUser(userName, userIcon,
+ userInfo -> {
+ switchToUser(userInfo.id);
+ finishIfNeeded();
+ }, () -> {
+ Log.e(TAG, "Unable to create user");
+ finishIfNeeded();
+ });
+ }
+
+ private void finishIfNeeded() {
+ if (!isFinishing() && !isDestroyed()) {
+ finish();
+ }
+ }
+
+ private void switchToUser(int userId) {
+ try {
+ mActivityManager.switchUser(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't switch user.", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
new file mode 100644
index 000000000000..3a270bb77e46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
@@ -0,0 +1,82 @@
+/*
+ * 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.user;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserManager;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.users.UserCreatingDialog;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+public class UserCreator {
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+
+ @Inject
+ public UserCreator(Context context, UserManager userManager) {
+ mContext = context;
+ mUserManager = userManager;
+ }
+
+ /**
+ * Shows a progress dialog then starts the user creation process on the main thread.
+ *
+ * @param successCallback is called when the user creation is successful.
+ * @param errorCallback is called when userManager.createUser returns null.
+ * (Exceptions are not handled by this class)
+ */
+ public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
+ Runnable errorCallback) {
+
+ Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
+ userCreationProgressDialog.show();
+
+ // userManager.createUser will block the thread so post is needed for the dialog to show
+ ThreadUtils.postOnMainThread(() -> {
+ UserInfo user =
+ mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
+ if (user == null) {
+ // Couldn't create user for some reason
+ userCreationProgressDialog.dismiss();
+ errorCallback.run();
+ return;
+ }
+
+ Drawable newUserIcon = userIcon;
+ if (newUserIcon == null) {
+ newUserIcon = UserIcons.getDefaultUserIcon(mContext.getResources(), user.id, false);
+ }
+ mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon));
+
+ userCreationProgressDialog.dismiss();
+ successCallback.accept(user);
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
new file mode 100644
index 000000000000..0ad0984e8231
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -0,0 +1,36 @@
+/*
+ * 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.user;
+
+import com.android.settingslib.users.EditUserInfoController;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for User related classes.
+ */
+@Module
+public class UserModule {
+
+ private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
+
+ @Provides
+ EditUserInfoController provideEditUserInfoController() {
+ return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index a6cd350b33ce..344f0d2f5506 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -27,7 +27,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -93,10 +92,6 @@ public class InjectionInflationController {
}
/**
- * Creates the QuickStatusBarHeader.
- */
- QuickStatusBarHeader createQsHeader();
- /**
* Creates the QSFooterImpl.
*/
QSFooterImpl createQsFooter();
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 90b95ea9562e..0d63324966fd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -25,6 +25,7 @@ import android.provider.Settings;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
+import com.android.wm.shell.pip.tv.PipNotification;
import java.util.Arrays;
@@ -34,8 +35,8 @@ public class NotificationChannels extends SystemUI {
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
public static String GENERAL = "GEN";
public static String STORAGE = "DSK";
- public static String TVPIP = "TPP";
public static String BATTERY = "BAT";
+ public static String TVPIP = PipNotification.NOTIFICATION_CHANNEL_TVPIP;
public static String HINTS = "HNT";
public NotificationChannels(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 3347cf6ca2a4..603d423143ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -46,6 +46,7 @@ class TransitionLayout @JvmOverloads constructor(
private var measureAsConstraint: Boolean = false
private var currentState: TransitionViewState = TransitionViewState()
private var updateScheduled = false
+ private var isPreDrawApplicatorRegistered = false
private var desiredMeasureWidth = 0
private var desiredMeasureHeight = 0
@@ -74,6 +75,7 @@ class TransitionLayout @JvmOverloads constructor(
override fun onPreDraw(): Boolean {
updateScheduled = false
viewTreeObserver.removeOnPreDrawListener(this)
+ isPreDrawApplicatorRegistered = false
applyCurrentState()
return true
}
@@ -94,6 +96,14 @@ class TransitionLayout @JvmOverloads constructor(
}
}
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ if (isPreDrawApplicatorRegistered) {
+ viewTreeObserver.removeOnPreDrawListener(preDrawApplicator)
+ isPreDrawApplicatorRegistered = false
+ }
+ }
+
/**
* Apply the current state to the view and its widgets
*/
@@ -158,7 +168,10 @@ class TransitionLayout @JvmOverloads constructor(
private fun applyCurrentStateOnPredraw() {
if (!updateScheduled) {
updateScheduled = true
- viewTreeObserver.addOnPreDrawListener(preDrawApplicator)
+ if (!isPreDrawApplicatorRegistered) {
+ viewTreeObserver.addOnPreDrawListener(preDrawApplicator)
+ isPreDrawApplicatorRegistered = true
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
index aa50292edbf7..71b255229c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java
@@ -199,7 +199,7 @@ class ThresholdSensorImpl implements ThresholdSensor {
@Override
public String toString() {
- return String.format("{registered=%s, paused=%s, threshold=%s, sensor=%s}",
+ return String.format("{isLoaded=%s, registered=%s, paused=%s, threshold=%s, sensor=%s}",
isLoaded(), mRegistered, mPaused, mThreshold, mSensor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 78f83d3c09b4..735338f84267 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -90,6 +90,7 @@ import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
@@ -122,8 +123,11 @@ public class VolumeDialogImpl implements VolumeDialog,
static final int DIALOG_SAFETYWARNING_TIMEOUT_MILLIS = 5000;
static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000;
static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000;
- static final int DIALOG_SHOW_ANIMATION_DURATION = 300;
- static final int DIALOG_HIDE_ANIMATION_DURATION = 250;
+
+ private final int mDialogShowAnimationDurationMs;
+ private final int mDialogHideAnimationDurationMs;
+ private final boolean mShowLowMediaVolumeIcon;
+ private final boolean mChangeVolumeRowTintWhenInactive;
private final Context mContext;
private final H mHandler = new H();
@@ -154,8 +158,6 @@ public class VolumeDialogImpl implements VolumeDialog,
private boolean mShowing;
private boolean mShowA11yStream;
- private final boolean mShowLowMediaVolumeIcon;
-
private int mActiveStream;
private int mPrevActiveStream;
private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -183,6 +185,12 @@ public class VolumeDialogImpl implements VolumeDialog,
Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
mShowLowMediaVolumeIcon =
mContext.getResources().getBoolean(R.bool.config_showLowMediaVolumeIcon);
+ mChangeVolumeRowTintWhenInactive =
+ mContext.getResources().getBoolean(R.bool.config_changeVolumeRowTintWhenInactive);
+ mDialogShowAnimationDurationMs =
+ mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs);
+ mDialogHideAnimationDurationMs =
+ mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs);
}
@Override
@@ -272,7 +280,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialogView.animate()
.alpha(1)
.translationX(0)
- .setDuration(DIALOG_SHOW_ANIMATION_DURATION)
+ .setDuration(mDialogShowAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
.withEndAction(() -> {
if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
@@ -512,6 +520,7 @@ public class VolumeDialogImpl implements VolumeDialog,
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
+ Dependency.get(MediaOutputDialogFactory.class).dismiss();
Dependency.get(ActivityStarter.class).startActivity(intent,
true /* dismissShade */);
});
@@ -592,7 +601,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mODICaptionsTooltipView.setAlpha(0.f);
mODICaptionsTooltipView.animate()
.alpha(1.f)
- .setStartDelay(DIALOG_SHOW_ANIMATION_DURATION)
+ .setStartDelay(mDialogShowAnimationDurationMs)
.withEndAction(() -> {
if (D.BUG) Log.d(TAG, "tool:checkODICaptionsTooltip() putBoolean true");
Prefs.putBoolean(mContext,
@@ -614,7 +623,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mODICaptionsTooltipView.animate()
.alpha(0.f)
.setStartDelay(0)
- .setDuration(DIALOG_HIDE_ANIMATION_DURATION)
+ .setDuration(mDialogHideAnimationDurationMs)
.withEndAction(() -> mODICaptionsTooltipView.setVisibility(INVISIBLE))
.start();
}
@@ -793,7 +802,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialogView.setAlpha(1);
ViewPropertyAnimator animator = mDialogView.animate()
.alpha(0)
- .setDuration(DIALOG_HIDE_ANIMATION_DURATION)
+ .setDuration(mDialogHideAnimationDurationMs)
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.withEndAction(() -> mHandler.postDelayed(() -> {
mDialog.dismiss();
@@ -1076,7 +1085,7 @@ public class VolumeDialogImpl implements VolumeDialog,
iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt;
} else if (isStreamMuted(ss)) {
- iconRes = row.iconMuteRes;
+ iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
iconRes = mShowLowMediaVolumeIcon && ss.level * 2 < (ss.levelMax + ss.levelMin)
? R.drawable.ic_volume_media_low : row.iconRes;
@@ -1154,6 +1163,9 @@ public class VolumeDialogImpl implements VolumeDialog,
row.slider.requestFocus();
}
boolean useActiveColoring = isActive && row.slider.isEnabled();
+ if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) {
+ return;
+ }
final ColorStateList tint = useActiveColoring
? Utils.getColorAccent(mContext)
: Utils.getColorAttr(mContext, android.R.attr.colorForeground);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
new file mode 100644
index 000000000000..f55445ca1de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -0,0 +1,106 @@
+/*
+ * 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 com.android.systemui.wmshell;
+
+import android.content.Context;
+import android.os.Handler;
+import android.view.LayoutInflater;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.tv.PipController;
+import com.android.wm.shell.pip.tv.PipControlsView;
+import com.android.wm.shell.pip.tv.PipControlsViewController;
+import com.android.wm.shell.pip.tv.PipNotification;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger module for TV Pip.
+ */
+@Module
+public abstract class TvPipModule {
+
+ @SysUISingleton
+ @Provides
+ static Pip providePipController(Context context,
+ PipBoundsHandler pipBoundsHandler,
+ PipTaskOrganizer pipTaskOrganizer,
+ WindowManagerShellWrapper windowManagerShellWrapper) {
+ return new PipController(context, pipBoundsHandler, pipTaskOrganizer,
+ windowManagerShellWrapper);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipControlsViewController providePipControlsViewContrller(
+ PipControlsView pipControlsView, PipController pipController,
+ LayoutInflater layoutInflater, Handler handler) {
+ return new PipControlsViewController(pipControlsView, pipController, layoutInflater,
+ handler);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipControlsView providePipControlsView(Context context) {
+ return new PipControlsView(context, null);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipNotification providePipNotification(Context context,
+ PipController pipController) {
+ return new PipNotification(context, pipController);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsHandler providePipBoundsHandler(Context context) {
+ return new PipBoundsHandler(context);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsState providePipBoundsState() {
+ return new PipBoundsState();
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipTaskOrganizer providePipTaskOrganizer(Context context,
+ PipBoundsState pipBoundsState,
+ PipBoundsHandler pipBoundsHandler,
+ PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+ Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
+ PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+ return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
+ pipSurfaceTransactionHelper, splitScreenOptional, displayController,
+ pipUiEventLogger, shellTaskOrganizer);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 0869cf739d02..56efffc29d85 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,26 +20,18 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.pip.tv.PipController;
-import com.android.systemui.pip.tv.PipNotification;
-import com.android.systemui.pip.tv.dagger.TvPipComponent;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import java.util.Optional;
+import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@@ -49,60 +41,23 @@ import dagger.Provides;
* branches of SystemUI.
*/
// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
-@Module(includes = WMShellBaseModule.class, subcomponents = {TvPipComponent.class})
+@Module(includes = {WMShellBaseModule.class, TvPipModule.class})
public class TvWMShellModule {
@SysUISingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @Main Handler mainHandler,
+ DisplayController displayController, @Main Executor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
+ return new DisplayImeController(wmService, displayController, mainExecutor,
+ transactionPool);
}
- @SysUISingleton
- @Provides
- static Pip providePipController(Context context,
- BroadcastDispatcher broadcastDispatcher,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- PipTaskOrganizer pipTaskOrganizer) {
- return new PipController(context, broadcastDispatcher, pipBoundsHandler,
- pipSurfaceTransactionHelper, pipTaskOrganizer);
- }
-
- @SysUISingleton
- @Provides
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, @Main Handler handler,
- TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+ TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue) {
return new SplitScreenController(context, displayController, systemWindows,
- displayImeController, handler, transactionPool, shellTaskOrganizer);
- }
-
- @SysUISingleton
- @Provides
- static PipNotification providePipNotification(Context context,
- BroadcastDispatcher broadcastDispatcher,
- PipController pipController) {
- return new PipNotification(context, broadcastDispatcher, pipController);
- }
-
- @SysUISingleton
- @Provides
- static PipBoundsHandler providesPipBoundsHandler(Context context) {
- return new PipBoundsHandler(context);
- }
-
- @SysUISingleton
- @Provides
- static PipTaskOrganizer providesPipTaskOrganizer(Context context,
- PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
- Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
- PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
- return new PipTaskOrganizer(context, pipBoundsHandler,
- pipSurfaceTransactionHelper, splitScreenOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer);
+ displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ce125f3fdce0..9281a090fd97 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -16,34 +16,52 @@
package com.android.systemui.wmshell;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -53,11 +71,12 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Optional;
@@ -70,10 +89,22 @@ import javax.inject.Inject;
@SysUISingleton
public final class WMShell extends SystemUI
implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> {
+ private static final String TAG = WMShell.class.getName();
+ private static final int INVALID_SYSUI_STATE_MASK =
+ SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+ | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
+ | SYSUI_STATE_BOUNCER_SHOWING
+ | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+ | SYSUI_STATE_BUBBLES_EXPANDED
+ | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+
private final CommandQueue mCommandQueue;
+ private final ConfigurationController mConfigurationController;
private final DisplayImeController mDisplayImeController;
+ private final InputConsumerController mInputConsumerController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final TaskStackChangeListeners mTaskStackChangeListeners;
private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
@@ -84,14 +115,17 @@ public final class WMShell extends SystemUI
// are non-optional windowing features like FULLSCREEN.
private final ShellTaskOrganizer mShellTaskOrganizer;
private final ProtoTracer mProtoTracer;
-
+ private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
+ private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
@Inject
public WMShell(Context context, CommandQueue commandQueue,
+ ConfigurationController configurationController,
+ InputConsumerController inputConsumerController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- ActivityManagerWrapper activityManagerWrapper,
+ TaskStackChangeListeners taskStackChangeListeners,
DisplayImeController displayImeController,
NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
@@ -103,9 +137,10 @@ public final class WMShell extends SystemUI
ProtoTracer protoTracer) {
super(context);
mCommandQueue = commandQueue;
- mCommandQueue.addCallback(this);
+ mConfigurationController = configurationController;
+ mInputConsumerController = inputConsumerController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mActivityManagerWrapper = activityManagerWrapper;
+ mTaskStackChangeListeners = taskStackChangeListeners;
mDisplayImeController = displayImeController;
mNavigationModeController = navigationModeController;
mScreenLifecycle = screenLifecycle;
@@ -120,6 +155,7 @@ public final class WMShell extends SystemUI
@Override
public void start() {
+ mCommandQueue.addCallback(this);
// This is to prevent circular init problem by separating registration step out of its
// constructor. And make sure the initialization of DisplayImeController won't depend on
// specific feature anymore.
@@ -131,12 +167,99 @@ public final class WMShell extends SystemUI
@VisibleForTesting
void initPip(Pip pip) {
+ if (!PipUtils.hasSystemFeature(mContext)) {
+ return;
+ }
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void showPictureInPictureMenu() {
pip.showPictureInPictureMenu();
}
});
+
+ mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ pip.hidePipMenu(null, null);
+ }
+ }
+ };
+ mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
+
+ mSysUiState.addCallback(sysUiStateFlag -> {
+ mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
+ pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
+ });
+
+ mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ pip.onDensityOrFontScaleChanged();
+ }
+
+ @Override
+ public void onOverlayChanged() {
+ pip.onOverlayChanged();
+ }
+ });
+
+ // Handle for system task stack changes.
+ mTaskStackChangeListeners.registerTaskStackListener(
+ new TaskStackChangeListener() {
+ @Override
+ public void onTaskStackChanged() {
+ pip.onTaskStackChanged();
+ }
+
+ @Override
+ public void onActivityPinned(String packageName, int userId, int taskId,
+ int stackId) {
+ pip.onActivityPinned(packageName);
+ mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ final Pair<ComponentName, Integer> topPipActivityInfo =
+ PipUtils.getTopPipActivity(
+ mContext, ActivityManager.getService());
+ final ComponentName topActivity = topPipActivityInfo.first;
+ pip.onActivityUnpinned(topActivity);
+ mInputConsumerController.unregisterInputConsumer();
+ }
+
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ pip.onActivityRestartAttempt(task, clearedTask);
+ }
+ });
+
+ try {
+ RootTaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ if (taskInfo != null) {
+ // If SystemUI restart, and it already existed a pinned stack,
+ // register the pip input consumer to ensure touch can send to it.
+ mInputConsumerController.registerInputConsumer(true /* withSfVsync */);
+ }
+ } catch (RemoteException | UnsupportedOperationException e) {
+ Log.e(TAG, "Failed to register pinned stack listener", e);
+ e.printStackTrace();
+ }
+
+ // Register the listener for input consumer touch events. Only for Phone
+ if (pip.getPipTouchHandler() != null) {
+ mInputConsumerController.setInputListener(pip.getPipTouchHandler()::handleTouchEvent);
+ mInputConsumerController.setRegistrationListener(
+ pip.getPipTouchHandler()::onRegistrationChanged);
+ }
+
+ // The media session listener needs to be re-registered when switching users
+ UserInfoController userInfoController = Dependency.get(UserInfoController.class);
+ userInfoController.addCallback((String name, Drawable picture, String userAccount) ->
+ pip.registerSessionListenerForCurrentUser());
}
@VisibleForTesting
@@ -153,7 +276,7 @@ public final class WMShell extends SystemUI
};
mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
- mActivityManagerWrapper.registerTaskStackListener(
+ mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -189,10 +312,6 @@ public final class WMShell extends SystemUI
@VisibleForTesting
void initOneHanded(OneHanded oneHanded) {
- if (!oneHanded.hasOneHandedFeature()) {
- return;
- }
-
int currentMode = mNavigationModeController.addListener(mode ->
oneHanded.setThreeButtonModeEnabled(mode == NAV_BAR_MODE_3BUTTON));
oneHanded.setThreeButtonModeEnabled(currentMode == NAV_BAR_MODE_3BUTTON);
@@ -269,7 +388,7 @@ public final class WMShell extends SystemUI
}
});
- mActivityManagerWrapper.registerTaskStackListener(
+ mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -300,8 +419,8 @@ public final class WMShell extends SystemUI
if (handleLoggingCommand(args, pw)) {
return;
}
-
// Dump WMShell stuff here if no commands were handled
+ mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 7c129ac92fe3..09678b5d1772 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.wmshell;
+import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
@@ -23,22 +24,33 @@ import android.util.DisplayMetrics;
import android.view.IWindowManager;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.common.AnimationThread;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipMediaController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
+import java.util.Optional;
+
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
@@ -71,12 +83,33 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
+ static InputConsumerController provideInputConsumerController() {
+ return InputConsumerController.getPipInputConsumer();
+ }
+
+ @SysUISingleton
+ @Provides
static FloatingContentCoordinator provideFloatingContentCoordinator() {
return new FloatingContentCoordinator();
}
@SysUISingleton
@Provides
+ static PipAppOpsListener providePipAppOpsListener(Context context,
+ IActivityManager activityManager,
+ PipTouchHandler pipTouchHandler) {
+ return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipMediaController providePipMediaController(Context context,
+ IActivityManager activityManager) {
+ return new PipMediaController(context, activityManager);
+ }
+
+ @SysUISingleton
+ @Provides
static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
PackageManager packageManager) {
return new PipUiEventLogger(uiEventLogger, packageManager);
@@ -84,9 +117,8 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
- static PipSurfaceTransactionHelper providesPipSurfaceTransactionHelper(Context context,
- ConfigurationController configController) {
- return new PipSurfaceTransactionHelper(context, configController);
+ static PipSurfaceTransactionHelper providesPipSurfaceTransactionHelper(Context context) {
+ return new PipSurfaceTransactionHelper(context);
}
@SysUISingleton
@@ -98,14 +130,29 @@ public abstract class WMShellBaseModule {
@SysUISingleton
@Provides
- static ShellTaskOrganizer provideShellTaskOrganizer(TransactionPool transactionPool) {
- ShellTaskOrganizer organizer = new ShellTaskOrganizer(transactionPool);
+ static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler,
+ TransactionPool pool) {
+ return new SyncTransactionQueue(pool, handler);
+ }
+
+ @SysUISingleton
+ @Provides
+ static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
+ @Main Handler handler, TransactionPool transactionPool) {
+ ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool,
+ new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
organizer.registerOrganizer();
return organizer;
}
@SysUISingleton
@Provides
+ static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
+ return new WindowManagerShellWrapper();
+ }
+
+ @SysUISingleton
+ @Provides
static FlingAnimationUtils.Builder provideFlingAnimationUtilsBuilder(
DisplayMetrics displayMetrics) {
return new FlingAnimationUtils.Builder(displayMetrics);
@@ -118,5 +165,12 @@ public abstract class WMShellBaseModule {
abstract SplitScreen optionalSplitScreen();
@BindsOptionalOf
- abstract OneHanded optionalOneHanded();
+ abstract Bubbles optionalBubbles();
+
+ @SysUISingleton
+ @Provides
+ static Optional<OneHanded> provideOneHandedController(Context context,
+ DisplayController displayController) {
+ return Optional.ofNullable(OneHandedController.create(context, displayController));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 16fb2cacc950..975757a4c259 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -20,30 +20,32 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.pip.PipBoundsHandler;
-import com.android.systemui.pip.PipSurfaceTransactionHelper;
-import com.android.systemui.pip.PipTaskOrganizer;
-import com.android.systemui.pip.PipUiEventLogger;
-import com.android.systemui.pip.phone.PipController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.FloatingContentCoordinator;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.onehanded.OneHanded;
-import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipBoundsHandler;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipAppOpsListener;
+import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipMediaController;
+import com.android.wm.shell.pip.phone.PipMenuActivityController;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Optional;
+import java.util.concurrent.Executor;
import dagger.Module;
import dagger.Provides;
@@ -58,29 +60,28 @@ public class WMShellModule {
@SysUISingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, @Main Handler mainHandler,
+ DisplayController displayController, @Main Executor mainExecutor,
TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, mainHandler, transactionPool);
+ return new DisplayImeController(wmService, displayController, mainExecutor,
+ transactionPool);
}
@SysUISingleton
@Provides
static Pip providePipController(Context context,
- BroadcastDispatcher broadcastDispatcher,
- ConfigurationController configController,
- DeviceConfigProxy deviceConfig,
DisplayController displayController,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
+ PipAppOpsListener pipAppOpsListener,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper surfaceTransactionHelper,
+ PipBoundsState pipBoundsState,
+ PipMediaController pipMediaController,
+ PipMenuActivityController pipMenuActivityController,
PipTaskOrganizer pipTaskOrganizer,
- PipUiEventLogger pipUiEventLogger) {
- return new PipController(context, broadcastDispatcher, configController, deviceConfig,
- displayController, floatingContentCoordinator, sysUiState, pipBoundsHandler,
- surfaceTransactionHelper,
- pipTaskOrganizer,
- pipUiEventLogger);
+ PipTouchHandler pipTouchHandler,
+ WindowManagerShellWrapper windowManagerShellWrapper) {
+ return new PipController(context, displayController,
+ pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
+ pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
+ windowManagerShellWrapper);
}
@SysUISingleton
@@ -88,9 +89,16 @@ public class WMShellModule {
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, @Main Handler handler,
- TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer) {
+ TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue) {
return new SplitScreenController(context, displayController, systemWindows,
- displayImeController, handler, transactionPool, shellTaskOrganizer);
+ displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipBoundsState providePipBoundsState() {
+ return new PipBoundsState();
}
@SysUISingleton
@@ -101,20 +109,33 @@ public class WMShellModule {
@SysUISingleton
@Provides
+ static PipMenuActivityController providesPipMenuActivityController(Context context,
+ PipMediaController pipMediaController, PipTaskOrganizer pipTaskOrganizer) {
+ return new PipMenuActivityController(context, pipMediaController, pipTaskOrganizer);
+ }
+
+ @SysUISingleton
+ @Provides
+ static PipTouchHandler providesPipTouchHandler(Context context,
+ PipMenuActivityController menuActivityController, PipBoundsHandler pipBoundsHandler,
+ PipBoundsState pipBoundsState,
+ PipTaskOrganizer pipTaskOrganizer,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger) {
+ return new PipTouchHandler(context, menuActivityController, pipBoundsHandler,
+ pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger);
+ }
+
+ @SysUISingleton
+ @Provides
static PipTaskOrganizer providesPipTaskOrganizer(Context context,
+ PipBoundsState pipBoundsState,
PipBoundsHandler pipBoundsHandler,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
- return new PipTaskOrganizer(context, pipBoundsHandler,
+ return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
pipSurfaceTransactionHelper, splitScreenOptional, displayController,
pipUiEventLogger, shellTaskOrganizer);
}
-
- @SysUISingleton
- @Provides
- static OneHanded provideOneHandedController(Context context,
- DisplayController displayController) {
- return OneHandedController.create(context, displayController);
- }
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index c10cdd780260..5946cecc9f0a 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -71,6 +71,13 @@
android:exported="false"
android:resizeableActivity="true" />
+ <activity android:name="com.android.systemui.emergency.EmergencyActivityTest"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.systemui.action.LAUNCH_EMERGENCY"/>
+ </intent-filter>
+ </activity>
+
<activity
android:name="com.android.systemui.globalactions.GlobalActionsImeTest$TestActivity"
android:excludeFromRecents="true"
@@ -83,6 +90,10 @@
<activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
android:exported="false" />
+
+ <activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
+ android:exported="false" />
+
<provider
android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
index 54e879e2ff38..64632afe9bfa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
@@ -16,8 +16,10 @@
package com.android.keyguard;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.media.AudioManager;
import android.telephony.TelephonyManager;
@@ -47,13 +49,15 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase {
@Mock
private KeyguardHostView mKeyguardHostView;
@Mock
- private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
- @Mock
private AudioManager mAudioManager;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
private ViewMediatorCallback mViewMediatorCallback;
+ @Mock
+ KeyguardSecurityContainerController.Factory mKeyguardSecurityContainerControllerFactory;
+ @Mock
+ private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -62,9 +66,12 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase {
@Before
public void setup() {
+ when(mKeyguardSecurityContainerControllerFactory.create(any(
+ KeyguardSecurityContainer.SecurityCallback.class)))
+ .thenReturn(mKeyguardSecurityContainerController);
mKeyguardHostViewController = new KeyguardHostViewController(
- mKeyguardHostView, mKeyguardUpdateMonitor, mKeyguardSecurityContainerController,
- mAudioManager, mTelephonyManager, mViewMediatorCallback);
+ mKeyguardHostView, mKeyguardUpdateMonitor, mAudioManager, mTelephonyManager,
+ mViewMediatorCallback, mKeyguardSecurityContainerControllerFactory);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index cdb91ecfad89..eef38d316775 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -89,12 +89,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
- mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
+ mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
- mKeyguardStateController, mKeyguardSecurityViewFlipperController);
-
- mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
+ mKeyguardStateController, mKeyguardSecurityViewFlipperController)
+ .create(mSecurityCallback);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d93cc05c57db..e73ed801b7df 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -53,6 +53,7 @@ import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.media.AudioManager;
import android.os.Bundle;
@@ -132,7 +133,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private FaceManager mFaceManager;
@Mock
- private List<FaceSensorProperties> mFaceSensorProperties;
+ private List<FaceSensorPropertiesInternal> mFaceSensorProperties;
@Mock
private BiometricManager mBiometricManager;
@Mock
@@ -177,13 +178,14 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFaceManager.getSensorProperties()).thenReturn(mFaceSensorProperties);
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
// IBiometricsFace@1.0 does not support detection, only authentication.
when(mFaceSensorProperties.isEmpty()).thenReturn(false);
- when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorProperties(0 /* id */,
- false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
- 1 /* maxTemplatesAllowed */));
+ when(mFaceSensorProperties.get(anyInt())).thenReturn(new FaceSensorPropertiesInternal(
+ 0 /* id */,
+ FaceSensorProperties.STRENGTH_STRONG, 1 /* maxTemplatesAllowed */,
+ false /* supportsFaceDetection */, true /* supportsSelfIllumination */));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index 353fe625b8ea..35fe1ba8f805 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -168,7 +168,7 @@ public final class ClockManagerTest extends SysuiTestCase {
verify(mMockListener2).onClockChanged(captor2.capture());
assertThat(captor1.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
assertThat(captor2.getValue()).isInstanceOf(BUBBLE_CLOCK_CLASS);
- assertThat(captor1.getValue()).isNotSameAs(captor2.getValue());
+ assertThat(captor1.getValue()).isNotSameInstanceAs(captor2.getValue());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index d107f646bb6b..ab805f42ab56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -62,6 +62,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R.dimen;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import org.junit.Before;
@@ -88,6 +89,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private TunerService mTunerService;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private UserTracker mUserTracker;
@Before
public void setup() {
@@ -109,7 +112,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler,
- mBroadcastDispatcher, mTunerService) {
+ mBroadcastDispatcher, mTunerService, mUserTracker) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 1638ea1ca843..71a0434c41b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -29,8 +29,8 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
@@ -50,7 +50,7 @@ public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
private SizeCompatModeActivityController mController;
private TaskStackChangeListener mTaskStackListener;
- private @Mock ActivityManagerWrapper mMockAm;
+ private @Mock TaskStackChangeListeners mMockTaskListeners;
private @Mock RestartActivityButton mMockButton;
private @Mock IBinder mMockActivityToken;
@@ -59,7 +59,7 @@ public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
doReturn(true).when(mMockButton).show();
- mController = new SizeCompatModeActivityController(mContext, mMockAm,
+ mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
new CommandQueue(mContext)) {
@Override
RestartActivityButton createRestartButton(Context context) {
@@ -69,7 +69,7 @@ public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
- verify(mMockAm).registerTaskStackListener(listenerCaptor.capture());
+ verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
mTaskStackListener = listenerCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 1e969c226ff1..fa78d1cc0191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -36,6 +36,7 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
@@ -65,6 +66,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
@Mock
private ModeSwitchesController mModeSwitchesController;
@Mock
+ private NavigationModeController mNavigationModeController;
+ @Mock
private IRemoteMagnificationAnimationCallback mAnimationCallback;
private IWindowMagnificationConnection mIWindowMagnificationConnection;
private WindowMagnification mWindowMagnification;
@@ -79,7 +82,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase {
}).when(mAccessibilityManager).setWindowMagnificationConnection(
any(IWindowMagnificationConnection.class));
mWindowMagnification = new WindowMagnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
+ getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ mNavigationModeController);
mWindowMagnification.mWindowMagnificationAnimationController =
mWindowMagnificationAnimationController;
mWindowMagnification.requestWindowMagnificationConnection(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 64e067396059..3c248c73ab71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -22,17 +22,20 @@ import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK;
+import static com.android.systemui.accessibility.MagnificationModeSwitch.FADING_ANIMATION_DURATION_MS;
import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;
import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,10 +48,12 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -64,6 +69,9 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class MagnificationModeSwitchTest extends SysuiTestCase {
+ private static final float FADE_IN_ALPHA = 1f;
+ private static final float FADE_OUT_ALPHA = 0f;
+
private ImageView mSpyImageView;
@Mock
private WindowManager mWindowManager;
@@ -82,49 +90,47 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
).when(mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
mSpyImageView = Mockito.spy(new ImageView(mContext));
- doAnswer(invocation -> null).when(mSpyImageView).setOnTouchListener(
- mTouchListenerCaptor.capture());
- initMockImageViewAndAnimator();
+ resetMockImageViewAndAnimator();
mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView);
}
@Test
- public void removeButton_removeView() {
+ public void removeButton_buttonIsShowing_removeView() {
mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
mMagnificationModeSwitch.removeButton();
verify(mWindowManager).removeView(mSpyImageView);
- // First invocation is in showButton.
- verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mViewPropertyAnimator).cancel();
}
@Test
public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
-
- verify(mSpyImageView).setAlpha(1.0f);
verify(mSpyImageView).setImageResource(
getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
- assertShowButtonAnimation();
+ assertShowFadingAnimation(FADE_IN_ALPHA);
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
+
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
verify(mViewPropertyAnimator).withEndAction(captor.capture());
verify(mWindowManager).addView(eq(mSpyImageView), any(WindowManager.LayoutParams.class));
captor.getValue().run();
- // First invocation is in showButton.
- verify(mViewPropertyAnimator, times(2)).cancel();
+ verify(mViewPropertyAnimator).cancel();
verify(mWindowManager).removeView(mSpyImageView);
}
@Test
- public void onConfigurationChanged_setImageResource() {
+ public void onConfigurationChanged_buttonIsShowing_setImageResource() {
mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ resetMockImageViewAndAnimator();
+
mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);
- verify(mSpyImageView, times(2)).setImageResource(
+ verify(mSpyImageView).setImageResource(
getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
}
@@ -142,13 +148,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
resetMockImageViewAndAnimator();
listener.onTouch(mSpyImageView, MotionEvent.obtain(
0, ViewConfiguration.getTapTimeout(), ACTION_UP, 100, 100, 0));
- verify(mViewPropertyAnimator).cancel();
- verify(mSpyImageView).setImageResource(
- getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
- verify(mWindowManager).removeView(mSpyImageView);
- final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
- assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode);
+ verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
@Test
@@ -163,7 +163,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
listener.onTouch(mSpyImageView, MotionEvent.obtain(
0, 0, ACTION_DOWN, 100, 100, 0));
- verify(mSpyImageView).setAlpha(1.0f);
verify(mViewPropertyAnimator).cancel();
listener.onTouch(mSpyImageView, MotionEvent.obtain(
@@ -174,9 +173,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
resetMockImageViewAndAnimator();
listener.onTouch(mSpyImageView, MotionEvent.obtain(
0, ViewConfiguration.getTapTimeout() + 10, ACTION_UP, 100 + offset, 100, 0));
- verify(mSpyImageView).setAlpha(1.0f);
assertModeUnchanged(previousMode);
- assertShowButtonAnimation();
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
}
@Test
@@ -194,9 +192,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
resetMockImageViewAndAnimator();
listener.onTouch(mSpyImageView, MotionEvent.obtain(
0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100, 100, 0));
- verify(mSpyImageView).setAlpha(1.0f);
assertModeUnchanged(previousMode);
- assertShowButtonAnimation();
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
}
@Test
@@ -217,9 +214,49 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
resetMockImageViewAndAnimator();
listener.onTouch(mSpyImageView, MotionEvent.obtain(
0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100 + offset, 100, 0));
- verify(mSpyImageView).setAlpha(1.0f);
assertModeUnchanged(previousMode);
- assertShowButtonAnimation();
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
+ }
+
+ @Test
+ public void initializeA11yNode_showWindowModeButton_expectedValues() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
+
+ mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
+
+ assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
+ nodeInfo.getContentDescription());
+ assertEquals(mContext.getString(R.string.magnification_mode_switch_state_window),
+ nodeInfo.getStateDescription());
+ assertThat(nodeInfo.getActionList(),
+ hasItems(new AccessibilityNodeInfo.AccessibilityAction(
+ ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label))));
+ }
+
+ @Test
+ public void performA11yActions_showWindowModeButton_verifyTapAction() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ resetMockImageViewAndAnimator();
+
+ mSpyImageView.performAccessibilityAction(
+ ACTION_CLICK.getId(), null);
+
+ verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void showButton_showFadeOutAnimation_fadeOutAnimationCanceled() {
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
+ resetMockImageViewAndAnimator();
+
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+
+ verify(mViewPropertyAnimator).cancel();
+ assertEquals(1f, mSpyImageView.getAlpha());
+ assertShowFadingAnimation(FADE_OUT_ALPHA);
}
private void assertModeUnchanged(int expectedMode) {
@@ -228,30 +265,50 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
assertEquals(expectedMode, actualMode);
}
- private void assertShowButtonAnimation() {
- verify(mViewPropertyAnimator).cancel();
- verify(mViewPropertyAnimator).setDuration(anyLong());
- verify(mViewPropertyAnimator).alpha(anyFloat());
+ private void assertShowFadingAnimation(float alpha) {
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ if (alpha == FADE_IN_ALPHA) { // Fade-in
+ verify(mSpyImageView).postOnAnimation(runnableCaptor.capture());
+ } else { // Fade-out
+ verify(mSpyImageView).postOnAnimationDelayed(runnableCaptor.capture(), anyLong());
+ }
+ resetMockAnimator();
+
+ runnableCaptor.getValue().run();
+
+ verify(mViewPropertyAnimator).setDuration(eq(FADING_ANIMATION_DURATION_MS));
+ verify(mViewPropertyAnimator).alpha(alpha);
verify(mViewPropertyAnimator).start();
}
- private void initMockImageViewAndAnimator() {
+ private void resetMockImageViewAndAnimator() {
+ Mockito.reset(mSpyImageView);
+ doAnswer(invocation -> null).when(mSpyImageView).setOnTouchListener(
+ mTouchListenerCaptor.capture());
+ resetMockAnimator();
+ }
+
+ private void resetMockAnimator() {
+ Mockito.reset(mViewPropertyAnimator);
when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
mViewPropertyAnimator);
-
when(mSpyImageView.animate()).thenReturn(mViewPropertyAnimator);
- doAnswer(invocation -> {
- Runnable run = invocation.getArgument(0);
- run.run();
- return null;
- }).when(mSpyImageView).postDelayed(any(), anyLong());
}
- private void resetMockImageViewAndAnimator() {
- Mockito.reset(mViewPropertyAnimator);
- Mockito.reset(mSpyImageView);
- initMockImageViewAndAnimator();
+ /**
+ * Verifies the tap behaviour including the image of the button and the magnification mode.
+ *
+ * @param expectedMode the expected mode after tapping
+ */
+ private void verifyTapAction(int expectedMode) {
+ verify(mViewPropertyAnimator).cancel();
+ verify(mSpyImageView).setImageResource(
+ getIconResId(expectedMode));
+ verify(mWindowManager).removeView(mSpyImageView);
+ final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
+ assertEquals(expectedMode, actualMode);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 539b321991ad..5f2fd697b86d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -17,10 +17,19 @@
package com.android.systemui.accessibility;
import static android.view.Choreographer.FrameCallback;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -37,17 +46,20 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -71,6 +83,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
private Resources mResources;
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
+ private View mMirrorView;
@Before
public void setUp() {
@@ -83,12 +96,16 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
).when(mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
doAnswer(invocation -> {
- View view = invocation.getArgument(0);
+ mMirrorView = invocation.getArgument(0);
WindowManager.LayoutParams lp = invocation.getArgument(1);
- view.setLayoutParams(lp);
+ mMirrorView.setLayoutParams(lp);
return null;
}).when(mWindowManager).addView(any(View.class), any(WindowManager.LayoutParams.class));
doAnswer(invocation -> {
+ mMirrorView = null;
+ return null;
+ }).when(mWindowManager).removeView(any(View.class));
+ doAnswer(invocation -> {
FrameCallback callback = invocation.getArgument(0);
callback.doFrame(0);
return null;
@@ -147,14 +164,18 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
- public void setScale_enabled_expectedValue() {
+ public void setScale_enabled_expectedValueAndUpdateStateDescription() {
mInstrumentation.runOnMainSync(
- () -> mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ () -> mWindowMagnificationController.enableWindowMagnification(2.0f, Float.NaN,
Float.NaN));
mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f));
assertEquals(3.0f, mWindowMagnificationController.getScale(), 0);
+ ArgumentCaptor<Runnable> runnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableArgumentCaptor.capture(), anyLong());
+ runnableArgumentCaptor.getValue().run();
+ assertThat(mMirrorView.getStateDescription().toString(), containsString("300"));
}
@Test
@@ -227,4 +248,63 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mResources, atLeastOnce()).getDimensionPixelSize(anyInt());
}
+
+ @Test
+ public void initializeA11yNode_enabled_expectedValues() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN,
+ Float.NaN);
+ });
+ assertNotNull(mMirrorView);
+ final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
+
+ mMirrorView.onInitializeAccessibilityNodeInfo(nodeInfo);
+
+ assertNotNull(nodeInfo.getContentDescription());
+ assertThat(nodeInfo.getStateDescription().toString(), containsString("250"));
+ assertThat(nodeInfo.getActionList(),
+ hasItems(new AccessibilityAction(R.id.accessibility_action_zoom_in, null),
+ new AccessibilityAction(R.id.accessibility_action_zoom_out, null),
+ new AccessibilityAction(R.id.accessibility_action_move_right, null),
+ new AccessibilityAction(R.id.accessibility_action_move_left, null),
+ new AccessibilityAction(R.id.accessibility_action_move_down, null),
+ new AccessibilityAction(R.id.accessibility_action_move_up, null)));
+ }
+
+ @Test
+ public void performA11yActions_visible_expectedResults() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN,
+ Float.NaN);
+ });
+ assertNotNull(mMirrorView);
+
+ assertTrue(
+ mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_out, null));
+ // Minimum scale is 2.0.
+ assertEquals(2.0f, mWindowMagnificationController.getScale(), 0f);
+
+ assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_zoom_in, null));
+ assertEquals(3.0f, mWindowMagnificationController.getScale(), 0f);
+
+ // TODO: Verify the final state when the mirror surface is visible.
+ assertTrue(mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_up, null));
+ assertTrue(
+ mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_down, null));
+ assertTrue(
+ mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_right, null));
+ assertTrue(
+ mMirrorView.performAccessibilityAction(R.id.accessibility_action_move_left, null));
+ }
+
+ @Test
+ public void onNavigationModeChanged_updateMirrorViewLayout() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN,
+ Float.NaN);
+ mWindowMagnificationController.onNavigationModeChanged(NAV_BAR_MODE_GESTURAL);
+ });
+
+ verify(mWindowManager).updateViewLayout(eq(mMirrorView), any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 936558bca2d2..4a0e216ae87e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -35,6 +35,7 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
@@ -53,6 +54,8 @@ public class WindowMagnificationTest extends SysuiTestCase {
private AccessibilityManager mAccessibilityManager;
@Mock
private ModeSwitchesController mModeSwitchesController;
+ @Mock
+ private NavigationModeController mNavigationModeController;
private CommandQueue mCommandQueue;
private WindowMagnification mWindowMagnification;
@@ -63,7 +66,8 @@ public class WindowMagnificationTest extends SysuiTestCase {
mCommandQueue = new CommandQueue(getContext());
mWindowMagnification = new WindowMagnification(getContext(),
- getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController);
+ getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
+ mNavigationModeController);
mWindowMagnification.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 66656c5d251c..e0420ca38f45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -45,9 +45,11 @@ import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -119,11 +121,15 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- FingerprintSensorProperties prop = new FingerprintSensorProperties(
- 1, FingerprintSensorProperties.TYPE_UDFPS, true, 1);
- List<FingerprintSensorProperties> props = new ArrayList<>();
+ FingerprintSensorPropertiesInternal prop = new FingerprintSensorPropertiesInternal(
+ 1 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 1 /* maxEnrollmentsPerUser */,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* resetLockoutRequireHardwareAuthToken */);
+ List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
props.add(prop);
- when(mFingerprintManager.getSensorProperties()).thenReturn(props);
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
mAuthController = new TestableAuthController(context, mCommandQueue,
mStatusBarStateController, mActivityTaskManager, mFingerprintManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 9b9f840e5383..e24f4ca3581d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,7 +27,10 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.PowerManager;
import android.os.RemoteException;
@@ -54,11 +59,18 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class UdfpsControllerTest extends SysuiTestCase {
+ // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
+ // leaving SystemUI.
+ private static final int TEST_UDFPS_SENSOR_ID = 1;
+
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@@ -98,6 +110,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
public void setUp() {
setUpResources();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
+ final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
mSystemSettings = new FakeSettings();
mFgExecutor = new FakeExecutor(new FakeSystemClock());
mUdfpsController = new UdfpsController(
@@ -112,6 +131,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
+
+ assertEquals(TEST_UDFPS_SENSOR_ID, mUdfpsController.mUdfpsSensorId);
}
private void setUpResources() {
@@ -138,15 +159,15 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void showUdfpsOverlay_addsViewToWindow() throws RemoteException {
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(eq(mUdfpsView), any());
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException {
- mOverlayController.showUdfpsOverlay();
- mOverlayController.hideUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
verify(mWindowManager).removeView(eq(mUdfpsView));
}
@@ -156,7 +177,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the bouncer is showing
mUdfpsController.setBouncerVisibility(/* isShowing */ true);
// WHEN a request to show the overlay is received
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
// THEN the overlay is not attached
verify(mWindowManager, never()).addView(eq(mUdfpsView), any());
@@ -165,7 +186,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void setBouncerVisibility_overlayDetached() throws RemoteException {
// GIVEN that the overlay has been requested
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
// WHEN the bouncer becomes visible
mUdfpsController.setBouncerVisibility(/* isShowing */ true);
mFgExecutor.runAllReady();
@@ -178,7 +199,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// GIVEN that the bouncer is visible
mUdfpsController.setBouncerVisibility(/* isShowing */ true);
// AND the overlay has been requested
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
// WHEN the bouncer is closed
mUdfpsController.setBouncerVisibility(/* isShowing */ false);
mFgExecutor.runAllReady();
@@ -193,7 +214,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
// WHEN ACTION_DOWN is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -201,7 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
event.recycle();
// THEN the event is passed to the FingerprintManager
- verify(mFingerprintManager).onFingerDown(eq(0), eq(0), eq(0f), eq(0f));
+ verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mUdfpsSensorId), eq(0), eq(0),
+ eq(0f), eq(0f));
// AND the scrim and dot is shown
verify(mUdfpsView).showScrimAndDot();
}
@@ -209,12 +231,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterrupt() throws RemoteException {
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0);
// THEN the event is passed to the FingerprintManager
- verify(mFingerprintManager).onFingerDown(eq(0), eq(0), anyFloat(), anyFloat());
+ verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mUdfpsSensorId), eq(0), eq(0),
+ anyFloat(), anyFloat());
// AND the scrim and dot is shown
verify(mUdfpsView).showScrimAndDot();
}
@@ -222,7 +245,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void cancelAodInterrupt() throws RemoteException {
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0);
// WHEN it is cancelled
@@ -234,7 +257,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterruptTimeout() throws RemoteException {
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay();
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0);
// WHEN it times out
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index f65c2c91c50a..b082d17e58ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -91,7 +91,9 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import com.google.common.collect.ImmutableList;
@@ -191,6 +193,10 @@ public class BubbleControllerTest extends SysuiTestCase {
private LauncherApps mLauncherApps;
@Mock private LockscreenLockIconController mLockIconController;
+ @Mock private WindowManagerShellWrapper mWindowManagerShellWrapper;
+
+ @Mock private BubbleLogger mBubbleLogger;
+
private BubbleData mBubbleData;
private TestableLooper mTestableLooper;
@@ -246,7 +252,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mock(HeadsUpManager.class),
mock(Handler.class)
);
- mBubbleData = new BubbleData(mContext);
+ mBubbleData = new BubbleData(mContext, mBubbleLogger);
when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mBubbleController = new TestableBubbleController(
mContext,
@@ -269,7 +275,11 @@ public class BubbleControllerTest extends SysuiTestCase {
mock(INotificationManager.class),
mStatusBarService,
mWindowManager,
- mLauncherApps);
+ mWindowManagerShellWrapper,
+ mLauncherApps,
+ mBubbleLogger,
+ mock(Handler.class),
+ mock(ShellTaskOrganizer.class));
mBubbleController.setExpandListener(mBubbleExpandListener);
// Get a reference to the BubbleController's entry listener
@@ -1007,6 +1017,29 @@ public class BubbleControllerTest extends SysuiTestCase {
verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
}
+
+ /**
+ * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
+ * the that bubble does not get promoted from the overflow.
+ */
+ @Test
+ public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
+ // Setup
+ mBubbleController.updateBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow2.getEntry());
+ assertTrue(mBubbleController.hasBubbles());
+
+ // Overflow it
+ mBubbleData.dismissBubbleWithKey(mRow.getEntry().getKey(),
+ BubbleController.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+ assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())).isTrue();
+
+ // Test
+ mBubbleController.updateBubble(mRow.getEntry());
+ assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse();
+ }
+
/**
* Sets the bubble metadata flags for this entry. These ]flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 315caeebe0e9..0c872db45194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -16,9 +16,8 @@
package com.android.systemui.bubbles;
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
-
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -29,7 +28,9 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -39,10 +40,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleData.TimeSource;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.google.common.collect.ImmutableList;
@@ -68,15 +65,15 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubbleDataTest extends SysuiTestCase {
- private NotificationEntry mEntryA1;
- private NotificationEntry mEntryA2;
- private NotificationEntry mEntryA3;
- private NotificationEntry mEntryB1;
- private NotificationEntry mEntryB2;
- private NotificationEntry mEntryB3;
- private NotificationEntry mEntryC1;
- private NotificationEntry mEntryInterruptive;
- private NotificationEntry mEntryDismissed;
+ private BubbleEntry mEntryA1;
+ private BubbleEntry mEntryA2;
+ private BubbleEntry mEntryA3;
+ private BubbleEntry mEntryB1;
+ private BubbleEntry mEntryB2;
+ private BubbleEntry mEntryB3;
+ private BubbleEntry mEntryC1;
+ private BubbleEntry mEntryInterruptive;
+ private BubbleEntry mEntryDismissed;
private Bubble mBubbleA1;
private Bubble mBubbleA2;
@@ -98,8 +95,8 @@ public class BubbleDataTest extends SysuiTestCase {
private PendingIntent mExpandIntent;
@Mock
private PendingIntent mDeleteIntent;
-
- private NotificationTestHelper mNotificationTestHelper;
+ @Mock
+ private BubbleLogger mBubbleLogger;
@Captor
private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@@ -112,29 +109,23 @@ public class BubbleDataTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
- mNotificationTestHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
MockitoAnnotations.initMocks(this);
- mEntryA1 = createBubbleEntry(1, "a1", "package.a");
- mEntryA2 = createBubbleEntry(1, "a2", "package.a");
- mEntryA3 = createBubbleEntry(1, "a3", "package.a");
- mEntryB1 = createBubbleEntry(1, "b1", "package.b");
- mEntryB2 = createBubbleEntry(1, "b2", "package.b");
- mEntryB3 = createBubbleEntry(1, "b3", "package.b");
- mEntryC1 = createBubbleEntry(1, "c1", "package.c");
-
- mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d");
- modifyRanking(mEntryInterruptive)
- .setVisuallyInterruptive(true)
- .build();
+ mEntryA1 = createBubbleEntry(1, "a1", "package.a", null);
+ mEntryA2 = createBubbleEntry(1, "a2", "package.a", null);
+ mEntryA3 = createBubbleEntry(1, "a3", "package.a", null);
+ mEntryB1 = createBubbleEntry(1, "b1", "package.b", null);
+ mEntryB2 = createBubbleEntry(1, "b2", "package.b", null);
+ mEntryB3 = createBubbleEntry(1, "b3", "package.b", null);
+ mEntryC1 = createBubbleEntry(1, "c1", "package.c", null);
+
+ NotificationListenerService.Ranking ranking =
+ mock(NotificationListenerService.Ranking.class);
+ when(ranking.visuallyInterruptive()).thenReturn(true);
+ mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null);
- ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
- mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
- mEntryDismissed.setRow(row);
+ mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null);
mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener);
@@ -145,7 +136,7 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
- mBubbleData = new BubbleData(getContext());
+ mBubbleData = new BubbleData(getContext(), mBubbleLogger);
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
@@ -513,6 +504,26 @@ public class BubbleDataTest extends SysuiTestCase {
}
/**
+ * Verifies that when a non visually interruptive update occurs, that the selection does not
+ * change.
+ */
+ @Test
+ public void test_notVisuallyInterruptive_updateBubble_selectionDoesntChange() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryB2, 3000);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
+ mBubbleData.setListener(mListener);
+
+ assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+
+ // Test
+ sendUpdatedEntryAtTime(mEntryB1, 5000, false /* isVisuallyInterruptive */);
+ assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2);
+ }
+
+ /**
* Verifies that a request to expand the stack has no effect if there are no bubbles.
*/
@Test
@@ -793,47 +804,48 @@ public class BubbleDataTest extends SysuiTestCase {
private void assertBubbleAdded(Bubble expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.addedBubble).named("addedBubble").isEqualTo(expected);
+ assertWithMessage("addedBubble").that(update.addedBubble).isEqualTo(expected);
}
private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.removedBubbles).named("removedBubbles")
+ assertWithMessage("removedBubbles").that(update.removedBubbles)
.isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
}
private void assertOrderNotChanged() {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.orderChanged).named("orderChanged").isFalse();
+ assertWithMessage("orderChanged").that(update.orderChanged).isFalse();
}
private void assertOrderChangedTo(Bubble... order) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.orderChanged).named("orderChanged").isTrue();
- assertThat(update.bubbles).named("bubble order").isEqualTo(ImmutableList.copyOf(order));
+ assertWithMessage("orderChanged").that(update.orderChanged).isTrue();
+ assertWithMessage("bubble order").that(update.bubbles)
+ .isEqualTo(ImmutableList.copyOf(order));
}
private void assertSelectionNotChanged() {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.selectionChanged).named("selectionChanged").isFalse();
+ assertWithMessage("selectionChanged").that(update.selectionChanged).isFalse();
}
private void assertSelectionChangedTo(Bubble bubble) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.selectionChanged).named("selectionChanged").isTrue();
- assertThat(update.selectedBubble).named("selectedBubble").isEqualTo(bubble);
+ assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
+ assertWithMessage("selectedBubble").that(update.selectedBubble).isEqualTo(bubble);
}
private void assertSelectionCleared() {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.selectionChanged).named("selectionChanged").isTrue();
- assertThat(update.selectedBubble).named("selectedBubble").isNull();
+ assertWithMessage("selectionChanged").that(update.selectionChanged).isTrue();
+ assertWithMessage("selectedBubble").that(update.selectedBubble).isNull();
}
private void assertExpandedChangedTo(boolean expected) {
BubbleData.Update update = mUpdateCaptor.getValue();
- assertThat(update.expandedChanged).named("expandedChanged").isTrue();
- assertThat(update.expanded).named("expanded").isEqualTo(expected);
+ assertWithMessage("expandedChanged").that(update.expandedChanged).isTrue();
+ assertWithMessage("expanded").that(update.expanded).isEqualTo(expected);
}
private void assertOverflowChangedTo(ImmutableList<Bubble> bubbles) {
@@ -842,12 +854,13 @@ public class BubbleDataTest extends SysuiTestCase {
}
- private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
- return createBubbleEntry(userId, notifKey, packageName, 1000);
+ private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
+ NotificationListenerService.Ranking ranking) {
+ return createBubbleEntry(userId, notifKey, packageName, ranking, 1000);
}
- private void setPostTime(NotificationEntry entry, long postTime) {
- when(entry.getSbn().getPostTime()).thenReturn(postTime);
+ private void setPostTime(BubbleEntry entry, long postTime) {
+ when(entry.getStatusBarNotification().getPostTime()).thenReturn(postTime);
}
/**
@@ -855,16 +868,19 @@ public class BubbleDataTest extends SysuiTestCase {
* required for BubbleData functionality and verification. NotificationTestHelper is used only
* as a convenience to create a Notification w/BubbleMetadata.
*/
- private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
- long postTime) {
+ private BubbleEntry createBubbleEntry(int userId, String notifKey, String packageName,
+ NotificationListenerService.Ranking ranking, long postTime) {
// BubbleMetadata
Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder(
mExpandIntent, Icon.createWithResource("", 0))
.setDeleteIntent(mDeleteIntent)
.build();
// Notification -> BubbleMetadata
- Notification notification = mNotificationTestHelper.createNotification(false,
- null /* groupKey */, bubbleMetadata);
+ Notification notification = mock(Notification.class);
+ notification.setBubbleMetadata(bubbleMetadata);
+
+ // Notification -> extras
+ notification.extras = new Bundle();
// StatusBarNotification
StatusBarNotification sbn = mock(StatusBarNotification.class);
@@ -875,17 +891,23 @@ public class BubbleDataTest extends SysuiTestCase {
when(sbn.getNotification()).thenReturn(notification);
// NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
- return new NotificationEntryBuilder().setSbn(sbn).build();
+ return new BubbleEntry(sbn, ranking, true, false, false, false);
}
private void setCurrentTime(long time) {
when(mTimeSource.currentTimeMillis()).thenReturn(time);
}
- private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
+ private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
+ sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */);
+ }
+
+ private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime,
+ boolean visuallyInterruptive) {
setPostTime(entry, postTime);
// BubbleController calls this:
Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */);
+ b.setVisuallyInterruptiveForTest(visuallyInterruptive);
// And then this
mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/,
true /* showInShade */);
@@ -895,4 +917,4 @@ public class BubbleDataTest extends SysuiTestCase {
setCurrentTime(time);
mBubbleData.setExpanded(shouldBeExpanded);
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index f7f3a377f31d..29ead593c51a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -24,12 +24,14 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -37,8 +39,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -52,8 +52,10 @@ import org.mockito.MockitoAnnotations;
public class BubbleTest extends SysuiTestCase {
@Mock
private Notification mNotif;
+ @Mock
+ private StatusBarNotification mSbn;
- private NotificationEntry mEntry;
+ private BubbleEntry mBubbleEntry;
private Bundle mExtras;
private Bubble mBubble;
@@ -67,18 +69,16 @@ public class BubbleTest extends SysuiTestCase {
mExtras = new Bundle();
mNotif.extras = mExtras;
- mEntry = new NotificationEntryBuilder()
- .setNotification(mNotif)
- .build();
-
- mBubble = new Bubble(mEntry, mSuppressionListener, null);
-
Intent target = new Intent(mContext, BubblesTestActivity.class);
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
PendingIntent.getActivity(mContext, 0, target, 0),
Icon.createWithResource(mContext, R.drawable.android))
.build();
- mEntry.setBubbleMetadata(metadata);
+ when(mSbn.getNotification()).thenReturn(mNotif);
+ when(mNotif.getBubbleMetadata()).thenReturn(metadata);
+ when(mSbn.getKey()).thenReturn("mock");
+ mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
+ mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null);
}
@Test
@@ -86,7 +86,7 @@ public class BubbleTest extends SysuiTestCase {
final String msg = "Hello there!";
doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
- assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
+ assertEquals(msg, Bubble.extractFlyoutMessage(mBubbleEntry).message);
}
@Test
@@ -97,7 +97,7 @@ public class BubbleTest extends SysuiTestCase {
mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
// Should be big text, not the small text.
- assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
+ assertEquals(msg, Bubble.extractFlyoutMessage(mBubbleEntry).message);
}
@Test
@@ -105,7 +105,7 @@ public class BubbleTest extends SysuiTestCase {
doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
// Media notifs don't get update messages.
- assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
+ assertNull(Bubble.extractFlyoutMessage(mBubbleEntry).message);
}
@Test
@@ -121,7 +121,7 @@ public class BubbleTest extends SysuiTestCase {
// Should be the last one only.
assertEquals("Really? I prefer them that way.",
- BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
+ Bubble.extractFlyoutMessage(mBubbleEntry).message);
}
@Test
@@ -136,8 +136,8 @@ public class BubbleTest extends SysuiTestCase {
"Oh, hello!", 0, "Mady").toBundle()});
// Should be the last one only.
- assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message);
- assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName);
+ assertEquals("Oh, hello!", Bubble.extractFlyoutMessage(mBubbleEntry).message);
+ assertEquals("Mady", Bubble.extractFlyoutMessage(mBubbleEntry).senderName);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java
new file mode 100644
index 000000000000..7c1b41443e26
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.bubbles;
+
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class MultiWindowTaskListenerTest extends SysuiTestCase {
+
+ @Mock
+ ShellTaskOrganizer mOrganizer;
+ @Mock
+ MultiWindowTaskListener.Listener mPendingListener;
+ @Mock
+ SurfaceControl mLeash;
+ @Mock
+ ActivityManager.RunningTaskInfo mTaskInfo;
+ @Mock
+ WindowContainerToken mToken;
+
+ Handler mHandler;
+ MultiWindowTaskListener mTaskListener;
+ TestableLooper mTestableLooper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestableLooper = TestableLooper.get(this);
+ mHandler = new Handler(mTestableLooper.getLooper());
+
+ mTaskInfo = new ActivityManager.RunningTaskInfo();
+ mTaskInfo.token = mToken;
+
+ mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer);
+ }
+
+ private void addTaskAndVerify() {
+ mTaskListener.setPendingListener(mPendingListener);
+ mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+ mTestableLooper.processAllMessages();
+ verify(mPendingListener).onTaskAppeared(eq(mTaskInfo), eq(mLeash));
+ }
+
+ @Test
+ public void testListenForMultiWindowMode() {
+ mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer);
+ verify(mOrganizer).addListener(eq(mTaskListener), eq(TASK_LISTENER_TYPE_MULTI_WINDOW));
+ }
+
+ @Test
+ public void testRemovePendingListener() {
+ addTaskAndVerify();
+ reset(mPendingListener);
+
+ mTaskListener.removeListener(mPendingListener);
+
+ // If it was removed, our pendingListener shouldn't get triggered:
+ mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskListener.onTaskInfoChanged(mTaskInfo);
+ mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+ mTaskListener.onTaskVanished(mTaskInfo);
+
+ mTestableLooper.processAllMessages();
+ verify(mPendingListener, never()).onTaskAppeared(any(), any());
+ verify(mPendingListener, never()).onTaskInfoChanged(any());
+ verify(mPendingListener, never()).onBackPressedOnTaskRoot(any());
+ verify(mPendingListener, never()).onTaskVanished(any());
+ }
+
+ @Test
+ public void testOnTaskAppeared() {
+ addTaskAndVerify();
+ verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mToken), eq(true));
+ }
+
+ @Test
+ public void testOnTaskAppeared_nullListener() {
+ mTaskListener.onTaskAppeared(mTaskInfo, mLeash);
+ mTestableLooper.processAllMessages();
+
+ verify(mOrganizer, never()).setInterceptBackPressedOnTaskRoot(any(), anyBoolean());
+ verify(mPendingListener, never()).onTaskAppeared(any(), any());
+ }
+
+ @Test
+ public void testOnTaskVanished() {
+ addTaskAndVerify();
+ mTaskListener.onTaskVanished(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener).onTaskVanished(eq(mTaskInfo));
+ }
+
+ @Test
+ public void testOnTaskVanished_neverAdded() {
+ mTaskListener.onTaskVanished(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener, never()).onTaskVanished(any());
+ }
+
+ @Test
+ public void testOnTaskInfoChanged() {
+ addTaskAndVerify();
+ mTaskListener.onTaskInfoChanged(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener).onTaskInfoChanged(eq(mTaskInfo));
+ }
+
+ @Test
+ public void testOnTaskInfoChanged_neverAdded() {
+ mTaskListener.onTaskInfoChanged(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener, never()).onTaskInfoChanged(any());
+ }
+
+ @Test
+ public void testOnBackPressedOnTaskRoot() {
+ addTaskAndVerify();
+ mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener).onBackPressedOnTaskRoot(eq(mTaskInfo));
+ }
+
+ @Test
+ public void testOnBackPressedOnTaskRoot_neverAdded() {
+ mTaskListener.onBackPressedOnTaskRoot(mTaskInfo);
+ mTestableLooper.processAllMessages();
+
+ verify(mPendingListener, never()).onBackPressedOnTaskRoot(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index dd191e9cd328..cbacd5393ccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -90,8 +90,10 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.InjectionInflationController;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import org.junit.Before;
import org.junit.Ignore;
@@ -185,6 +187,10 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
private IStatusBarService mStatusBarService;
@Mock
private LauncherApps mLauncherApps;
+ @Mock
+ private WindowManagerShellWrapper mWindowManagerShellWrapper;
+ @Mock
+ private BubbleLogger mBubbleLogger;
private BubbleData mBubbleData;
@@ -248,7 +254,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
mock(HeadsUpManager.class),
mock(Handler.class)
);
- mBubbleData = new BubbleData(mContext);
+ mBubbleData = new BubbleData(mContext, mBubbleLogger);
when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
mBubbleController = new TestableBubbleController(
mContext,
@@ -271,7 +277,11 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
mock(INotificationManager.class),
mStatusBarService,
mWindowManager,
- mLauncherApps);
+ mWindowManagerShellWrapper,
+ mLauncherApps,
+ mBubbleLogger,
+ mock(Handler.class),
+ mock(ShellTaskOrganizer.class));
mBubbleController.addNotifCallback(mNotifCallback);
mBubbleController.setExpandListener(mBubbleExpandListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
new file mode 100644
index 000000000000..6f3968dc5819
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.bubbles;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
+public class TaskViewTest extends SysuiTestCase {
+
+ @Mock
+ TaskView.Listener mViewListener;
+ @Mock
+ ActivityManager.RunningTaskInfo mTaskInfo;
+ @Mock
+ WindowContainerToken mToken;
+ @Mock
+ ShellTaskOrganizer mOrganizer;
+ @Mock
+ MultiWindowTaskListener mTaskListener;
+
+ SurfaceSession mSession;
+ SurfaceControl mLeash;
+
+ Context mContext;
+ TaskView mTaskView;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLeash = new SurfaceControl.Builder(mSession)
+ .setName("test")
+ .build();
+
+ mContext = getContext();
+
+ when(mTaskListener.getTaskOrganizer()).thenReturn(mOrganizer);
+ mTaskInfo = new ActivityManager.RunningTaskInfo();
+ mTaskInfo.token = mToken;
+ mTaskInfo.taskId = 314;
+ mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
+
+ mTaskView = new TaskView(mContext, mTaskListener);
+ mTaskView.setListener(mViewListener);
+ }
+
+ @After
+ public void tearDown() {
+ if (mTaskView != null) {
+ mTaskView.release();
+ }
+ }
+
+ @Test
+ public void testSetPendingListener_throwsException() {
+ TaskView taskView = new TaskView(mContext, mTaskListener);
+ taskView.setListener(mViewListener);
+ try {
+ taskView.setListener(mViewListener);
+ } catch (IllegalStateException e) {
+ // pass
+ return;
+ }
+ fail("Expected IllegalStateException");
+ }
+
+ @Test
+ public void testStartActivity() {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ mTaskView.startActivity(mock(PendingIntent.class), null, options);
+
+ verify(mTaskListener).setPendingListener(eq(mTaskView));
+ assertThat(options.getLaunchWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW);
+ assertThat(options.getTaskAlwaysOnTop()).isTrue();
+ }
+
+ @Test
+ public void testOnTaskAppeared_noSurface() {
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+
+ verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+ verify(mViewListener, never()).onInitialized();
+ // If there's no surface the task should be made invisible
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+ }
+
+ @Test
+ public void testOnTaskAppeared_withSurface() {
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+
+ verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSurfaceCreated_noTask() {
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ verify(mViewListener).onInitialized();
+ // No task, no visibility change
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSurfaceCreated_withTask() {
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+ verify(mViewListener).onInitialized();
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
+ }
+
+ @Test
+ public void testSurfaceDestroyed_noTask() {
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskView.surfaceCreated(sh);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testSurfaceDestroyed_withTask() {
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+ }
+
+ @Test
+ public void testOnReleased() {
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ mTaskView.release();
+
+ verify(mTaskListener).removeListener(eq(mTaskView));
+ verify(mViewListener).onReleased();
+ }
+
+ @Test
+ public void testOnTaskVanished() {
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+ mTaskView.onTaskVanished(mTaskInfo);
+
+ verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
+ }
+
+ @Test
+ public void testOnBackPressedOnTaskRoot() {
+ mTaskView.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+
+ verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
index 58b27f24a1d4..27c6fc147772 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java
@@ -19,6 +19,7 @@ package com.android.systemui.bubbles;
import android.app.INotificationManager;
import android.content.Context;
import android.content.pm.LauncherApps;
+import android.os.Handler;
import android.view.WindowManager;
import com.android.internal.statusbar.IStatusBarService;
@@ -35,7 +36,9 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.FloatingContentCoordinator;
/**
* Testable BubbleController subclass that immediately synchronizes surfaces.
@@ -63,14 +66,19 @@ public class TestableBubbleController extends BubbleController {
INotificationManager notificationManager,
IStatusBarService statusBarService,
WindowManager windowManager,
- LauncherApps launcherApps) {
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ BubbleLogger bubbleLogger,
+ Handler mainHandler,
+ ShellTaskOrganizer shellTaskOrganizer) {
super(context,
notificationShadeWindowController, statusBarStateController, shadeController,
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
notifPipeline, featureFlags, dumpManager, floatingContentCoordinator,
dataRepository, sysUiState, notificationManager, statusBarService,
- windowManager, launcherApps);
+ windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger,
+ mainHandler, shellTaskOrganizer);
setInflateSynchronously(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index cc62a2f36392..9242ce940bcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -33,7 +33,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
-import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import org.junit.Before;
import org.junit.Ignore;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index bb003ee59978..0a81c38e7448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -27,6 +27,7 @@ import android.service.controls.IControlsSubscription
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -61,6 +62,8 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
@Mock
private lateinit var mockControlsController: ControlsController
+ @Mock(stubOnly = true)
+ private lateinit var mockUserTracker: UserTracker
@Captor
private lateinit var subscriberCaptor: ArgumentCaptor<IControlsSubscriber.Stub>
@@ -82,9 +85,10 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
providers.clear()
+ `when`(mockUserTracker.userHandle).thenReturn(user)
controller = TestableControlsBindingControllerImpl(
- mContext, executor, Lazy { mockControlsController })
+ mContext, executor, Lazy { mockControlsController }, mockUserTracker)
}
@After
@@ -364,8 +368,9 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
class TestableControlsBindingControllerImpl(
context: Context,
executor: DelayableExecutor,
- lazyController: Lazy<ControlsController>
-) : ControlsBindingControllerImpl(context, executor, lazyController) {
+ lazyController: Lazy<ControlsController>,
+ userTracker: UserTracker
+) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) {
companion object {
val providers = mutableListOf<ControlsProviderLifecycleManager>()
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 45262c7788f1..f6c836a24f21 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
@@ -38,6 +38,7 @@ import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
@@ -82,6 +83,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
private lateinit var listingController: ControlsListingController
+ @Mock(stubOnly = true)
+ private lateinit var userTracker: UserTracker
@Captor
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -143,6 +146,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
Settings.Secure.putIntForUser(mContext.contentResolver,
ControlsControllerImpl.CONTROLS_AVAILABLE, 1, otherUser)
+ `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
+
delayableExecutor = FakeExecutor(FakeSystemClock())
val wrapper = object : ContextWrapper(mContext) {
@@ -162,7 +167,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
listingController,
broadcastDispatcher,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java)
+ mock(DumpManager::class.java),
+ userTracker
)
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
@@ -217,7 +223,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
listingController,
broadcastDispatcher,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java)
+ mock(DumpManager::class.java),
+ userTracker
)
assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 841b2553ebda..db41d8d37a43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
@@ -66,6 +67,8 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
private lateinit var serviceInfo: ServiceInfo
@Mock
private lateinit var serviceInfo2: ServiceInfo
+ @Mock(stubOnly = true)
+ private lateinit var userTracker: UserTracker
private var componentName = ComponentName("pkg1", "class1")
private var componentName2 = ComponentName("pkg2", "class2")
@@ -86,6 +89,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
`when`(serviceInfo.componentName).thenReturn(componentName)
`when`(serviceInfo2.componentName).thenReturn(componentName2)
+ `when`(userTracker.userId).thenReturn(user)
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
@@ -93,7 +97,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
}
}
- controller = ControlsListingControllerImpl(wrapper, executor, { mockSL })
+ controller = ControlsListingControllerImpl(wrapper, executor, { mockSL }, userTracker)
verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
}
@@ -121,7 +125,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
`when`(mockServiceListing.reload()).then {
callback?.onServicesReloaded(listOf(serviceInfo))
}
- ControlsListingControllerImpl(mContext, exec, { mockServiceListing })
+ ControlsListingControllerImpl(mContext, exec, { mockServiceListing }, userTracker)
}
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
index 691db7fbac5c..a52a598ee7ec 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/emergency/EmergencyActivityTest.java
@@ -14,18 +14,21 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.pip
+package com.android.systemui.emergency;
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.helpers.PipAppHelper
+import android.app.Activity;
+import android.os.Bundle;
-abstract class PipTestBase(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- protected val testApp = PipAppHelper(instrumentation)
+import com.android.systemui.R;
- companion object {
- const val sPipWindowTitle = "PipMenuActivity"
+/**
+ * Test activity for resolving {@link EmergencyGesture#ACTION_LAUNCH_EMERGENCY} action.
+ */
+public class EmergencyActivityTest extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index 1dc415048f74..2e8e3ed664b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -122,4 +122,8 @@ public class WakefulnessLifecycleTest extends SysuiTestCase {
mWakefulness.dump(null, new PrintWriter(new ByteArrayOutputStream()), new String[0]);
}
-} \ No newline at end of file
+ @Test(expected = NullPointerException.class)
+ public void throwNPEOnNullObserver() {
+ mWakefulness.addObserver(null);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index 3439fe53c48f..cd5740d227c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -41,8 +41,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import org.junit.Before;
import org.junit.Test;
@@ -62,7 +62,7 @@ public class WorkLockActivityControllerTest extends SysuiTestCase {
private static final int TASK_ID = 444;
private @Mock Context mContext;
- private @Mock ActivityManagerWrapper mActivityManager;
+ private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
private @Mock IActivityTaskManager mIActivityTaskManager;
private WorkLockActivityController mController;
@@ -78,10 +78,10 @@ public class WorkLockActivityControllerTest extends SysuiTestCase {
// Construct controller. Save the TaskStackListener for injecting events.
final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
- mController = new WorkLockActivityController(mContext, mActivityManager,
+ mController = new WorkLockActivityController(mContext, mTaskStackChangeListeners,
mIActivityTaskManager);
- verify(mActivityManager).registerTaskStackListener(listenerCaptor.capture());
+ verify(mTaskStackChangeListeners).registerTaskStackListener(listenerCaptor.capture());
mTaskStackListener = listenerCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 8a30b00e609d..182a056be557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -38,12 +38,12 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
@@ -53,7 +53,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
@@ -94,6 +93,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet
+ @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
private lateinit var appIcon: ImageView
private lateinit var appName: TextView
private lateinit var albumView: ImageView
@@ -128,7 +128,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet)
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
- seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil)
+ seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
+ mediaOutputDialogFactory)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to.
@@ -203,7 +204,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
fun bindWhenUnattached() {
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, null, null, device, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
assertThat(player.isPlaying()).isFalse()
}
@@ -212,7 +213,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
assertThat(appName.getText()).isEqualTo(APP)
assertThat(titleText.getText()).isEqualTo(TITLE)
assertThat(artistText.getText()).isEqualTo(ARTIST)
@@ -223,7 +224,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
verify(view).setBackgroundTintList(list.capture())
assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
@@ -234,7 +235,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
assertThat(seamless.isEnabled()).isTrue()
}
@@ -246,7 +247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
verify(expandedSet).setVisibility(seamless.id, View.GONE)
verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
verify(collapsedSet).setVisibility(seamless.id, View.GONE)
@@ -258,7 +259,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
- player.bind(state)
+ player.bind(state, PACKAGE)
assertThat(seamless.isEnabled()).isTrue()
assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString(
com.android.internal.R.string.ext_media_seamless_action))
@@ -270,7 +271,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
resumption = true)
- player.bind(state)
+ player.bind(state, PACKAGE)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
assertThat(seamless.isEnabled()).isFalse()
}
@@ -322,31 +323,18 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun dismissButtonClick() {
+ val mediaKey = "key for dismissal"
player.attach(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
notificationKey = KEY)
- player.bind(state)
+ player.bind(state, mediaKey)
dismiss.callOnClick()
val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java)
verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean())
captor.value.onDismiss()
- verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong())
- }
-
- @Test
- fun dismissButtonClick_nullNotificationKey() {
- player.attach(holder)
- val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
- emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
- player.bind(state)
-
- verify(keyguardDismissUtil, never())
- .executeWhenUnlocked(
- any(ActivityStarter.OnDismissAction::class.java),
- ArgumentMatchers.anyBoolean()
- )
+ verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index fdb432cc097c..ab3b20898b23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -16,13 +16,12 @@
package com.android.systemui.media
-import android.app.Notification
import android.graphics.drawable.Drawable
-import android.media.MediaMetadata
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
-import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -55,7 +54,6 @@ private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
-private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val USER_ID = 0
@@ -68,6 +66,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
public class MediaDeviceManagerTest : SysuiTestCase() {
private lateinit var manager: MediaDeviceManager
+ @Mock private lateinit var controllerFactory: MediaControllerFactory
@Mock private lateinit var lmmFactory: LocalMediaManagerFactory
@Mock private lateinit var lmm: LocalMediaManager
@Mock private lateinit var mr2: MediaRouter2Manager
@@ -78,10 +77,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Mock private lateinit var device: MediaDevice
@Mock private lateinit var icon: Drawable
@Mock private lateinit var route: RoutingSessionInfo
+ @Mock private lateinit var controller: MediaController
+ @Mock private lateinit var playbackInfo: PlaybackInfo
private lateinit var session: MediaSession
- private lateinit var metadataBuilder: MediaMetadata.Builder
- private lateinit var playbackBuilder: PlaybackState.Builder
- private lateinit var notifBuilder: Notification.Builder
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -89,8 +87,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
- manager = MediaDeviceManager(context, lmmFactory, mr2, fakeFgExecutor, fakeBgExecutor,
- dumpster)
+ manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
+ fakeBgExecutor, dumpster)
manager.addListener(listener)
// Configure mocks.
@@ -101,28 +99,13 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
// Create a media sesssion and notification for testing.
- metadataBuilder = MediaMetadata.Builder().apply {
- putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
- putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
- }
- playbackBuilder = PlaybackState.Builder().apply {
- setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
- setActions(PlaybackState.ACTION_PLAY)
- }
- session = MediaSession(context, SESSION_KEY).apply {
- setMetadata(metadataBuilder.build())
- setPlaybackState(playbackBuilder.build())
- }
- session.setActive(true)
- notifBuilder = Notification.Builder(context, "NONE").apply {
- setContentTitle(SESSION_TITLE)
- setContentText(SESSION_ARTIST)
- setSmallIcon(android.R.drawable.ic_media_pause)
- setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
- }
+ session = MediaSession(context, SESSION_KEY)
+
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
+ whenever(controllerFactory.create(session.sessionToken))
+ .thenReturn(controller)
}
@After
@@ -336,6 +319,41 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
assertThat(data.icon).isNull()
}
+ @Test
+ fun audioInfoChanged() {
+ whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+ whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
+ // GIVEN a controller with local playback type
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(mr2)
+ // WHEN onAudioInfoChanged fires with remote playback type
+ whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+ verify(controller).registerCallback(captor.capture())
+ captor.value.onAudioInfoChanged(playbackInfo)
+ // THEN the route is checked
+ verify(mr2).getRoutingSessionForMediaController(eq(controller))
+ }
+
+ @Test
+ fun audioInfoHasntChanged() {
+ whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
+ // GIVEN a controller with remote playback type
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+ reset(mr2)
+ // WHEN onAudioInfoChanged fires with remote playback type
+ val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+ verify(controller).registerCallback(captor.capture())
+ captor.value.onAudioInfoChanged(playbackInfo)
+ // THEN the route is not checked
+ verify(mr2, never()).getRoutingSessionForMediaController(eq(controller))
+ }
+
fun captureCallback(): LocalMediaManager.DeviceCallback {
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
verify(lmm).registerCallback(captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
new file mode 100644
index 000000000000..5d81de6bce00
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -0,0 +1,258 @@
+/*
+ * 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.media
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.graphics.Color
+import android.media.MediaDescription
+import android.media.session.MediaSession
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+private const val KEY = "TEST_KEY"
+private const val OLD_KEY = "RESUME_KEY"
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE_NAME = "PKG"
+private const val CLASS_NAME = "CLASS"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val USER_ID = 0
+private const val MEDIA_PREFERENCES = "media_control_prefs"
+private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
+
+private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any<T>()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaResumeListenerTest : SysuiTestCase() {
+
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var device: MediaDeviceData
+ @Mock private lateinit var token: MediaSession.Token
+ @Mock private lateinit var tunerService: TunerService
+ @Mock private lateinit var resumeBrowserFactory: ResumeMediaBrowserFactory
+ @Mock private lateinit var resumeBrowser: ResumeMediaBrowser
+ @Mock private lateinit var sharedPrefs: SharedPreferences
+ @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var pendingIntent: PendingIntent
+
+ @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
+
+ private lateinit var executor: FakeExecutor
+ private lateinit var data: MediaData
+ private lateinit var resumeListener: MediaResumeListener
+
+ private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+ private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
+ Settings.Secure.putInt(context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+
+ whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
+ .thenReturn(resumeBrowser)
+
+ // resume components are stored in sharedpreferences
+ whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
+ .thenReturn(sharedPrefs)
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
+ whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
+ whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
+ whenever(mockContext.packageManager).thenReturn(context.packageManager)
+ whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
+
+ executor = FakeExecutor(FakeSystemClock())
+ resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ data = MediaData(
+ userId = USER_ID,
+ initialized = true,
+ backgroundColor = BG_COLOR,
+ app = APP,
+ appIcon = null,
+ artist = ARTIST,
+ song = TITLE,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = PACKAGE_NAME,
+ token = token,
+ clickIntent = null,
+ device = device,
+ active = true,
+ notificationKey = KEY,
+ resumeAction = null)
+ }
+
+ @After
+ fun tearDown() {
+ Settings.Global.putInt(context.contentResolver,
+ Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
+ Settings.Secure.putInt(context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
+ }
+
+ @Test
+ fun testWhenNoResumption_doesNothing() {
+ Settings.Secure.putInt(context.contentResolver,
+ Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
+
+ // When listener is created, we do NOT register a user change listener
+ val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
+ resumeBrowserFactory)
+ listener.setManager(mediaDataManager)
+ verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
+ any(), any(), any())
+
+ // When data is loaded, we do NOT execute or update anything
+ listener.onMediaDataLoaded(KEY, OLD_KEY, data)
+ assertThat(executor.numPending()).isEqualTo(0)
+ verify(mediaDataManager, never()).setResumeAction(any(), any())
+ }
+
+ @Test
+ fun testOnLoad_checksForResume_noService() {
+ // When media data is loaded that has not been checked yet, and does not have a MBS
+ resumeListener.onMediaDataLoaded(KEY, null, data)
+
+ // Then we report back to the manager
+ verify(mediaDataManager).setResumeAction(KEY, null)
+ }
+
+ @Test
+ fun testOnLoad_checksForResume_hasService() {
+ // Set up mocks to successfully find a MBS that returns valid media
+ val pm = mock(PackageManager::class.java)
+ whenever(mockContext.packageManager).thenReturn(pm)
+ val resolveInfo = ResolveInfo()
+ val serviceInfo = ServiceInfo()
+ serviceInfo.packageName = PACKAGE_NAME
+ resolveInfo.serviceInfo = serviceInfo
+ resolveInfo.serviceInfo.name = CLASS_NAME
+ val resumeInfo = listOf(resolveInfo)
+ whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
+
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.testConnection()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // When media data is loaded that has not been checked yet, and does have a MBS
+ val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+ resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+ // Then we test whether the service is valid
+ executor.runAllReady()
+ verify(resumeBrowser).testConnection()
+
+ // And since it is, we report back to the manager
+ verify(mediaDataManager).setResumeAction(eq(KEY), any())
+
+ // But we do not tell it to add new controls
+ verify(mediaDataManager, never())
+ .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
+
+ // Finally, make sure the resume browser disconnected
+ verify(resumeBrowser).disconnect()
+ }
+
+ @Test
+ fun testOnLoad_doesNotCheckAgain() {
+ // When a media data is loaded that has been checked already
+ var dataCopy = data.copy(hasCheckedForResume = true)
+ resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+ // Then we should not check it again
+ verify(resumeBrowser, never()).testConnection()
+ verify(mediaDataManager, never()).setResumeAction(KEY, null)
+ }
+
+ @Test
+ fun testOnUserUnlock_loadsTracks() {
+ // Set up mock service to successfully find valid media
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Make sure broadcast receiver is registered
+ resumeListener.setManager(mediaDataManager)
+ verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
+ any(), any(), any())
+
+ // When we get an unlock event
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(context, intent)
+
+ // Then we should attempt to find recent media for each saved component
+ verify(resumeBrowser, times(3)).findRecentMedia()
+
+ // Then since the mock service found media, the manager should be informed
+ verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index f38524369b46..f3979592c894 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -23,8 +23,9 @@ import android.media.session.PlaybackState
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -33,7 +34,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
@@ -63,10 +63,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Mock private lateinit var mediaControllerFactory: MediaControllerFactory
@Mock private lateinit var mediaController: MediaController
- @Mock private lateinit var executor: DelayableExecutor
+ private lateinit var executor: FakeExecutor
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
- @Mock private lateinit var cancellationRunnable: Runnable
- @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable>
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
@JvmField @Rule val mockito = MockitoJUnit.rule()
private lateinit var metadataBuilder: MediaMetadata.Builder
@@ -78,7 +76,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Before
fun setup() {
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
- `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable)
+ executor = FakeExecutor(FakeSystemClock())
mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
mediaTimeoutListener.timeoutCallback = timeoutCallback
@@ -120,7 +118,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
fun testOnMediaDataLoaded_registersTimeout_whenPaused() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
- verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+ assertThat(executor.numPending()).isEqualTo(1)
verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
}
@@ -137,6 +135,17 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
@Test
+ fun testOnMediaDataRemoved_clearsTimeout() {
+ // GIVEN media that is paused
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ assertThat(executor.numPending()).isEqualTo(1)
+ // WHEN the media is removed
+ mediaTimeoutListener.onMediaDataRemoved(KEY)
+ // THEN the timeout runnable is cancelled
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
fun testOnMediaDataLoaded_migratesKeys() {
// From not playing
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
@@ -151,7 +160,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
verify(mediaController).registerCallback(anyObject())
// Enqueues callback
- verify(executor).execute(anyObject())
+ assertThat(executor.numPending()).isEqualTo(1)
}
@Test
@@ -166,8 +175,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
`when`(mediaController.playbackState).thenReturn(playingState)
mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
- // Never cancels callback, or schedule another one
- verify(cancellationRunnable, never()).run()
+ // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor
+ // is another scheduled
+ assertThat(executor.numPending()).isEqualTo(1)
}
@Test
@@ -177,7 +187,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
- verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+ assertThat(executor.numPending()).isEqualTo(1)
}
@Test
@@ -187,7 +197,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
- verify(cancellationRunnable).run()
+ assertThat(executor.numPending()).isEqualTo(0)
}
@Test
@@ -195,10 +205,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- clearInvocations(cancellationRunnable)
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
- verify(cancellationRunnable, never()).run()
+ assertThat(executor.numPending()).isEqualTo(1)
}
@Test
@@ -206,7 +215,10 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we're have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- timeoutCaptor.value.run()
+ with(executor) {
+ advanceClockToNext()
+ runAllReady()
+ }
verify(timeoutCallback).invoke(eq(KEY), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
new file mode 100644
index 000000000000..d26229edf71a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt
@@ -0,0 +1,287 @@
+/*
+ * 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.media
+
+import android.content.ComponentName
+import android.content.Context
+import android.media.MediaDescription
+import android.media.browse.MediaBrowser
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.service.media.MediaBrowserService
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+private const val PACKAGE_NAME = "package"
+private const val CLASS_NAME = "class"
+private const val TITLE = "song title"
+private const val MEDIA_ID = "media ID"
+private const val ROOT = "media browser root"
+
+private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any<T>()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+public class ResumeMediaBrowserTest : SysuiTestCase() {
+
+ private lateinit var resumeBrowser: TestableResumeMediaBrowser
+ private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ private val description = MediaDescription.Builder()
+ .setTitle(TITLE)
+ .setMediaId(MEDIA_ID)
+ .build()
+
+ @Mock lateinit var callback: ResumeMediaBrowser.Callback
+ @Mock lateinit var listener: MediaResumeListener
+ @Mock lateinit var service: MediaBrowserService
+ @Mock lateinit var browserFactory: MediaBrowserFactory
+ @Mock lateinit var browser: MediaBrowser
+ @Mock lateinit var token: MediaSession.Token
+ @Mock lateinit var mediaController: MediaController
+ @Mock lateinit var transportControls: MediaController.TransportControls
+
+ @Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback>
+ @Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(browserFactory.create(any(), capture(connectionCallback), any()))
+ .thenReturn(browser)
+
+ whenever(mediaController.transportControls).thenReturn(transportControls)
+
+ resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory,
+ mediaController)
+ }
+
+ @Test
+ fun testConnection_connectionFails_callsOnError() {
+ // When testConnection cannot connect to the service
+ setupBrowserFailed()
+ resumeBrowser.testConnection()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testConnection_connects_onConnected() {
+ // When testConnection can connect to the service
+ setupBrowserConnection()
+ resumeBrowser.testConnection()
+
+ // Then it calls onConnected
+ verify(callback).onConnected()
+ }
+
+ @Test
+ fun testConnection_noValidMedia_error() {
+ // When testConnection can connect to the service, and does not find valid media
+ setupBrowserConnectionNoResults()
+ resumeBrowser.testConnection()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testConnection_hasValidMedia_addTrack() {
+ // When testConnection can connect to the service, and finds valid media
+ setupBrowserConnectionValidMedia()
+ resumeBrowser.testConnection()
+
+ // Then it calls addTrack
+ verify(callback).onConnected()
+ verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
+ }
+
+ @Test
+ fun testFindRecentMedia_connectionFails_error() {
+ // When findRecentMedia is called and we cannot connect
+ setupBrowserFailed()
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testFindRecentMedia_noRoot_error() {
+ // When findRecentMedia is called and does not get a valid root
+ setupBrowserConnection()
+ whenever(browser.getRoot()).thenReturn(null)
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testFindRecentMedia_connects_onConnected() {
+ // When findRecentMedia is called and we connect
+ setupBrowserConnection()
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls onConnected
+ verify(callback).onConnected()
+ }
+
+ @Test
+ fun testFindRecentMedia_noChildren_error() {
+ // When findRecentMedia is called and we connect, but do not get any results
+ setupBrowserConnectionNoResults()
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testFindRecentMedia_notPlayable_error() {
+ // When findRecentMedia is called and we connect, but do not get a playable child
+ setupBrowserConnectionNotPlayable()
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testFindRecentMedia_hasValidMedia_addTrack() {
+ // When findRecentMedia is called and we can connect and get playable media
+ setupBrowserConnectionValidMedia()
+ resumeBrowser.findRecentMedia()
+
+ // Then it calls addTrack
+ verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
+ }
+
+ @Test
+ fun testRestart_connectionFails_error() {
+ // When restart is called and we cannot connect
+ setupBrowserFailed()
+ resumeBrowser.restart()
+
+ // Then it calls onError
+ verify(callback).onError()
+ }
+
+ @Test
+ fun testRestart_connects() {
+ // When restart is called and we connect successfully
+ setupBrowserConnection()
+ resumeBrowser.restart()
+
+ // Then it creates a new controller and sends play command
+ verify(transportControls).prepare()
+ verify(transportControls).play()
+
+ // Then it calls onConnected
+ verify(callback).onConnected()
+ }
+
+ /**
+ * Helper function to mock a failed connection
+ */
+ private fun setupBrowserFailed() {
+ whenever(browser.connect()).thenAnswer {
+ connectionCallback.value.onConnectionFailed()
+ }
+ }
+
+ /**
+ * Helper function to mock a successful connection only
+ */
+ private fun setupBrowserConnection() {
+ whenever(browser.connect()).thenAnswer {
+ connectionCallback.value.onConnected()
+ }
+ whenever(browser.isConnected()).thenReturn(true)
+ whenever(browser.getRoot()).thenReturn(ROOT)
+ whenever(browser.sessionToken).thenReturn(token)
+ }
+
+ /**
+ * Helper function to mock a successful connection, but no media results
+ */
+ private fun setupBrowserConnectionNoResults() {
+ setupBrowserConnection()
+ whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+ subscriptionCallback.value.onChildrenLoaded(ROOT, emptyList())
+ }
+ }
+
+ /**
+ * Helper function to mock a successful connection, but no playable results
+ */
+ private fun setupBrowserConnectionNotPlayable() {
+ setupBrowserConnection()
+
+ val child = MediaBrowser.MediaItem(description, 0)
+
+ whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+ subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
+ }
+ }
+
+ /**
+ * Helper function to mock a successful connection with playable media
+ */
+ private fun setupBrowserConnectionValidMedia() {
+ setupBrowserConnection()
+
+ val child = MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)
+
+ whenever(browser.serviceComponent).thenReturn(component)
+ whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
+ subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
+ }
+ }
+
+ /**
+ * Override so media controller use is testable
+ */
+ private class TestableResumeMediaBrowser(
+ context: Context,
+ callback: Callback,
+ componentName: ComponentName,
+ browserFactory: MediaBrowserFactory,
+ private val fakeController: MediaController
+ ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) {
+
+ override fun createMediaController(token: MediaSession.Token): MediaController {
+ return fakeController
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 0e376bd356a2..2d460aa0c9c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -192,6 +192,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
public void onItemClick_clickDevice_verifyConnectDevice() {
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mFrameLayout.performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 42b21c61510a..27b5b7fda684 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -36,7 +36,6 @@ import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
@@ -55,7 +54,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
// Mock
private MediaOutputBaseAdapter mMediaOutputBaseAdapter = mock(MediaOutputBaseAdapter.class);
-
private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
private ShadeController mShadeController = mock(ShadeController.class);
@@ -157,30 +155,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
verify(mMediaOutputBaseAdapter).notifyDataSetChanged();
}
- @Test
- public void refresh_with6Devices_checkBottomPaddingVisibility() {
- for (int i = 0; i < 6; i++) {
- mMediaOutputController.mMediaDevices.add(mock(MediaDevice.class));
- }
- mMediaOutputBaseDialogImpl.refresh();
- final View view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
- R.id.list_bottom_padding);
-
- assertThat(view.getVisibility()).isEqualTo(View.GONE);
- }
-
- @Test
- public void refresh_with5Devices_checkBottomPaddingVisibility() {
- for (int i = 0; i < 5; i++) {
- mMediaOutputController.mMediaDevices.add(mock(MediaDevice.class));
- }
- mMediaOutputBaseDialogImpl.refresh();
- final View view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
- R.id.list_bottom_padding);
-
- assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
@@ -189,24 +163,34 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mAdapter = mMediaOutputBaseAdapter;
}
+ @Override
int getHeaderIconRes() {
return mHeaderIconRes;
}
+ @Override
IconCompat getHeaderIcon() {
return mIconCompat;
}
+ @Override
int getHeaderIconSize() {
return 10;
}
+ @Override
CharSequence getHeaderText() {
return mHeaderTitle;
}
+ @Override
CharSequence getHeaderSubtitle() {
return mHeaderSubtitle;
}
+
+ @Override
+ int getStopButtonVisibility() {
+ return 0;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
new file mode 100644
index 000000000000..9ebb587d3d85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.media.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.MediaRoute2Info;
+import android.media.session.MediaSessionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.phone.ShadeController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaOutputDialogTest extends SysuiTestCase {
+
+ private static final String TEST_PACKAGE = "test_package";
+
+ // Mock
+ private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+ private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+ private ShadeController mShadeController = mock(ShadeController.class);
+ private ActivityStarter mStarter = mock(ActivityStarter.class);
+ private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+ private MediaDevice mMediaDevice = mock(MediaDevice.class);
+
+ private MediaOutputDialog mMediaOutputDialog;
+ private MediaOutputController mMediaOutputController;
+ private List<String> mFeatures = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter);
+ mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputDialog = new MediaOutputDialog(mContext, false, mMediaOutputController);
+
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
+ when(mMediaDevice.getFeatures()).thenReturn(mFeatures);
+ }
+
+ @After
+ public void tearDown() {
+ mMediaOutputDialog.dismissDialog();
+ }
+
+ @Test
+ public void getStopButtonVisibility_remoteDevice_returnVisible() {
+ mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
+
+ mFeatures.clear();
+ mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
+
+ mFeatures.clear();
+ mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
+
+ mFeatures.clear();
+ mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void getStopButtonVisibility_localDevice_returnGone() {
+ mFeatures.add(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.GONE);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index 3f10c8da576b..3494bd61b656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -32,15 +32,11 @@ import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarBut
import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP;
-import static junit.framework.Assert.assertEquals;
-
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.hardware.input.InputManager;
import android.testing.AndroidTestingRunner;
@@ -53,8 +49,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.navigationbar.buttons.KeyButtonView;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.recents.OverviewProxyService;
import org.junit.Before;
@@ -71,7 +66,7 @@ public class KeyButtonViewTest extends SysuiTestCase {
private KeyButtonView mKeyButtonView;
private MetricsLogger mMetricsLogger;
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
private UiEventLogger mUiEventLogger;
private InputManager mInputManager = mock(InputManager.class);
@Captor
@@ -81,7 +76,7 @@ public class KeyButtonViewTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
- mBubbleController = mDependency.injectMockDependency(BubbleController.class);
+ mBubbles = mDependency.injectMockDependency(Bubbles.class);
mDependency.injectMockDependency(OverviewProxyService.class);
mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class);
@@ -152,19 +147,4 @@ public class KeyButtonViewTest extends SysuiTestCase {
verify(mUiEventLogger, times(1)).log(expected);
}
}
-
- @Test
- public void testBubbleEvents_bubbleExpanded() {
- when(mBubbleController.getExpandedDisplayId(mContext)).thenReturn(3);
-
- int action = KeyEvent.ACTION_DOWN;
- int flags = 0;
- int code = KeyEvent.KEYCODE_BACK;
- mKeyButtonView.setCode(code);
- mKeyButtonView.sendEvent(action, flags);
-
- verify(mInputManager, times(1)).injectInputEvent(mInputEventCaptor.capture(),
- anyInt());
- assertEquals(3, mInputEventCaptor.getValue().getDisplayId());
- }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
index 1f10d013222d..cb17829ddaf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerFlagsTest.kt
@@ -16,15 +16,14 @@
package com.android.systemui.privacy
-import android.os.UserManager
import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
@@ -62,11 +61,9 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
@Mock
private lateinit var callback: PrivacyItemController.Callback
@Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var userTracker: UserTracker
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
@@ -77,9 +74,8 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
appOpsController,
executor,
executor,
- broadcastDispatcher,
deviceConfigProxy,
- userManager,
+ userTracker,
dumpManager
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 0a079b148f5d..16a11050f9bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -18,10 +18,8 @@ package com.android.systemui.privacy
import android.app.ActivityManager
import android.app.AppOpsManager
-import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
-import android.os.UserManager
import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -30,8 +28,8 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpItem
import com.android.systemui.appops.AppOpsController
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
@@ -52,6 +50,7 @@ import org.mockito.ArgumentMatchers.anyList
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
@@ -84,9 +83,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Mock
private lateinit var callback: PrivacyItemController.Callback
@Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
+ private lateinit var userTracker: UserTracker
@Mock
private lateinit var dumpManager: DumpManager
@Captor
@@ -103,9 +100,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
appOpsController,
executor,
executor,
- broadcastDispatcher,
deviceConfigProxy,
- userManager,
+ userTracker,
dumpManager
)
}
@@ -119,11 +115,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
// Listen to everything by default
changeAll(true)
- doReturn(listOf(object : UserInfo() {
- init {
- id = CURRENT_USER_ID
- }
- })).`when`(userManager).getProfiles(anyInt())
+ `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0)))
privacyItemController = PrivacyItemController()
}
@@ -163,37 +155,26 @@ class PrivacyItemControllerTest : SysuiTestCase() {
}
@Test
- fun testRegisterReceiver_allUsers() {
+ fun testRegisterCallback() {
privacyItemController.addCallback(callback)
executor.runAllReady()
- verify(broadcastDispatcher, atLeastOnce()).registerReceiver(
- eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL))
- verify(broadcastDispatcher, never())
- .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver))
- }
-
- @Test
- fun testReceiver_ACTION_USER_FOREGROUND() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_USER_SWITCHED))
- executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
+ verify(userTracker, atLeastOnce()).addCallback(
+ eq(privacyItemController.userTrackerCallback), any())
+ verify(userTracker, never()).removeCallback(eq(privacyItemController.userTrackerCallback))
}
@Test
- fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE))
+ fun testCallback_userChanged() {
+ privacyItemController.userTrackerCallback.onUserChanged(0, mContext)
executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
+ verify(userTracker).userProfiles
}
@Test
- fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE))
+ fun testReceiver_profilesChanged() {
+ privacyItemController.userTrackerCallback.onProfilesChanged(emptyList())
executor.runAllReady()
- verify(userManager).getProfiles(anyInt())
+ verify(userTracker).userProfiles
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
index f29d6a2307a5..35ecb1424942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java
@@ -14,30 +14,40 @@
package com.android.systemui.qs;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.R.id;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -47,19 +57,46 @@ public class QSFooterImplTest extends LeakCheckedTest {
private QSFooterImpl mFooter;
private ActivityStarter mActivityStarter;
private DeviceProvisionedController mDeviceProvisionedController;
+ private UserInfoController mUserInfoController;
+ private UserTracker mUserTracker;
+ @Mock
+ private ClipboardManager mClipboardManager;
@Before
public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
mDeviceProvisionedController = mDependency.injectMockDependency(
DeviceProvisionedController.class);
+ mUserInfoController = mDependency.injectMockDependency(UserInfoController.class);
+ mUserTracker = mDependency.injectMockDependency(UserTracker.class);
+
+ mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
+
+ when(mUserTracker.getUserContext()).thenReturn(mContext);
+
TestableLooper.get(this).runWithLooper(
() -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate(
R.layout.qs_footer_impl, null));
}
@Test
+ public void testBuildTextCopy() {
+ TextView buildTextView = mFooter.requireViewById(R.id.build);
+ CharSequence buildText = "TEST";
+ buildTextView.setText(buildText);
+ buildTextView.setLongClickable(true);
+
+ buildTextView.performLongClick();
+
+ ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class);
+ verify(mClipboardManager).setPrimaryClip(captor.capture());
+ assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(buildText);
+ }
+
+ @Test
@Ignore("failing")
public void testSettings_UserNotSetup() {
View settingsButton = mFooter.findViewById(id.settings_button);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index b46c6ef81fa7..e472cb22ed6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -37,7 +37,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -45,6 +44,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -107,7 +107,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mock(PluginManager.class), mock(TunerService.class),
() -> mock(AutoTileManager.class), mock(DumpManager.class),
mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)),
- mock(QSLogger.class), mock(UiEventLogger.class));
+ mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class));
qs.setHost(host);
qs.setListening(true);
@@ -115,11 +115,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
qs.setListening(false);
processAllMessages();
-
- // Manually push header through detach so it can handle standard cleanup it does on
- // removed from window.
- ((QuickStatusBarHeader) qs.getView().findViewById(R.id.header)).onDetachedFromWindow();
-
host.destroy();
processAllMessages();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index cb3a04862eb7..4b7a26870308 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -38,7 +38,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.UiEventLoggerFake;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -49,7 +48,7 @@ import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.util.animation.DisappearParameters;
import com.android.systemui.util.animation.UniqueObjectHostView;
@@ -93,11 +92,9 @@ public class QSPanelTest extends SysuiTestCase {
@Mock
private MediaHost mMediaHost;
@Mock
- private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
private ActivityStarter mActivityStarter;
- @Mock
- private NotificationEntryManager mEntryManager;
+ @Mock(stubOnly = true)
+ private UserTracker mUserTracker;
private UiEventLoggerFake mUiEventLogger;
@Before
@@ -117,7 +114,7 @@ public class QSPanelTest extends SysuiTestCase {
mTestableLooper.runWithLooper(() -> {
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mQsPanel = new QSPanel(mContext, null, mDumpManager, mBroadcastDispatcher,
- mQSLogger, mMediaHost, mUiEventLogger);
+ mQSLogger, mMediaHost, mUiEventLogger, mUserTracker);
mQsPanel.onFinishInflate();
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 417b19f0cfc1..fd1866b9ebc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -17,7 +17,6 @@ package com.android.systemui.qs;
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -25,7 +24,6 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.os.UserManager;
import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -42,6 +40,7 @@ import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController;
import org.junit.Before;
@@ -73,20 +72,20 @@ public class QSSecurityFooterTest extends SysuiTestCase {
private TestableImageView mFooterIcon;
private QSSecurityFooter mFooter;
private SecurityController mSecurityController = mock(SecurityController.class);
- private UserManager mUserManager;
+ private UserTracker mUserTracker;
@Before
public void setUp() {
mDependency.injectTestDependency(SecurityController.class, mSecurityController);
mDependency.injectTestDependency(Dependency.BG_LOOPER,
TestableLooper.get(this).getLooper());
+ mUserTracker = mock(UserTracker.class);
+ when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE,
new LayoutInflaterBuilder(mContext)
.replace("ImageView", TestableImageView.class)
.build());
- mUserManager = Mockito.mock(UserManager.class);
- mContext.addMockSystemService(Context.USER_SERVICE, mUserManager);
- mFooter = new QSSecurityFooter(null, mContext);
+ mFooter = new QSSecurityFooter(null, mContext, mUserTracker);
mRootView = (ViewGroup) mFooter.getView();
mFooterText = mRootView.findViewById(R.id.footer_text);
mFooterIcon = mRootView.findViewById(R.id.footer_icon);
@@ -141,7 +140,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
final UserInfo mockUserInfo = Mockito.mock(UserInfo.class);
when(mockUserInfo.isDemo()).thenReturn(true);
- when(mUserManager.getUserInfo(anyInt())).thenReturn(mockUserInfo);
+ when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
mFooter.refreshState();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c8e1a74d969f..452ff4af7c15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -55,6 +55,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -110,6 +111,8 @@ public class QSTileHostTest extends SysuiTestCase {
private CustomTile mCustomTile;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserTracker mUserTracker;
private Handler mHandler;
private TestableLooper mLooper;
@@ -122,7 +125,7 @@ public class QSTileHostTest extends SysuiTestCase {
mHandler = new Handler(mLooper.getLooper());
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
- mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger);
+ mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker);
setUpTileFactory();
Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
@@ -301,10 +304,10 @@ public class QSTileHostTest extends SysuiTestCase {
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger, UserTracker userTracker) {
super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
tunerService, autoTiles, dumpManager, broadcastDispatcher,
- Optional.of(statusBar), qsLogger, uiEventLogger);
+ Optional.of(statusBar), qsLogger, uiEventLogger, userTracker);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 2ef7c65acb0b..a2ffb8409c1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -55,6 +55,7 @@ import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -103,6 +104,8 @@ public class TileQueryHelperTest extends SysuiTestCase {
private QSTileHost mQSTileHost;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private UserTracker mUserTracker;
@Captor
private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor;
@@ -133,7 +136,7 @@ public class TileQueryHelperTest extends SysuiTestCase {
FakeSystemClock clock = new FakeSystemClock();
mMainExecutor = new FakeExecutor(clock);
mBgExecutor = new FakeExecutor(clock);
- mTileQueryHelper = new TileQueryHelper(mContext, mMainExecutor, mBgExecutor);
+ mTileQueryHelper = new TileQueryHelper(mContext, mUserTracker, mMainExecutor, mBgExecutor);
mTileQueryHelper.setListener(mListener);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 683e8f4a2511..6a9d9fa6d5a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -22,11 +22,13 @@ import static junit.framework.Assert.assertTrue;
import android.content.ComponentName;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.settings.UserTracker;
import org.junit.After;
import org.junit.Before;
@@ -44,6 +46,7 @@ public class TileServiceManagerTest extends SysuiTestCase {
private HandlerThread mThread;
private Handler mHandler;
private TileServiceManager mTileServiceManager;
+ private UserTracker mUserTracker;
@Before
public void setUp() throws Exception {
@@ -51,13 +54,18 @@ public class TileServiceManagerTest extends SysuiTestCase {
mThread.start();
mHandler = Handler.createAsync(mThread.getLooper());
mTileServices = Mockito.mock(TileServices.class);
+ mUserTracker = Mockito.mock(UserTracker.class);
+ Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+
Mockito.when(mTileServices.getContext()).thenReturn(mContext);
mTileLifecycle = Mockito.mock(TileLifecycleManager.class);
Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false);
ComponentName componentName = new ComponentName(mContext,
TileServiceManagerTest.class);
Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName);
- mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mTileLifecycle);
+ mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+ mTileLifecycle);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 53ed4cf12b2f..2a3bc31cb2a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -46,6 +46,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -92,6 +93,8 @@ public class TileServicesTest extends SysuiTestCase {
private QSLogger mQSLogger;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserTracker mUserTracker;
@Before
public void setUp() throws Exception {
@@ -110,8 +113,10 @@ public class TileServicesTest extends SysuiTestCase {
mock(BroadcastDispatcher.class),
Optional.of(mStatusBar),
mQSLogger,
- mUiEventLogger);
- mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher);
+ mUiEventLogger,
+ mUserTracker);
+ mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher,
+ mUserTracker);
}
@After
@@ -186,8 +191,8 @@ public class TileServicesTest extends SysuiTestCase {
private class TestTileServices extends TileServices {
TestTileServices(QSTileHost host, Looper looper,
- BroadcastDispatcher broadcastDispatcher) {
- super(host, looper, broadcastDispatcher);
+ BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) {
+ super(host, looper, broadcastDispatcher, userTracker);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 5d14898cdd2c..faf43a21356e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@ import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
+import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -73,12 +74,16 @@ public class CastTileTest extends SysuiTestCase {
@Mock
private QSTileHost mHost;
@Mock
- NetworkController.SignalCallback mCallback;
+ NetworkController.SignalCallback mSignalCallback;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
+ private HotspotController mHotspotController;
+ @Mock
+ private HotspotController.Callback mHotspotCallback;
+ @Mock
private QSLogger mQSLogger;
private TestableLooper mTestableLooper;
@@ -101,7 +106,8 @@ public class CastTileTest extends SysuiTestCase {
mQSLogger,
mController,
mKeyguard,
- mNetworkController
+ mNetworkController,
+ mHotspotController
);
// We are not setting the mocks to listening, so we trigger a first refresh state to
@@ -113,14 +119,22 @@ public class CastTileTest extends SysuiTestCase {
ArgumentCaptor.forClass(NetworkController.SignalCallback.class);
verify(mNetworkController).observe(any(LifecycleOwner.class),
signalCallbackArgumentCaptor.capture());
- mCallback = signalCallbackArgumentCaptor.getValue();
+ mSignalCallback = signalCallbackArgumentCaptor.getValue();
+
+ ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(HotspotController.Callback.class);
+ verify(mHotspotController).observe(any(LifecycleOwner.class),
+ hotspotCallbackArgumentCaptor.capture());
+ mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
}
+ // -------------------------------------------------
+ // All these tests for enabled/disabled wifi have hotspot not enabled
@Test
public void testStateUnavailable_wifiDisabled() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(false, 0, "");
- mCallback.setWifiIndicators(false, mock(NetworkController.IconState.class),
+ mSignalCallback.setWifiIndicators(false, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
mTestableLooper.processAllMessages();
@@ -132,7 +146,7 @@ public class CastTileTest extends SysuiTestCase {
public void testStateUnavailable_wifiNotConnected() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(false, 0, "");
- mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+ mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
mTestableLooper.processAllMessages();
@@ -143,7 +157,7 @@ public class CastTileTest extends SysuiTestCase {
private void enableWifiAndProcessMessages() {
NetworkController.IconState qsIcon =
new NetworkController.IconState(true, 0, "");
- mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
+ mSignalCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
qsIcon, false,false, "",
false, "");
mTestableLooper.processAllMessages();
@@ -166,6 +180,46 @@ public class CastTileTest extends SysuiTestCase {
enableWifiAndProcessMessages();
assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
}
+ // -------------------------------------------------
+
+ // -------------------------------------------------
+ // All these tests for enabled/disabled hotspot have wifi not enabled
+ @Test
+ public void testStateUnavailable_hotspotDisabled() {
+ mHotspotCallback.onHotspotChanged(false, 0);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void testStateUnavailable_hotspotEnabledNotConnected() {
+ mHotspotCallback.onHotspotChanged(true, 0);
+ mTestableLooper.processAllMessages();
+
+ assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void testStateActive_hotspotEnabledAndConnectedAndCasting() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastController.CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ mHotspotCallback.onHotspotChanged(true, 1);
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+ }
+
+ @Test
+ public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() {
+ mHotspotCallback.onHotspotChanged(true, 1);
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+ }
+ // -------------------------------------------------
@Test
public void testHandleClick_castDevicePresent() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
new file mode 100644
index 000000000000..0c3db57f1a2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.recents;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+import dagger.Lazy;
+
+/**
+ * Unit tests for {@link com.android.systemui.recents.OverviewProxyService}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class OverviewProxyServiceTest extends SysuiTestCase {
+ private OverviewProxyService mSpiedOverviewProxyService;
+ private TestableContext mSpiedContext;
+
+ @Mock private BroadcastDispatcher mMockBroadcastDispatcher;
+ @Mock private CommandQueue mMockCommandQueue;
+ @Mock private Lazy<NavigationBarController> mMockNavBarControllerLazy;
+ @Mock private IPinnedStackAnimationListener mMockPinnedStackAnimationListener;
+ @Mock private NavigationModeController mMockNavModeController;
+ @Mock private NotificationShadeWindowController mMockStatusBarWinController;
+ @Mock private Optional<Pip> mMockPipOptional;
+ @Mock private Optional<SplitScreen> mMockSplitScreenOptional;
+ @Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy;
+ @Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional;
+ @Mock private PackageManager mPackageManager;
+ @Mock private SysUiState mMockSysUiState;
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+
+ mSpiedContext = spy(mContext);
+
+ when(mPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+ when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
+
+ mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue,
+ mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController,
+ mMockSysUiState, mMockPipOptional, mMockSplitScreenOptional,
+ mMockStatusBarOptionalLazy, mMockOneHandedOptional,
+ mMockBroadcastDispatcher));
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotNotifySwipeToHomeFinished() throws RemoteException {
+ mSpiedOverviewProxyService.mSysUiProxy.notifySwipeToHomeFinished();
+
+ verify(mMockPipOptional, never()).ifPresent(any());
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotSetPinnedStackAnimationListener() throws RemoteException {
+ mSpiedOverviewProxyService.mSysUiProxy.setPinnedStackAnimationListener(
+ mMockPinnedStackAnimationListener);
+
+ verify(mMockPipOptional, never()).ifPresent(any());
+ }
+
+ @Test
+ public void testNonPipDevice_shouldNotSetShelfHeight() throws RemoteException {
+ mSpiedOverviewProxyService.mSysUiProxy.setShelfHeight(true /* visible */,
+ 100 /* shelfHeight */);
+
+ verify(mMockPipOptional, never()).ifPresent(any());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java
new file mode 100644
index 000000000000..fde56ba76736
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RecyclerViewActivity.java
@@ -0,0 +1,80 @@
+/*
+ * 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.screenshot;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.LayoutParams;
+
+import java.util.Random;
+
+public class RecyclerViewActivity extends Activity {
+ public static final int CHILD_VIEW_HEIGHT = 300;
+ private static final int CHILD_VIEWS = 12;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ RecyclerView recyclerView = new RecyclerView(this);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setAdapter(new TestAdapter());
+ recyclerView.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ setContentView(recyclerView);
+ }
+
+ static final class TestViewHolder extends RecyclerView.ViewHolder {
+ TestViewHolder(View itemView) {
+ super(itemView);
+ }
+ }
+
+ static final class TestAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private final Random mRandom = new Random();
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new TestViewHolder(new TextView(parent.getContext()));
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ TextView view = (TextView) holder.itemView;
+ view.setText("Child #" + position);
+ view.setTextColor(Color.WHITE);
+ view.setTextSize(30f);
+ view.setBackgroundColor(
+ Color.rgb(mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat()));
+ view.setMinHeight(CHILD_VIEW_HEIGHT);
+ }
+
+ @Override
+ public int getItemCount() {
+ return CHILD_VIEWS;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 402a99d4c23d..dee6020dfd56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -108,11 +108,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase {
return new TestableAlertingNotificationManager();
}
- protected StatusBarNotification createNewNotification(int id) {
- Notification.Builder n = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
+ protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
return new StatusBarNotification(
TEST_PACKAGE_NAME /* pkg */,
TEST_PACKAGE_NAME,
@@ -126,6 +122,14 @@ public class AlertingNotificationManagerTest extends SysuiTestCase {
0 /* postTime */);
}
+ protected StatusBarNotification createNewNotification(int id) {
+ Notification.Builder n = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ return createNewSbn(id, n);
+ }
+
@Before
public void setUp() {
mTestHandler = Handler.createAsync(Looper.myLooper());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 72e6df27a254..724ea023f2bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -80,7 +80,7 @@ class MediaArtworkProcessorTest : SysuiTestCase() {
val background2 = processor.processArtwork(context, artwork)!!
// THEN the two bitmaps are the same
// Note: This is currently broken and trying to use caching causes issues
- assertThat(background1).isNotSameAs(background2)
+ assertThat(background1).isNotSameInstanceAs(background2)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index a36a4c43e278..0bf1ac31c9dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -45,6 +45,7 @@ import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doThrow
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -79,6 +80,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Before
fun setup() {
`when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(root.isAttachedToWindow).thenReturn(true)
`when`(statusBarStateController.state).then { statusBarState }
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
(answer.arguments[0] as Float * maxBlur).toInt()
@@ -219,6 +221,13 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun updateBlurCallback_invalidWindow() {
+ `when`(root.isAttachedToWindow).thenReturn(false)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(wallpaperManager, times(0)).setWallpaperZoomOut(any(), anyFloat())
+ }
+
+ @Test
+ fun updateBlurCallback_exception() {
doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
.setWallpaperZoomOut(any(), anyFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index d041ee047ae0..10eca00feec4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,7 +36,7 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
@@ -65,6 +65,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import java.util.List;
+import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -106,7 +107,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
mock(StatusBarStateControllerImpl.class), mEntryManager,
mock(KeyguardBypassController.class),
- mock(BubbleController.class),
+ Optional.of(mock(Bubbles.class)),
mock(DynamicPrivacyController.class),
mock(ForegroundServiceSectionController.class),
mock(DynamicChildBindController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
new file mode 100644
index 000000000000..16eb1d9dbca1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.statusbar.commandline
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+
+import com.android.systemui.SysuiTestCase
+
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.Executor
+
+private fun <T> anyObject(): T {
+ return Mockito.anyObject<T>()
+}
+
+private fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class CommandRegistryTest : SysuiTestCase() {
+ lateinit var registry: CommandRegistry
+ val inLineExecutor = object : Executor {
+ override fun execute(command: Runnable) {
+ command.run()
+ }
+ }
+
+ val writer: PrintWriter = PrintWriter(StringWriter())
+
+ @Before
+ fun setup() {
+ registry = CommandRegistry(context, inLineExecutor)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun testRegisterCommand_throwsWhenAlreadyRegistered() {
+ registry.registerCommand(COMMAND) { FakeCommand() }
+ // Should throw when registering the same command twice
+ registry.registerCommand(COMMAND) { FakeCommand() }
+ }
+
+ @Test
+ fun testOnShellCommand() {
+ var fakeCommand = mock(Command::class.java)
+ registry.registerCommand(COMMAND) { fakeCommand }
+ registry.onShellCommand(writer, arrayOf(COMMAND))
+ verify(fakeCommand).execute(anyObject(), anyList())
+ }
+
+ @Test
+ fun testArgsPassedToShellCommand() {
+ var fakeCommand = mock(Command::class.java)
+ registry.registerCommand(COMMAND) { fakeCommand }
+ registry.onShellCommand(writer, arrayOf(COMMAND, "arg1", "arg2", "arg3"))
+ verify(fakeCommand).execute(anyObject(), safeEq(listOf("arg1", "arg2", "arg3")))
+ }
+
+ class FakeCommand() : Command {
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ }
+
+ override fun help(pw: PrintWriter) {
+ }
+ }
+}
+
+private const val COMMAND = "test_command"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index d0dfb17111c6..524ead22ee94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -34,6 +34,7 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -64,6 +65,7 @@ public class DynamicChildBindControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mDependency.injectMockDependency(MediaOutputDialogFactory.class);
allowTestableLooperAsMainThread();
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 3e1616c9fa7b..d04d8ee76b99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -21,6 +21,8 @@ import static android.service.notification.NotificationListenerService.REASON_CA
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -346,7 +348,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
setSmartActions(mEntry.getKey(), null);
mEntryManager.updateNotificationRanking(mRankingMap);
- assertNull(mEntry.getSmartActions());
+ assertThat(mEntry.getSmartActions()).isEmpty();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index dfe006dfd4fe..d835123e4cad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -61,6 +62,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -112,7 +115,8 @@ public class NotificationFilterTest extends SysuiTestCase {
mDependency.injectTestDependency(NotificationGroupManagerLegacy.class,
new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
- () -> mock(PeopleNotificationIdentifier.class)));
+ () -> mock(PeopleNotificationIdentifier.class),
+ Optional.of(() -> mock(Bubbles.class))));
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index 79fa43604c38..5898664dea8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -118,7 +118,7 @@ class PeopleHubViewControllerTest : SysuiTestCase() {
val people = viewModel.people.toList()
assertThat(people.size).isEqualTo(1)
assertThat(people[0].name).isEqualTo("name")
- assertThat(people[0].icon).isSameAs(fakePerson.avatar)
+ assertThat(people[0].icon).isSameInstanceAs(fakePerson.avatar)
people[0].onClick()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index edb8776bcb02..b20f95cfad3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -46,7 +46,6 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -78,7 +77,6 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase {
public void setUp() {
allowTestableLooperAsMainThread();
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(BubbleController.class);
when(mGutsManager.openGuts(
any(View.class),
anyInt(),
@@ -86,7 +84,6 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase {
any(NotificationMenuRowPlugin.MenuItem.class)))
.thenReturn(true);
when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
- mDependency.injectMockDependency(BubbleController.class);
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index a2f8c1cb0ad3..6b0a23f2b4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -54,13 +54,13 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import org.junit.Assert;
import org.junit.Before;
@@ -87,6 +87,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Mock private NotifRemoteViewCache mCache;
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
+ @Mock private InflatedSmartReplies mInflatedSmartReplies;
+
+ private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater =
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mInflatedSmartReplies;
@Before
public void setUp() throws Exception {
@@ -103,16 +108,13 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
ExpandableNotificationRow row = helper.createRow(mBuilder.build());
mRow = spy(row);
- final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class);
- final SmartReplyController smartReplyController = mock(SmartReplyController.class);
mNotificationInflater = new NotificationContentInflater(
mCache,
mock(NotificationRemoteInputManager.class),
- () -> smartReplyConstants,
- () -> smartReplyController,
mConversationNotificationProcessor,
mock(MediaFeatureFlag.class),
- mock(Executor.class));
+ mock(Executor.class),
+ mSmartRepliesAndActionsInflater);
}
@Test
@@ -120,13 +122,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
BindParams params = new BindParams();
params.usesIncreasedHeadsUpHeight = true;
Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+ mNotificationInflater.inflateNotificationViews(
+ mRow.getEntry(),
mRow,
params,
true /* inflateSynchronously */,
FLAG_CONTENT_VIEW_ALL,
builder,
- mContext);
+ mContext,
+ mSmartRepliesAndActionsInflater);
verify(builder).createHeadsUpContentView(true);
}
@@ -135,13 +139,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
BindParams params = new BindParams();
params.usesIncreasedHeight = true;
Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+ mNotificationInflater.inflateNotificationViews(
+ mRow.getEntry(),
mRow,
params,
true /* inflateSynchronously */,
FLAG_CONTENT_VIEW_ALL,
builder,
- mContext);
+ mContext,
+ mSmartRepliesAndActionsInflater);
verify(builder).createContentView(true);
}
@@ -366,7 +372,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
}
}
- private class AsyncFailRemoteView extends RemoteViews {
+ private static class AsyncFailRemoteView extends RemoteViews {
Handler mHandler = Handler.createAsync(Looper.getMainLooper());
public AsyncFailRemoteView(String packageName, int layoutId) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index c2091da2347e..d08b2b78d00b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -25,8 +25,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AppOpsManager;
-import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -37,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +50,8 @@ public class NotificationContentViewTest extends SysuiTestCase {
@Before
@UiThreadTest
public void setup() {
+ mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+
mView = new NotificationContentView(mContext, null);
ExpandableNotificationRow row = new ExpandableNotificationRow(mContext, null);
ExpandableNotificationRow mockRow = spy(row);
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 3c5aa1ae9519..9465a3db00ac 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
@@ -76,7 +76,7 @@ import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -97,6 +97,7 @@ import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import javax.inject.Provider;
@@ -136,7 +137,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Mock
private OnUserInteractionCallback mOnUserInteractionCallback;
@Mock
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
@Mock
private LauncherApps mLauncherApps;
@Mock
@@ -161,7 +162,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mTestHandler = new Handler(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);
@@ -255,7 +255,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
assertEquals(mIconDrawable, view.getDrawable());
}
@@ -279,7 +279,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -330,7 +330,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertTrue(textView.getText().toString().contains(group.getName()));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -355,7 +355,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
assertEquals(GONE, textView.getVisibility());
@@ -379,7 +379,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
}
@@ -414,7 +414,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -442,7 +442,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -468,7 +468,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -495,7 +495,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
false,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -520,7 +520,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View view = mNotificationInfo.findViewById(R.id.silence);
assertThat(view.isSelected()).isTrue();
}
@@ -548,7 +548,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -579,7 +579,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -609,7 +609,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -653,7 +653,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mTestableLooper.processAllMessages();
@@ -696,7 +696,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View silence = mNotificationInfo.findViewById(R.id.silence);
@@ -740,7 +740,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -777,7 +777,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -813,7 +813,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -851,7 +851,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -887,7 +887,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -923,7 +923,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -958,7 +958,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
View silence = mNotificationInfo.findViewById(R.id.silence);
silence.performClick();
@@ -992,7 +992,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1017,7 +1017,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mBuilderProvider,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -1052,7 +1052,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
() -> b,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
@@ -1092,7 +1092,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
() -> b,
true,
mTestHandler,
- mTestHandler, null, mBubbleController);
+ mTestHandler, null, Optional.of(mBubbles));
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index aff8ade6f1ae..8a5afe6ce667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -45,6 +45,7 @@ import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
@@ -79,7 +80,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationRowCom
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.time.FakeSystemClock;
@@ -138,6 +139,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
@Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ @Mock private InflatedSmartReplies mInflatedSmartReplies;
private StatusBarNotification mSbn;
private NotificationListenerService.RankingMap mRankingMap;
@@ -151,6 +153,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(SmartReplyController.class);
+ mDependency.injectMockDependency(MediaOutputDialogFactory.class);
mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
@@ -199,11 +202,11 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
NotificationContentInflater binder = new NotificationContentInflater(
cache,
mRemoteInputManager,
- () -> mock(SmartReplyConstants.class),
- () -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
- mBgExecutor);
+ mBgExecutor,
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mInflatedSmartReplies);
mRowContentBindStage = new RowContentBindStage(
binder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index e1668cab3333..bbc1df21237f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -67,7 +67,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -93,6 +93,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Optional;
+
import javax.inject.Provider;
/**
@@ -129,7 +131,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Mock private UserContextProvider mContextTracker;
- @Mock private BubbleController mBubbleController;
+ @Mock private Bubbles mBubbles;
@Mock(answer = Answers.RETURNS_SELF)
private PriorityOnboardingDialogController.Builder mBuilder;
private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
@@ -145,7 +147,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
mDependency.injectTestDependency(
OnUserInteractionCallback.class,
mOnUserInteractionCallback);
- mDependency.injectTestDependency(BubbleController.class, mBubbleController);
mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
@@ -155,7 +156,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
() -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
mINotificationManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mProvider,
- mAssistantFeedbackController, mBubbleController,
+ mAssistantFeedbackController, Optional.of(mBubbles),
new UiEventLoggerFake(), mOnUserInteractionCallback);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mCheckSaveListener, mOnSettingsClickListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b952c056c33d..847e0a474a6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -45,15 +45,15 @@ import android.widget.RemoteViews;
import com.android.systemui.R;
import com.android.systemui.TestableDependency;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.media.MediaFeatureFlag;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -69,10 +69,11 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import org.mockito.ArgumentCaptor;
+import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -114,12 +115,13 @@ public class NotificationTestHelper {
mContext = context;
mTestLooper = testLooper;
dependency.injectMockDependency(NotificationMediaManager.class);
- dependency.injectMockDependency(BubbleController.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
+ dependency.injectMockDependency(MediaOutputDialogFactory.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = new NotificationGroupManagerLegacy(
mStatusBarStateController,
- () -> mock(PeopleNotificationIdentifier.class));
+ () -> mock(PeopleNotificationIdentifier.class),
+ Optional.of(() -> mock(Bubbles.class)));
mGroupExpansionManager = mGroupMembershipManager;
mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
mock(KeyguardBypassController.class), mock(NotificationGroupManagerLegacy.class),
@@ -133,11 +135,11 @@ public class NotificationTestHelper {
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class),
- () -> mock(SmartReplyConstants.class),
- () -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
- mock(Executor.class));
+ mock(Executor.class),
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mock(InflatedSmartReplies.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 57020eb08a7f..83b6d2c088b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,7 +29,6 @@ import android.view.View;
import androidx.test.filters.SmallTest;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -95,7 +94,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
.thenReturn(TEST_AUTO_DISMISS_TIME);
when(mVSManager.isReorderingAllowed()).thenReturn(true);
- mDependency.injectMockDependency(BubbleController.class);
mDependency.injectMockDependency(NotificationShadeWindowController.class);
mDependency.injectMockDependency(ConfigurationController.class);
mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mGroupManager, mVSManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 2ece8be8d332..7d84f86cc7b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -37,7 +37,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -61,6 +61,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.HashMap;
+import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -83,7 +84,6 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(BubbleController.class);
mHeadsUpManager = new HeadsUpManager(mContext) {};
when(mNotificationEntryManager.getPendingNotificationsIterator())
@@ -91,7 +91,8 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase {
mGroupManager = new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
- () -> mock(PeopleNotificationIdentifier.class));
+ () -> mock(PeopleNotificationIdentifier.class),
+ Optional.of(() -> mock(Bubbles.class)));
mDependency.injectTestDependency(NotificationGroupManagerLegacy.class, mGroupManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index 0aa009134440..29e445a13e24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -30,7 +30,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
@@ -45,6 +45,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -60,14 +62,15 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase {
@Before
public void setup() {
- mDependency.injectMockDependency(BubbleController.class);
+ mDependency.injectMockDependency(Bubbles.class);
initializeGroupManager();
}
private void initializeGroupManager() {
mGroupManager = new NotificationGroupManagerLegacy(
mock(StatusBarStateController.class),
- () -> mock(PeopleNotificationIdentifier.class));
+ () -> mock(PeopleNotificationIdentifier.class),
+ Optional.of(() -> mock(Bubbles.class)));
mGroupManager.setHeadsUpManager(mHeadsUpManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index 5222ffff2637..ede5fceb4ebd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -26,7 +26,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -41,6 +41,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -66,7 +68,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase {
StatusBarWindowController mStatusBarWindowController;
private NotificationIconAreaController mController;
@Mock
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
@Mock private DemoModeController mDemoModeController;
@Mock
private NotificationIconContainer mAodIcons;
@@ -83,7 +85,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase {
mNotificationMediaManager,
mListener,
mDozeParameters,
- mBubbleController,
+ Optional.of(mBubbles),
mDemoModeController,
mDarkIconDispatcher,
mStatusBarWindowController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 7ee27c9a8aab..386844317720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -350,7 +350,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
- assertThat(actionList).containsAllIn(
+ assertThat(actionList).containsAtLeastElementsIn(
new AccessibilityNodeInfo.AccessibilityAction[] {
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD,
AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 792637d8479b..f7489b1c164a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -53,7 +53,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -86,6 +86,7 @@ import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
+import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -115,7 +116,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private Handler mHandler;
@Mock
- private BubbleController mBubbleController;
+ private Bubbles mBubbles;
@Mock
private ShadeControllerImpl mShadeController;
@Mock
@@ -192,7 +193,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mStatusBarKeyguardViewManager,
mock(KeyguardManager.class),
mock(IDreamManager.class),
- mBubbleController,
+ Optional.of(mBubbles),
() -> mAssistManager,
mRemoteInputManager,
mock(NotificationGroupManagerLegacy.class),
@@ -279,7 +280,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
+ verify(mBubbles).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
// This is called regardless, and simply short circuits when there is nothing to do.
verify(mShadeController, atLeastOnce()).collapsePanel();
@@ -311,7 +312,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
+ verify(mBubbles).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
verify(mShadeController, atLeastOnce()).collapsePanel();
@@ -341,7 +342,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
+ verify(mBubbles).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
verify(mShadeController, atLeastOnce()).collapsePanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7d8a62607395..d6a7acbcbd78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -24,6 +24,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@ import android.app.StatusBarManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
+import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.fingerprint.FingerprintManager;
@@ -80,10 +83,11 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -93,7 +97,6 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
@@ -147,6 +150,7 @@ import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -219,7 +223,7 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private NetworkController mNetworkController;
@Mock private VibratorHelper mVibratorHelper;
- @Mock private BubbleController mBubbleController;
+ @Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@@ -232,7 +236,6 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private KeyguardLiftController mKeyguardLiftController;
@Mock private VolumeComponent mVolumeComponent;
@Mock private CommandQueue mCommandQueue;
- @Mock private Recents mRecents;
@Mock private Provider<StatusBarComponent.Builder> mStatusBarComponentBuilderProvider;
@Mock private StatusBarComponent.Builder mStatusBarComponentBuilder;
@Mock private StatusBarComponent mStatusBarComponent;
@@ -332,7 +335,7 @@ public class StatusBarTest extends SysuiTestCase {
mShadeController = new ShadeControllerImpl(mCommandQueue,
mStatusBarStateController, mNotificationShadeWindowController,
mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
- () -> mStatusBar, () -> mAssistManager, () -> mBubbleController);
+ () -> mStatusBar, () -> mAssistManager, Optional.of(() -> mBubbles));
mStatusBar = new StatusBar(
mContext,
@@ -374,7 +377,7 @@ public class StatusBarTest extends SysuiTestCase {
wakefulnessLifecycle,
mStatusBarStateController,
mVibratorHelper,
- mBubbleController,
+ Optional.of(mBubbles),
mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
@@ -392,7 +395,6 @@ public class StatusBarTest extends SysuiTestCase {
mDozeScrimController,
mVolumeComponent,
mCommandQueue,
- Optional.of(mRecents),
mStatusBarComponentBuilderProvider,
mPluginManager,
Optional.of(mSplitScreen),
@@ -876,6 +878,19 @@ public class StatusBarTest extends SysuiTestCase {
verify(mDozeServiceHost).setDozeSuppressed(false);
}
+ @Test
+ public void onEmergencyActionLaunchGesture_launchesEmergencyIntent() {
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ StatusBar statusBarSpy = spy(mStatusBar);
+
+ statusBarSpy.onEmergencyActionLaunchGestureDetected();
+
+ verify(statusBarSpy).startActivity(intentCaptor.capture(), eq(true));
+ Intent sentIntent = intentCaptor.getValue();
+ assertEquals(sentIntent.getAction(), EmergencyGesture.ACTION_LAUNCH_EMERGENCY);
+
+ }
+
public static class TestableNotificationInterruptStateProviderImpl extends
NotificationInterruptStateProviderImpl {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index fc7d0ce30687..0e4b053833b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,12 +16,15 @@
package com.android.systemui.statusbar.policy;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
+import android.app.Notification;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -30,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -84,5 +88,25 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
assertTrue("Heads up should live long enough", mLivesPastNormalTime);
assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
}
+
+ @Test
+ public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
+ HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+ ongoingCall.setEntry(new NotificationEntryBuilder()
+ .setSbn(createNewSbn(0,
+ new Notification.Builder(mContext, "")
+ .setCategory(Notification.CATEGORY_CALL)
+ .setOngoing(true)))
+ .build());
+
+ HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
+ activeRemoteInput.setEntry(new NotificationEntryBuilder()
+ .setSbn(createNewNotification(1))
+ .build());
+ activeRemoteInput.remoteInputActive = true;
+
+ assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
+ assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 53d8e5866347..8ee15bb36834 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -67,16 +67,20 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
private static final Intent WHITELISTED_TEST_INTENT =
new Intent("com.android.WHITELISTED_TEST_ACTION");
- @Mock SmartReplyConstants mSmartReplyConstants;
- @Mock Notification mNotification;
- NotificationEntry mEntry;
- @Mock RemoteInput mRemoteInput;
- @Mock RemoteInput mFreeFormRemoteInput;
- @Mock ActivityManagerWrapper mActivityManagerWrapper;
- @Mock PackageManagerWrapper mPackageManagerWrapper;
- @Mock DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
+ @Mock private SmartReplyConstants mSmartReplyConstants;
+ @Mock private Notification mNotification;
+ @Mock private RemoteInput mRemoteInput;
+ @Mock private RemoteInput mFreeFormRemoteInput;
+ @Mock private ActivityManagerWrapper mActivityManagerWrapper;
+ @Mock private PackageManagerWrapper mPackageManagerWrapper;
+ @Mock private DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
+ @Mock private SmartRepliesAndActions mSmartRepliesAndActions;
+ @Mock private SmartReplyInflater mSmartReplyInflater;
+ @Mock private SmartActionInflater mSmartActionInflater;
private Icon mActionIcon;
+ private NotificationEntry mEntry;
+ private SmartRepliesAndActionsInflaterImpl mSmartRepliesInflater;
@Before
@UiThreadTest
@@ -96,6 +100,14 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
+
+ mSmartRepliesInflater = new SmartRepliesAndActionsInflaterImpl(
+ mSmartReplyConstants,
+ mActivityManagerWrapper,
+ mPackageManagerWrapper,
+ mDevicePolicyManagerWrapper,
+ mSmartReplyInflater,
+ mSmartActionInflater);
}
@Test
@@ -107,7 +119,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -123,7 +135,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -135,7 +147,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
setupAppGeneratedReplies(smartReplies);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
@@ -150,7 +162,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
setupAppGeneratedSuggestions(smartReplies, smartActions);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
@@ -169,10 +181,9 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
- assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
- mEntry.getSmartReplies());
+ assertThat(repliesAndActions.smartReplies.choices).isEqualTo(mEntry.getSmartReplies());
assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue();
assertThat(repliesAndActions.smartActions).isNull();
}
@@ -187,7 +198,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -202,8 +213,9 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
modifyRanking(mEntry)
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
+
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions.actions)
@@ -226,7 +238,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices)
.isEqualTo(Arrays.asList(appGenSmartReplies));
@@ -248,7 +260,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartActions).isNull();
assertThat(repliesAndActions.smartReplies).isNull();
@@ -270,7 +282,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
mEntry.getSmartReplies());
@@ -306,7 +318,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
// Only the action for the whitelisted package should be allowed.
assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
@@ -329,7 +341,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
// We don't restrict replies or actions in screen pinning mode.
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
@@ -356,8 +368,10 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
- leftRepliesAndActions, rightRepliesAndActions)).isTrue();
+ assertThat(
+ SmartRepliesAndActionsInflaterKt
+ .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
+ .isTrue();
}
@Test
@@ -378,7 +392,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
}
@@ -400,7 +414,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
index 0c2361a9f6b9..be836d4132f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java
@@ -20,11 +20,13 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Intent;
import android.location.LocationManager;
import android.os.Handler;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -35,6 +37,7 @@ import com.android.systemui.BootCompleteCache;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback;
import org.junit.Before;
@@ -52,10 +55,13 @@ public class LocationControllerImplTest extends SysuiTestCase {
private TestableLooper mTestableLooper;
@Mock private AppOpsController mAppOpsController;
+ @Mock private UserTracker mUserTracker;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
+ when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
mTestableLooper = TestableLooper.get(this);
mLocationController = spy(new LocationControllerImpl(mContext,
@@ -63,7 +69,8 @@ public class LocationControllerImplTest extends SysuiTestCase {
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
mock(BroadcastDispatcher.class),
- mock(BootCompleteCache.class)));
+ mock(BootCompleteCache.class),
+ mUserTracker));
mTestableLooper.processAllMessages();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index e88b514ef238..4fb85ad1bb4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -17,9 +17,15 @@ package com.android.systemui.statusbar.policy;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ShortcutManager;
@@ -130,8 +136,18 @@ public class RemoteInputViewTest extends SysuiTestCase {
private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
throws Exception {
+ /**
+ * RemoteInputView, Icon, and Bubble have the situation need to handle the other user.
+ * SystemUI cross multiple user but this test(com.android.systemui.tests) doesn't cross
+ * multiple user. It needs some of mocking multiple user environment to ensure the
+ * createContextAsUser without throwing IllegalStateException.
+ */
+ Context contextSpy = spy(mContext);
+ doReturn(contextSpy).when(contextSpy).createContextAsUser(any(), anyInt());
+ doReturn(toUser.getIdentifier()).when(contextSpy).getUserId();
+
NotificationTestHelper helper = new NotificationTestHelper(
- mContext,
+ contextSpy,
mDependency,
TestableLooper.get(this));
ExpandableNotificationRow row = helper.createRow(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index f1a6e67edb43..836a81e42193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -70,6 +70,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import kotlin.sequences.Sequence;
+import kotlin.sequences.SequencesKt;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -98,31 +104,32 @@ public class SmartReplyViewTest extends SysuiTestCase {
private int mDoubleLinePaddingHorizontal;
private int mSpacing;
- @Mock private SmartReplyController mLogger;
private NotificationEntry mEntry;
private Notification mNotification;
+
+ private SmartReplyInflaterImpl mSmartReplyInflater;
+ private SmartActionInflaterImpl mSmartActionInflater;
+
@Mock private SmartReplyConstants mConstants;
+ @Mock private ActivityStarter mActivityStarter;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
+ @Mock private SmartReplyController mSmartReplyController;
- @Mock ActivityStarter mActivityStarter;
- @Mock HeadsUpManager mHeadsUpManager;
+ private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mReceiver = new BlockingQueueIntentReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
- action.onDismiss();
- });
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> action.onDismiss());
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationRemoteInputManager.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
- mContainer = new View(mContext, null);
- mView = SmartReplyView.inflate(mContext);
-
// Any number of replies are fine.
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
@@ -130,6 +137,9 @@ public class SmartReplyViewTest extends SysuiTestCase {
// Ensure there's no delay before we can click smart suggestions.
when(mConstants.getOnClickInitDelay()).thenReturn(0L);
+ mContainer = new View(mContext, null);
+ mView = SmartReplyView.inflate(mContext, mConstants);
+
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
R.dimen.smart_reply_button_padding_horizontal_single_line);
@@ -147,6 +157,18 @@ public class SmartReplyViewTest extends SysuiTestCase {
.build();
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
+
+ mSmartReplyInflater = new SmartReplyInflaterImpl(
+ mConstants,
+ mKeyguardDismissUtil,
+ mNotificationRemoteInputManager,
+ mSmartReplyController,
+ mContext);
+ mSmartActionInflater = new SmartActionInflaterImpl(
+ mConstants,
+ mActivityStarter,
+ mSmartReplyController,
+ mHeadsUpManager);
}
@After
@@ -168,7 +190,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {});
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> { });
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
@@ -179,9 +201,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
- actionRef.set(action);
- });
+
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> actionRef.set(action));
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
@@ -202,7 +223,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
public void testSendSmartReply_controllerCalled() {
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
- verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
+ verify(mSmartReplyController).smartReplySent(mEntry, 2, TEST_CHOICES[2],
MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
}
@@ -461,24 +482,28 @@ public class SmartReplyViewTest extends SysuiTestCase {
private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
- useDelayedOnClickListener);
+ List<Button> replyButtons =
+ inflateSmartReplies(
+ choices, false /* fromAssistant */, useDelayedOnClickListener)
+ .collect(Collectors.toList());
mView.addPreInflatedButtons(replyButtons);
}
- private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
- boolean useDelayedOnClickListener) {
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(TEST_ACTION), 0);
+ private SmartReplyView.SmartReplies createSmartReplies(CharSequence[] choices,
+ boolean fromAssistant) {
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0);
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
- SmartReplyView.SmartReplies smartReplies =
- new SmartReplyView.SmartReplies(
- Arrays.asList(choices),
- input,
- pendingIntent,
- fromAssistant);
- return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
- useDelayedOnClickListener);
+ return new SmartReplyView.SmartReplies(
+ Arrays.asList(choices), input, pendingIntent, fromAssistant);
+ }
+
+ private Stream<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
+ boolean useDelayedOnClickListener) {
+ SmartReplyView.SmartReplies smartReplies = createSmartReplies(choices, fromAssistant);
+ return IntStream.range(0, choices.length).mapToObj(idx ->
+ mSmartReplyInflater.inflateReplyButton(
+ mView, mEntry, smartReplies, idx, choices[idx], useDelayedOnClickListener));
}
private Notification.Action createAction(String actionTitle) {
@@ -501,14 +526,20 @@ public class SmartReplyViewTest extends SysuiTestCase {
private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> actions = mView.inflateSmartActions(
- getContext(),
- new SmartReplyView.SmartActions(createActions(actionTitles), false),
- mLogger,
- mEntry,
- mHeadsUpManager,
- useDelayedOnClickListener);
- mView.addPreInflatedButtons(actions);
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(
+ createActions(actionTitles), false);
+
+ Stream<Button> buttons = IntStream.range(0, smartActions.actions.size()).mapToObj(idx ->
+ mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ useDelayedOnClickListener,
+ getContext()));
+
+ mView.addPreInflatedButtons(buttons.collect(Collectors.toList()));
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
@@ -520,16 +551,25 @@ public class SmartReplyViewTest extends SysuiTestCase {
CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
- useDelayedOnClickListener);
- smartSuggestions.addAll(mView.inflateSmartActions(
- getContext(),
- new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
- mLogger,
- mEntry,
- mHeadsUpManager,
- useDelayedOnClickListener));
- mView.addPreInflatedButtons(smartSuggestions);
+ Sequence<Button> inflatedReplies = SequencesKt.asSequence(
+ inflateSmartReplies(choices, fromAssistant, useDelayedOnClickListener)
+ .iterator());
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(
+ createActions(actionTitles), fromAssistant);
+ Sequence<Button> inflatedSmartActions = SequencesKt.asSequence(
+ IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ useDelayedOnClickListener,
+ getContext()))
+ .iterator());
+ mView.addPreInflatedButtons(
+ SequencesKt.toList(SequencesKt.plus(inflatedReplies, inflatedSmartActions)));
+ mView.setSmartRepliesGeneratedByAssistant(fromAssistant);
}
private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) {
@@ -564,10 +604,18 @@ public class SmartReplyViewTest extends SysuiTestCase {
Button previous = null;
SmartReplyView.SmartReplies smartReplies =
new SmartReplyView.SmartReplies(Arrays.asList(choices), null, null, false);
- for (int i = 0; i < choices.length; ++i) {
- Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
- null /* SmartReplyController */, null /* NotificationEntry */,
- true /* useDelayedOnClickListener */);
+
+ Iterable<Button> inflatedReplies = SequencesKt.asIterable(SequencesKt.asSequence(
+ IntStream.range(0, smartReplies.choices.size()).mapToObj(
+ idx -> mSmartReplyInflater.inflateReplyButton(
+ mView,
+ mEntry,
+ smartReplies,
+ idx,
+ smartReplies.choices.get(idx),
+ true /* delayOnClickListener */))
+ .iterator()));
+ for (Button current : inflatedReplies) {
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
@@ -583,9 +631,21 @@ public class SmartReplyViewTest extends SysuiTestCase {
previous = current;
}
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(actions, false);
+ Iterable<Button> inflatedSmartActions = SequencesKt.asIterable(SequencesKt.asSequence(
+ IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ true /* delayOnClickListener */,
+ getContext()))
+ .iterator()));
+
// Add smart actions
- for (int i = 0; i < actions.size(); ++i) {
- Button current = inflateActionButton(actions.get(i));
+ for (Button current : inflatedSmartActions) {
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
@@ -672,8 +732,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
Thread.sleep(delayMs);
mView.getChildAt(2).performClick();
- verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
- any());
+ verify(mActivityStarter, times(1))
+ .startPendingIntentDismissingKeyguard(any(), any(), any());
}
@Test
@@ -684,8 +744,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
- verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
- any());
+ verify(mActivityStarter, times(1))
+ .startPendingIntentDismissingKeyguard(any(), any(), any());
}
@Test
@@ -869,18 +929,26 @@ public class SmartReplyViewTest extends SysuiTestCase {
assertReplyButtonHidden(mView.getChildAt(2));
}
- private Button inflateActionButton(Notification.Action action) {
- return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0,
- new SmartReplyView.SmartActions(Collections.singletonList(action), false),
- mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
- }
-
@Test
public void testInflateActionButton_smartActionIconSingleLineSizeForTwoLineButton() {
// Ensure smart action icons are the same size regardless of the number of text rows in the
// button.
- Button singleLineButton = inflateActionButton(createAction("One line"));
- Button doubleLineButton = inflateActionButton(createAction("Two\nlines"));
+ List<Notification.Action> actions = Stream.of("One line", "Two\nlines")
+ .map(this::createAction)
+ .collect(Collectors.toList());
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(actions, false);
+ List<Button> buttons = IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ true /* delayOnClickListener */,
+ getContext()))
+ .collect(Collectors.toList());
+ Button singleLineButton = buttons.get(0);
+ Button doubleLineButton = buttons.get(1);
Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
assertEquals(singleLineDrawable.getBounds().width(),
@@ -1068,7 +1136,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
- when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+ mView.setMinNumSystemGeneratedReplies(3);
// Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
String[] choices = new String[] {"reply1", "reply2"};
@@ -1082,7 +1150,9 @@ public class SmartReplyViewTest extends SysuiTestCase {
choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ // 395, 168
assertEqualMeasures(expectedView, mView);
+
// smart replies
assertReplyButtonHidden(mView.getChildAt(0));
assertReplyButtonHidden(mView.getChildAt(1));
@@ -1121,7 +1191,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
*/
@Test
public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
- when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+ mView.setMinNumSystemGeneratedReplies(2);
// Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
String[] choices = new String[] {"This is a very long two-line reply."};
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 0a10ab2fbf02..c743fd07c492 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -54,7 +54,11 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.util.IntPair;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -84,6 +88,7 @@ public class ToastUITest extends SysuiTestCase {
private static final String TEXT = "Hello World";
private static final int MESSAGE_RES_ID = R.id.message;
+ private FakeExecutor mFakeDelayableExecutor = new FakeExecutor(new FakeSystemClock());
private Context mContextSpy;
private ToastUI mToastUI;
@Mock private LayoutInflater mLayoutInflater;
@@ -91,6 +96,10 @@ public class ToastUITest extends SysuiTestCase {
@Mock private WindowManager mWindowManager;
@Mock private INotificationManager mNotificationManager;
@Mock private IAccessibilityManager mAccessibilityManager;
+ @Mock private PluginManager mPluginManager;
+ @Mock private DumpManager mDumpManager;
+ @Mock private ToastLogger mToastLogger;
+
@Mock private ITransientNotificationCallback mCallback;
@Captor private ArgumentCaptor<View> mViewCaptor;
@Captor private ArgumentCaptor<ViewGroup.LayoutParams> mParamsCaptor;
@@ -109,8 +118,10 @@ public class ToastUITest extends SysuiTestCase {
mContextSpy = spy(mContext);
doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
+ doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
mToastUI = new ToastUI(mContextSpy, mCommandQueue, mNotificationManager,
- mAccessibilityManager);
+ mAccessibilityManager, new ToastFactory(mPluginManager, mDumpManager),
+ mFakeDelayableExecutor, mToastLogger);
}
@Test
@@ -271,6 +282,29 @@ public class ToastUITest extends SysuiTestCase {
verify(mCallback).onToastHidden();
}
+ @Test
+ public void testShowToast_logs() {
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+
+ verify(mToastLogger).logOnShowToast(UID_1, PACKAGE_NAME_1, TEXT, TOKEN_1.toString());
+ }
+
+ @Test
+ public void testHideToast_logs() {
+ mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG,
+ mCallback);
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+ verify(mToastLogger).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
+ }
+
+ @Test
+ public void testHideToast_error_noLog() {
+ // no toast was shown, so this hide is invalid
+ mToastUI.hideToast(PACKAGE_NAME_1, TOKEN_1);
+ verify(mToastLogger, never()).logOnHideToast(PACKAGE_NAME_1, TOKEN_1.toString());
+ }
+
private View verifyWmAddViewAndAttachToParent() {
ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
verify(mWindowManager).addView(viewCaptor.capture(), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
index 8eecde1f4f7c..31848a67698c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
@@ -5,6 +5,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.wm.shell.common.FloatingContentCoordinator
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index f3cdbf7409c1..c0856892dc44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -21,6 +21,7 @@ import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.KeyguardManager;
@@ -167,6 +168,45 @@ public class VolumeDialogImplTest extends SysuiTestCase {
AccessibilityManager.FLAG_CONTENT_CONTROLS);
}
+ @Test
+ public void testVibrateOnRingerChangedToVibrate() {
+ final State initialSilentState = new State();
+ initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // change ringer to silent
+ mDialog.onStateChangedH(initialSilentState);
+
+ // expected: shouldn't call vibrate yet
+ verify(mController, never()).vibrate(any());
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // expected: vibrate device
+ verify(mController).vibrate(any());
+ }
+
+ @Test
+ public void testNoVibrateOnRingerInitialization() {
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = -1;
+
+ // ringer not initialized yet:
+ mDialog.onStateChangedH(initialUnsetState);
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // shouldn't call vibrate
+ verify(mController, never()).vibrate(any());
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index ef4d1078b8b9..2bc07ed43e29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -16,12 +16,17 @@
package com.android.systemui.wmshell;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.pm.PackageManager;
import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContext;
import androidx.test.runner.AndroidJUnit4;
@@ -31,16 +36,19 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.pip.Pip;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedGestureHandler;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
@@ -54,28 +62,39 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class WMShellTest extends SysuiTestCase {
-
+ InputConsumerController mInputConsumerController;
WMShell mWMShell;
+
@Mock CommandQueue mCommandQueue;
+ @Mock ConfigurationController mConfigurationController;
@Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock ActivityManagerWrapper mActivityManagerWrapper;
+ @Mock TaskStackChangeListeners mTaskStackChangeListeners;
@Mock DisplayImeController mDisplayImeController;
+ @Mock InputConsumerController mMockInputConsumerController;
@Mock NavigationModeController mNavigationModeController;
@Mock ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
+ @Mock PipTouchHandler mPipTouchHandler;
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock ProtoTracer mProtoTracer;
+ @Mock PackageManager mMockPackageManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, mCommandQueue, mKeyguardUpdateMonitor,
- mActivityManagerWrapper, mDisplayImeController, mNavigationModeController,
- mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
- Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer);
+ mInputConsumerController = InputConsumerController.getPipInputConsumer();
+
+ mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController,
+ mInputConsumerController, mKeyguardUpdateMonitor, mTaskStackChangeListeners,
+ mDisplayImeController, mNavigationModeController, mScreenLifecycle, mSysUiState,
+ Optional.of(mPip), Optional.of(mSplitScreen), Optional.of(mOneHanded),
+ mTaskOrganizer, mProtoTracer);
+
+ when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
+
}
@Test
@@ -89,8 +108,27 @@ public class WMShellTest extends SysuiTestCase {
public void initPip_registersCommandQueueCallback() {
mWMShell.initPip(mPip);
- // Once for the shell, once for pip
- verify(mCommandQueue, times(2)).addCallback(any(CommandQueue.Callbacks.class));
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
+ }
+
+ @Test
+ public void nonPipDevice_shouldNotInitPip() {
+ final TestableContext nonPipContext = getNonPipFeatureContext();
+ final WMShell nonPipWMShell = new WMShell(nonPipContext, mCommandQueue,
+ mConfigurationController, mMockInputConsumerController, mKeyguardUpdateMonitor,
+ mTaskStackChangeListeners, mDisplayImeController, mNavigationModeController,
+ mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen),
+ Optional.of(mOneHanded), mTaskOrganizer, mProtoTracer);
+ nonPipWMShell.initPip(mPip);
+
+ verify(mCommandQueue, never()).addCallback(any());
+ verify(mKeyguardUpdateMonitor, never()).registerCallback(any());
+ verify(mConfigurationController, never()).addCallback(any());
+ verify(mSysUiState, never()).addCallback(any());
+ verify(mTaskStackChangeListeners, never()).registerTaskStackListener(any());
+ verify(mMockInputConsumerController, never()).setInputListener(any());
+ verify(mMockInputConsumerController, never()).setRegistrationListener(any());
+ verify(mPip, never()).registerSessionListenerForCurrentUser();
}
@Test
@@ -98,26 +136,31 @@ public class WMShellTest extends SysuiTestCase {
mWMShell.initSplitScreen(mSplitScreen);
verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
- verify(mActivityManagerWrapper).registerTaskStackListener(
+ verify(mTaskStackChangeListeners).registerTaskStackListener(
any(TaskStackChangeListener.class));
}
@Test
public void initOneHanded_registersCallbacks() {
- when(mOneHanded.hasOneHandedFeature()).thenReturn(true);
mWMShell.initOneHanded(mOneHanded);
verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
- // Once for the shell, once for the one handed mode
- verify(mCommandQueue, times(2)).addCallback(any(CommandQueue.Callbacks.class));
+ verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
verify(mNavigationModeController).addListener(
any(NavigationModeController.ModeChangedListener.class));
- verify(mActivityManagerWrapper).registerTaskStackListener(
+ verify(mTaskStackChangeListeners).registerTaskStackListener(
any(TaskStackChangeListener.class));
verify(mOneHanded).registerGestureCallback(any(
OneHandedGestureHandler.OneHandedGestureEventCallback.class));
verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
}
+
+ TestableContext getNonPipFeatureContext() {
+ TestableContext spiedContext = spy(mContext);
+ when(mMockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
+ when(spiedContext.getPackageManager()).thenReturn(mMockPackageManager);
+ return spiedContext;
+ }
}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 915c2f6087b8..613d28b20b8e 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -102,6 +102,7 @@ java_defaults {
],
libs: [
"framework-tethering",
+ "framework-wifi",
],
jarjar_rules: "jarjar-rules.txt",
optimize: {
diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
index f6eb40a842d5..94c871d8a366 100644
--- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
+++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -17,18 +17,63 @@
#include <errno.h>
#include <error.h>
#include <jni.h>
+#include <linux/filter.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/JNIHelpCompat.h>
#include <nativehelper/ScopedUtfChars.h>
#include <net/if.h>
+#include <netinet/ether.h>
+#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <sys/socket.h>
+#include <stdio.h>
#define LOG_TAG "TetheringUtils"
#include <android/log.h>
namespace android {
+static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt);
+static const uint32_t kIPv6PayloadStart = sizeof(ip6_hdr);
+static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
+
+static void android_net_util_setupIcmpFilter(JNIEnv *env, jobject javaFd, uint32_t type) {
+ sock_filter filter_code[] = {
+ // Check header is ICMPv6.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeaderOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
+
+ // Check ICMPv6 type.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, type, 0, 1),
+
+ // Accept or reject.
+ BPF_STMT(BPF_RET | BPF_K, 0xffff),
+ BPF_STMT(BPF_RET | BPF_K, 0)
+ };
+
+ const sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ }
+}
+
+static void android_net_util_setupNaSocket(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT);
+}
+
+static void android_net_util_setupNsSocket(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ android_net_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT);
+}
+
static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
jint ifIndex)
{
@@ -125,7 +170,12 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket },
+ { "setupNaSocket", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_net_util_setupNaSocket },
+ { "setupNsSocket", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_net_util_setupNsSocket },
+ { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V",
+ (void*) android_net_util_setupRaSocket },
};
int register_android_net_util_TetheringUtils(JNIEnv* env) {
diff --git a/packages/Tethering/src/android/net/ip/DadProxy.java b/packages/Tethering/src/android/net/ip/DadProxy.java
new file mode 100644
index 000000000000..e2976b78908c
--- /dev/null
+++ b/packages/Tethering/src/android/net/ip/DadProxy.java
@@ -0,0 +1,54 @@
+/*
+ * 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.net.ip;
+
+import android.net.util.InterfaceParams;
+import android.os.Handler;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Basic Duplicate address detection proxy.
+ *
+ * @hide
+ */
+public class DadProxy {
+ private static final String TAG = DadProxy.class.getSimpleName();
+
+ @VisibleForTesting
+ public static NeighborPacketForwarder naForwarder;
+ public static NeighborPacketForwarder nsForwarder;
+
+ public DadProxy(Handler h, InterfaceParams tetheredIface) {
+ naForwarder = new NeighborPacketForwarder(h, tetheredIface,
+ NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
+ nsForwarder = new NeighborPacketForwarder(h, tetheredIface,
+ NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
+ }
+
+ /** Stop NS/NA Forwarders. */
+ public void stop() {
+ naForwarder.stop();
+ nsForwarder.stop();
+ }
+
+ /** Set upstream iface on both forwarders. */
+ public void setUpstreamIface(InterfaceParams upstreamIface) {
+ naForwarder.setUpstreamIface(upstreamIface);
+ nsForwarder.setUpstreamIface(upstreamIface);
+ }
+}
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 673cbf09d259..52d59fcdc19b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -51,6 +51,7 @@ import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -160,6 +161,15 @@ public class IpServer extends StateMachine {
/** Capture IpServer dependencies, for injection. */
public abstract static class Dependencies {
+ /**
+ * Create a DadProxy instance to be used by IpServer.
+ * To support multiple tethered interfaces concurrently DAD Proxy
+ * needs to be supported per IpServer instead of per upstream.
+ */
+ public DadProxy getDadProxy(Handler handler, InterfaceParams ifParams) {
+ return new DadProxy(handler, ifParams);
+ }
+
/** Create an IpNeighborMonitor to be used by this IpServer */
public IpNeighborMonitor getIpNeighborMonitor(Handler handler, SharedLog log,
IpNeighborMonitor.NeighborEventConsumer consumer) {
@@ -256,6 +266,7 @@ public class IpServer extends StateMachine {
// Advertisements (otherwise, we do not add them to mLinkProperties at all).
private LinkProperties mLastIPv6LinkProperties;
private RouterAdvertisementDaemon mRaDaemon;
+ private DadProxy mDadProxy;
// To be accessed only on the handler thread
private int mDhcpServerStartIndex = 0;
@@ -605,7 +616,7 @@ public class IpServer extends StateMachine {
if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
if (enabled) {
- mIpv4Address = requestIpv4Address();
+ mIpv4Address = requestIpv4Address(true /* useLastAddress */);
}
if (mIpv4Address == null) {
@@ -650,14 +661,14 @@ public class IpServer extends StateMachine {
return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
}
- private LinkAddress requestIpv4Address() {
+ private LinkAddress requestIpv4Address(final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
return new LinkAddress(BLUETOOTH_IFACE_ADDR);
}
- return mPrivateAddressCoordinator.requestDownstreamAddress(this);
+ return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
}
private boolean startIPv6() {
@@ -674,6 +685,13 @@ public class IpServer extends StateMachine {
return false;
}
+ // TODO: use ShimUtils instead of explicitly checking the version here.
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R || "S".equals(Build.VERSION.CODENAME)
+ || "T".equals(Build.VERSION.CODENAME)) {
+ // DAD Proxy starts forwarding packets after IPv6 upstream is present.
+ mDadProxy = mDeps.getDadProxy(getHandler(), mInterfaceParams);
+ }
+
return true;
}
@@ -685,6 +703,11 @@ public class IpServer extends StateMachine {
mRaDaemon.stop();
mRaDaemon = null;
}
+
+ if (mDadProxy != null) {
+ mDadProxy.stop();
+ mDadProxy = null;
+ }
}
// IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
@@ -702,11 +725,16 @@ public class IpServer extends StateMachine {
}
RaParams params = null;
- int upstreamIfindex = 0;
+ String upstreamIface = null;
+ InterfaceParams upstreamIfaceParams = null;
+ int upstreamIfIndex = 0;
if (v6only != null) {
- final String upstreamIface = v6only.getInterfaceName();
-
+ upstreamIface = v6only.getInterfaceName();
+ upstreamIfaceParams = mDeps.getInterfaceParams(upstreamIface);
+ if (upstreamIfaceParams != null) {
+ upstreamIfIndex = upstreamIfaceParams.index;
+ }
params = new RaParams();
params.mtu = v6only.getMtu();
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
@@ -726,15 +754,13 @@ public class IpServer extends StateMachine {
}
}
- upstreamIfindex = mDeps.getIfindex(upstreamIface);
-
// Add upstream index to name mapping for the tether stats usage in the coordinator.
// Although this mapping could be added by both class Tethering and IpServer, adding
// mapping from IpServer guarantees that the mapping is added before the adding
// forwarding rules. That is because there are different state machines in both
// classes. It is hard to guarantee the link property update order between multiple
// state machines.
- mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface);
+ mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface);
}
// If v6only is null, we pass in null to setRaParams(), which handles
@@ -743,8 +769,11 @@ public class IpServer extends StateMachine {
setRaParams(params);
mLastIPv6LinkProperties = v6only;
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfindex, null);
- mLastIPv6UpstreamIfindex = upstreamIfindex;
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null);
+ mLastIPv6UpstreamIfindex = upstreamIfIndex;
+ if (mDadProxy != null) {
+ mDadProxy.setUpstreamIface(upstreamIfaceParams);
+ }
}
private void removeRoutesFromLocalNetwork(@NonNull final List<RouteInfo> toBeRemoved) {
@@ -928,7 +957,7 @@ public class IpServer extends StateMachine {
}
final LinkAddress deprecatedLinkAddress = mIpv4Address;
- mIpv4Address = requestIpv4Address();
+ mIpv4Address = requestIpv4Address(false);
if (mIpv4Address == null) {
mLog.e("Fail to request a new downstream prefix");
return;
diff --git a/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java
new file mode 100644
index 000000000000..73fc833fabf5
--- /dev/null
+++ b/packages/Tethering/src/android/net/ip/NeighborPacketForwarder.java
@@ -0,0 +1,180 @@
+/*
+ * 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.net.ip;
+
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_RAW;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_NONBLOCK;
+import static android.system.OsConstants.SOCK_RAW;
+
+import android.net.util.InterfaceParams;
+import android.net.util.PacketReader;
+import android.net.util.SocketUtils;
+import android.net.util.TetheringUtils;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/**
+ * Basic IPv6 Neighbor Advertisement Forwarder.
+ *
+ * Forward NA packets from upstream iface to tethered iface
+ * and NS packets from tethered iface to upstream iface.
+ *
+ * @hide
+ */
+public class NeighborPacketForwarder extends PacketReader {
+ private final String mTag;
+
+ private FileDescriptor mFd;
+
+ // TODO: get these from NetworkStackConstants.
+ private static final int IPV6_ADDR_LEN = 16;
+ private static final int IPV6_DST_ADDR_OFFSET = 24;
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int ETH_HEADER_LEN = 14;
+
+ private InterfaceParams mListenIfaceParams, mSendIfaceParams;
+
+ private final int mType;
+ public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+ public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
+
+ public NeighborPacketForwarder(Handler h, InterfaceParams tetheredInterface, int type) {
+ super(h);
+ mTag = NeighborPacketForwarder.class.getSimpleName() + "-"
+ + tetheredInterface.name + "-" + type;
+ mType = type;
+
+ if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
+ mSendIfaceParams = tetheredInterface;
+ } else {
+ mListenIfaceParams = tetheredInterface;
+ }
+ }
+
+ /** Set new upstream iface and start/stop based on new params. */
+ public void setUpstreamIface(InterfaceParams upstreamParams) {
+ final InterfaceParams oldUpstreamParams;
+
+ if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
+ oldUpstreamParams = mListenIfaceParams;
+ mListenIfaceParams = upstreamParams;
+ } else {
+ oldUpstreamParams = mSendIfaceParams;
+ mSendIfaceParams = upstreamParams;
+ }
+
+ if (oldUpstreamParams == null && upstreamParams != null) {
+ start();
+ } else if (oldUpstreamParams != null && upstreamParams == null) {
+ stop();
+ } else if (oldUpstreamParams != null && upstreamParams != null
+ && oldUpstreamParams.index != upstreamParams.index) {
+ stop();
+ start();
+ }
+ }
+
+ // TODO: move NetworkStackUtils.closeSocketQuietly to
+ // frameworks/libs/net/common/device/com/android/net/module/util/[someclass].
+ private void closeSocketQuietly(FileDescriptor fd) {
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ignored) {
+ }
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ try {
+ // ICMPv6 packets from modem do not have eth header, so RAW socket cannot be used.
+ // To keep uniformity in both directions PACKET socket can be used.
+ mFd = Os.socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+
+ // TODO: convert setup*Socket to setupICMPv6BpfFilter with filter type?
+ if (mType == ICMPV6_NEIGHBOR_ADVERTISEMENT) {
+ TetheringUtils.setupNaSocket(mFd);
+ } else if (mType == ICMPV6_NEIGHBOR_SOLICITATION) {
+ TetheringUtils.setupNsSocket(mFd);
+ }
+
+ SocketAddress bindAddress = SocketUtils.makePacketSocketAddress(
+ ETH_P_IPV6, mListenIfaceParams.index);
+ Os.bind(mFd, bindAddress);
+ } catch (ErrnoException | SocketException e) {
+ Log.wtf(mTag, "Failed to create socket", e);
+ closeSocketQuietly(mFd);
+ return null;
+ }
+
+ return mFd;
+ }
+
+ private Inet6Address getIpv6DestinationAddress(byte[] recvbuf) {
+ Inet6Address dstAddr;
+ try {
+ dstAddr = (Inet6Address) Inet6Address.getByAddress(Arrays.copyOfRange(recvbuf,
+ IPV6_DST_ADDR_OFFSET, IPV6_DST_ADDR_OFFSET + IPV6_ADDR_LEN));
+ } catch (UnknownHostException | ClassCastException impossible) {
+ throw new AssertionError("16-byte array not valid IPv6 address?");
+ }
+ return dstAddr;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ if (mSendIfaceParams == null) {
+ return;
+ }
+
+ // The BPF filter should already have checked the length of the packet, but...
+ if (length < IPV6_HEADER_LEN) {
+ return;
+ }
+ Inet6Address destv6 = getIpv6DestinationAddress(recvbuf);
+ if (!destv6.isMulticastAddress()) {
+ return;
+ }
+ InetSocketAddress dest = new InetSocketAddress(destv6, 0);
+
+ FileDescriptor fd = null;
+ try {
+ fd = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW);
+ SocketUtils.bindSocketToInterface(fd, mSendIfaceParams.name);
+
+ int ret = Os.sendto(fd, recvbuf, 0, length, 0, dest);
+ } catch (ErrnoException | SocketException e) {
+ Log.e(mTag, "handlePacket error: " + e);
+ } finally {
+ closeSocketQuietly(fd);
+ }
+ }
+}
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 6f017dcb623f..7c0b7cc7515c 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -18,6 +18,7 @@ package android.net.ip;
import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.net.util.TetheringUtils.getAllNodesForScopeId;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.SOCK_RAW;
@@ -44,7 +45,6 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
-import java.net.UnknownHostException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -92,10 +92,6 @@ public class RouterAdvertisementDaemon {
private static final int DAY_IN_SECONDS = 86_400;
- private static final byte[] ALL_NODES = new byte[] {
- (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
- };
-
private final InterfaceParams mInterface;
private final InetSocketAddress mAllNodes;
@@ -240,7 +236,6 @@ public class RouterAdvertisementDaemon {
}
}
-
public RouterAdvertisementDaemon(InterfaceParams ifParams) {
mInterface = ifParams;
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
@@ -363,15 +358,6 @@ public class RouterAdvertisementDaemon {
}
}
- private static Inet6Address getAllNodesForScopeId(int scopeId) {
- try {
- return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
- } catch (UnknownHostException uhe) {
- Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
- return null;
- }
- }
-
private static byte asByte(int value) {
return (byte) value;
}
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index b17b4ba77cfb..53b54f7de05d 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -17,11 +17,15 @@ package android.net.util;
import android.net.TetherStatsParcel;
import android.net.TetheringRequestParcel;
+import android.util.Log;
import androidx.annotation.NonNull;
import java.io.FileDescriptor;
+import java.net.Inet6Address;
import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -30,6 +34,24 @@ import java.util.Objects;
* {@hide}
*/
public class TetheringUtils {
+ public static final byte[] ALL_NODES = new byte[] {
+ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ };
+
+ /**
+ * Configures a socket for receiving and sending ICMPv6 neighbor advertisments.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void setupNaSocket(FileDescriptor fd)
+ throws SocketException;
+
+ /**
+ * Configures a socket for receiving and sending ICMPv6 neighbor solicitations.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void setupNsSocket(FileDescriptor fd)
+ throws SocketException;
+
/**
* The object which records offload Tx/Rx forwarded bytes/packets.
* TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with
@@ -129,4 +151,15 @@ public class TetheringUtils {
&& request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
&& request.showProvisioningUi == otherRequest.showProvisioningUi;
}
+
+ /** Get inet6 address for all nodes given scope ID. */
+ public static Inet6Address getAllNodesForScopeId(int scopeId) {
+ try {
+ return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
+ } catch (UnknownHostException uhe) {
+ Log.wtf("TetheringUtils", "Failed to construct Inet6Address from "
+ + Arrays.toString(ALL_NODES) + " and scopedId " + scopeId);
+ return null;
+ }
+ }
}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 33b9d00e70dc..da5f25b2a596 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -28,6 +28,7 @@ import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
import android.net.netlink.NetlinkSocket;
+import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.SharedLog;
import android.net.util.SocketUtils;
@@ -41,11 +42,12 @@ import android.system.OsConstants;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.NoSuchElementException;
@@ -66,11 +68,12 @@ public class OffloadHardwareInterface {
private static final String NO_IPV4_ADDRESS = "";
private static final String NO_IPV4_GATEWAY = "";
// Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
- private static final int NF_NETLINK_CONNTRACK_NEW = 1;
- private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
- private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
+ public static final int NF_NETLINK_CONNTRACK_NEW = 1;
+ public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
+ public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
// Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h
public static final short NFNL_SUBSYS_CTNETLINK = 1;
+ public static final short IPCTNL_MSG_CT_NEW = 0;
public static final short IPCTNL_MSG_CT_GET = 1;
private final long NETLINK_MESSAGE_TIMEOUT_MS = 500;
@@ -237,7 +240,7 @@ public class OffloadHardwareInterface {
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
(short) (NLM_F_REQUEST | NLM_F_DUMP));
final NativeHandle h2 = mDeps.createConntrackSocket(
@@ -267,16 +270,23 @@ public class OffloadHardwareInterface {
}
@VisibleForTesting
- public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) {
- final int length = StructNlMsgHdr.STRUCT_SIZE;
+ public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
final byte[] msg = new byte[length];
- final StructNlMsgHdr nlh = new StructNlMsgHdr();
final ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlh = new StructNlMsgHdr();
nlh.nlmsg_len = length;
nlh.nlmsg_type = type;
nlh.nlmsg_flags = flags;
- nlh.nlmsg_seq = 1;
+ nlh.nlmsg_seq = 0;
nlh.pack(byteBuffer);
+
+ // Header needs to be added to buffer since a generic netlink request is being sent.
+ final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET);
+ nfh.pack(byteBuffer);
+
try {
NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
NETLINK_MESSAGE_TIMEOUT_MS);
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index fd9e36080c80..0cf14e3f868c 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -15,7 +15,14 @@
*/
package com.android.networkstack.tethering;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.util.PrefixUtils.asIpPrefix;
+
+import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
+import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
+import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
import static java.util.Arrays.asList;
@@ -23,21 +30,21 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.ip.IpServer;
-import android.net.util.PrefixUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import java.net.Inet4Address;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
@@ -56,13 +63,6 @@ import java.util.Set;
public class PrivateAddressCoordinator {
public static final int PREFIX_LENGTH = 24;
- private static final int MAX_UBYTE = 256;
- private static final int BYTE_MASK = 0xff;
- // reserved for bluetooth tethering.
- private static final int BLUETOOTH_RESERVED = 44;
- private static final int WIFI_P2P_RESERVED = 49;
- private static final byte DEFAULT_ID = (byte) 42;
-
// Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
// address may be requested before coordinator get current upstream notification. To ensure
// coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
@@ -70,36 +70,54 @@ public class PrivateAddressCoordinator {
// mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprecatedUpstreams().
private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
private final ArraySet<IpServer> mDownstreams;
- // IANA has reserved the following three blocks of the IP address space for private intranets:
- // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
- // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
- private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
private static final String LEGACY_WIFI_P2P_IFACE_ADDRESS = "192.168.49.1/24";
- private final IpPrefix mTetheringPrefix;
+ private static final String LEGACY_BLUETOOTH_IFACE_ADDRESS = "192.168.44.1/24";
+ private final List<IpPrefix> mTetheringPrefixes;
private final ConnectivityManager mConnectivityMgr;
private final TetheringConfiguration mConfig;
+ // keyed by downstream type(TetheringManager.TETHERING_*).
+ private final SparseArray<LinkAddress> mCachedAddresses;
public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
+ this(context, config, new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"))));
+ }
+
+ public PrivateAddressCoordinator(Context context, TetheringConfiguration config,
+ List<IpPrefix> prefixPools) {
mDownstreams = new ArraySet<>();
mUpstreamPrefixMap = new ArrayMap<>();
- mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
mConnectivityMgr = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
mConfig = config;
+ mCachedAddresses = new SparseArray<>();
+ // Reserved static addresses for bluetooth and wifi p2p.
+ mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
+ mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
+
+ mTetheringPrefixes = prefixPools;
}
/**
* Record a new upstream IpPrefix which may conflict with tethering downstreams.
- * The downstreams will be notified if a conflict is found.
+ * The downstreams will be notified if a conflict is found. When updateUpstreamPrefix is called,
+ * UpstreamNetworkState must have an already populated LinkProperties.
*/
- public void updateUpstreamPrefix(final Network network, final LinkProperties lp) {
- final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
+ public void updateUpstreamPrefix(final UpstreamNetworkState ns) {
+ // Do not support VPN as upstream. Normally, networkCapabilities is not expected to be null,
+ // but just checking to be sure.
+ if (ns.networkCapabilities != null && ns.networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+ removeUpstreamPrefix(ns.network);
+ return;
+ }
+
+ final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(
+ ns.linkProperties.getAllLinkAddresses());
if (ipv4Prefixes.isEmpty()) {
- removeUpstreamPrefix(network);
+ removeUpstreamPrefix(ns.network);
return;
}
- mUpstreamPrefixMap.put(network, ipv4Prefixes);
+ mUpstreamPrefixMap.put(ns.network, ipv4Prefixes);
handleMaybePrefixConflict(ipv4Prefixes);
}
@@ -108,7 +126,7 @@ public class PrivateAddressCoordinator {
for (LinkAddress address : linkAddresses) {
if (!address.isIpv4()) continue;
- list.add(PrefixUtils.asIpPrefix(address));
+ list.add(asIpPrefix(address));
}
return list;
@@ -147,65 +165,165 @@ public class PrivateAddressCoordinator {
mUpstreamPrefixMap.removeAll(toBeRemoved);
}
- private boolean isReservedSubnet(final int subnet) {
- return subnet == BLUETOOTH_RESERVED || subnet == WIFI_P2P_RESERVED;
- }
-
/**
* Pick a random available address and mark its prefix as in use for the provided IpServer,
* returns null if there is no available address.
*/
@Nullable
- public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
+ public LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
if (mConfig.shouldEnableWifiP2pDedicatedIp()
&& ipServer.interfaceType() == TETHERING_WIFI_P2P) {
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
- // Address would be 192.168.[subAddress]/24.
- final byte[] bytes = mTetheringPrefix.getRawAddress();
- final int subAddress = getRandomSubAddr();
- final int subNet = (subAddress >> 8) & BYTE_MASK;
- bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
- for (int i = 0; i < MAX_UBYTE; i++) {
- final int newSubNet = (subNet + i) & BYTE_MASK;
- if (isReservedSubnet(newSubNet)) continue;
-
- bytes[2] = (byte) newSubNet;
- final InetAddress addr;
- try {
- addr = InetAddress.getByAddress(bytes);
- } catch (UnknownHostException e) {
- throw new IllegalStateException("Invalid address, shouldn't happen.", e);
+ final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
+ if (useLastAddress && cachedAddress != null
+ && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
+ return cachedAddress;
+ }
+
+ for (IpPrefix prefixRange : mTetheringPrefixes) {
+ final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
+ if (newAddress != null) {
+ mDownstreams.add(ipServer);
+ mCachedAddresses.put(ipServer.interfaceType(), newAddress);
+ return newAddress;
}
+ }
- final IpPrefix prefix = new IpPrefix(addr, PREFIX_LENGTH);
- // Check whether this prefix is in use.
- if (isDownstreamPrefixInUse(prefix)) continue;
- // Check whether this prefix is conflict with any current upstream network.
- if (isConflictWithUpstream(prefix)) continue;
+ // No available address.
+ return null;
+ }
+
+ private int getPrefixBaseAddress(final IpPrefix prefix) {
+ return inet4AddressToIntHTH((Inet4Address) prefix.getAddress());
+ }
+
+ /**
+ * Check whether input prefix conflict with upstream prefixes or in-use downstream prefixes.
+ * If yes, return one of them.
+ */
+ private IpPrefix getConflictPrefix(final IpPrefix prefix) {
+ final IpPrefix upstream = getConflictWithUpstream(prefix);
+ if (upstream != null) return upstream;
+
+ return getInUseDownstreamPrefix(prefix);
+ }
+
+ // Get the next non-conflict sub prefix. E.g: To get next sub prefix from 10.0.0.0/8, if the
+ // previously selected prefix is 10.20.42.0/24(subPrefix: 0.20.42.0) and the conflicting prefix
+ // is 10.16.0.0/20 (10.16.0.0 ~ 10.16.15.255), then the max address under subPrefix is
+ // 0.16.15.255 and the next subPrefix is 0.16.16.255/24 (0.16.15.255 + 0.0.1.0).
+ // Note: the sub address 0.0.0.255 here is fine to be any value that it will be replaced as
+ // selected random sub address later.
+ private int getNextSubPrefix(final IpPrefix conflictPrefix, final int prefixRangeMask) {
+ final int suffixMask = ~prefixLengthToV4NetmaskIntHTH(conflictPrefix.getPrefixLength());
+ // The largest offset within the prefix assignment block that still conflicts with
+ // conflictPrefix.
+ final int maxConflict =
+ (getPrefixBaseAddress(conflictPrefix) | suffixMask) & ~prefixRangeMask;
+
+ final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
+ // Pick a sub prefix a full prefix (1 << (32 - PREFIX_LENGTH) addresses) greater than
+ // maxConflict. This ensures that the selected prefix never overlaps with conflictPrefix.
+ // There is no need to mask the result with PREFIX_LENGTH bits because this is done by
+ // findAvailablePrefixFromRange when it constructs the prefix.
+ return maxConflict + (1 << (32 - PREFIX_LENGTH));
+ }
+
+ private LinkAddress chooseDownstreamAddress(final IpPrefix prefixRange) {
+ // The netmask of the prefix assignment block (e.g., 0xfff00000 for 172.16.0.0/12).
+ final int prefixRangeMask = prefixLengthToV4NetmaskIntHTH(prefixRange.getPrefixLength());
+
+ // The zero address in the block (e.g., 0xac100000 for 172.16.0.0/12).
+ final int baseAddress = getPrefixBaseAddress(prefixRange);
+
+ // The subnet mask corresponding to PREFIX_LENGTH.
+ final int prefixMask = prefixLengthToV4NetmaskIntHTH(PREFIX_LENGTH);
+
+ // The offset within prefixRange of a randomly-selected prefix of length PREFIX_LENGTH.
+ // This may not be the prefix of the address returned by this method:
+ // - If it is already in use, the method will return an address in another prefix.
+ // - If all prefixes within prefixRange are in use, the method will return null. For
+ // example, for a /24 prefix within 172.26.0.0/12, this will be a multiple of 256 in
+ // [0, 1048576). In other words, a random 32-bit number with mask 0x000fff00.
+ //
+ // prefixRangeMask is required to ensure no wrapping. For example, consider:
+ // - prefixRange 127.0.0.0/8
+ // - randomPrefixStart 127.255.255.0
+ // - A conflicting prefix of 127.255.254.0/23
+ // In this case without prefixRangeMask, getNextSubPrefix would return 128.0.0.0, which
+ // means the "start < end" check in findAvailablePrefixFromRange would not reject the prefix
+ // because Java doesn't have unsigned integers, so 128.0.0.0 = 0x80000000 = -2147483648
+ // is less than 127.0.0.0 = 0x7f000000 = 2130706432.
+ //
+ // Additionally, it makes debug output easier to read by making the numbers smaller.
+ final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask;
+
+ // A random offset within the prefix. Used to determine the local address once the prefix
+ // is selected. It does not result in an IPv4 address ending in .0, .1, or .255
+ // For a PREFIX_LENGTH of 255, this is a number between 2 and 254.
+ final int subAddress = getSanitizedSubAddr(~prefixMask);
+
+ // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block,
+ // such that the prefix does not conflict with any upstream.
+ IpPrefix downstreamPrefix = findAvailablePrefixFromRange(
+ randomPrefixStart, (~prefixRangeMask) + 1, baseAddress, prefixRangeMask);
+ if (downstreamPrefix != null) return getLinkAddress(downstreamPrefix, subAddress);
+
+ // If that failed, do the same, but between 0 and randomPrefixStart.
+ downstreamPrefix = findAvailablePrefixFromRange(
+ 0, randomPrefixStart, baseAddress, prefixRangeMask);
+
+ return getLinkAddress(downstreamPrefix, subAddress);
+ }
- mDownstreams.add(ipServer);
- return new LinkAddress(addr, PREFIX_LENGTH);
+ private LinkAddress getLinkAddress(final IpPrefix prefix, final int subAddress) {
+ if (prefix == null) return null;
+
+ final InetAddress address = intToInet4AddressHTH(getPrefixBaseAddress(prefix) | subAddress);
+ return new LinkAddress(address, PREFIX_LENGTH);
+ }
+
+ private IpPrefix findAvailablePrefixFromRange(final int start, final int end,
+ final int baseAddress, final int prefixRangeMask) {
+ int newSubPrefix = start;
+ while (newSubPrefix < end) {
+ final InetAddress address = intToInet4AddressHTH(baseAddress | newSubPrefix);
+ final IpPrefix prefix = new IpPrefix(address, PREFIX_LENGTH);
+
+ final IpPrefix conflictPrefix = getConflictPrefix(prefix);
+
+ if (conflictPrefix == null) return prefix;
+
+ newSubPrefix = getNextSubPrefix(conflictPrefix, prefixRangeMask);
}
- // No available address.
return null;
}
- /** Get random sub address value. Return value is in 0 ~ 0xffff. */
+ /** Get random int which could be used to generate random address. */
@VisibleForTesting
- public int getRandomSubAddr() {
- return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
+ public int getRandomInt() {
+ return (new Random()).nextInt();
}
- private byte getSanitizedAddressSuffix(final int source, byte... excluded) {
- final byte subId = (byte) (source & BYTE_MASK);
- for (byte value : excluded) {
- if (subId == value) return DEFAULT_ID;
+ /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */
+ private int getSanitizedSubAddr(final int subAddrMask) {
+ final int randomSubAddr = getRandomInt() & subAddrMask;
+ // If prefix length > 30, the selecting speace would be less than 4 which may be hard to
+ // avoid 3 consecutive address.
+ if (PREFIX_LENGTH > 30) return randomSubAddr;
+
+ // TODO: maybe it is not necessary to avoid .0, .1 and .255 address because tethering
+ // address would not be conflicted. This code only works because PREFIX_LENGTH is not longer
+ // than 24
+ final int candidate = randomSubAddr & 0xff;
+ if (candidate == 0 || candidate == 1 || candidate == 255) {
+ return (randomSubAddr & 0xfffffffc) + 2;
}
- return subId;
+ return randomSubAddr;
}
/** Release downstream record for IpServer. */
@@ -218,14 +336,18 @@ public class PrivateAddressCoordinator {
mUpstreamPrefixMap.clear();
}
- private boolean isConflictWithUpstream(final IpPrefix source) {
+ private IpPrefix getConflictWithUpstream(final IpPrefix prefix) {
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i);
- for (IpPrefix target : list) {
- if (isConflictPrefix(source, target)) return true;
+ for (IpPrefix upstream : list) {
+ if (isConflictPrefix(prefix, upstream)) return upstream;
}
}
- return false;
+ return null;
+ }
+
+ private boolean isConflictWithUpstream(final IpPrefix prefix) {
+ return getConflictWithUpstream(prefix) != null;
}
private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
@@ -236,35 +358,60 @@ public class PrivateAddressCoordinator {
return prefix1.contains(prefix2.getAddress());
}
- private boolean isDownstreamPrefixInUse(final IpPrefix source) {
- // This class always generates downstream prefixes with the same prefix length, so
- // prefixes cannot be contained in each other. They can only be equal to each other.
+ // InUse Prefixes are prefixes of mCachedAddresses which are active downstream addresses, last
+ // downstream addresses(reserved for next time) and static addresses(e.g. bluetooth, wifi p2p).
+ private IpPrefix getInUseDownstreamPrefix(final IpPrefix prefix) {
+ for (int i = 0; i < mCachedAddresses.size(); i++) {
+ final IpPrefix downstream = asIpPrefix(mCachedAddresses.valueAt(i));
+ if (isConflictPrefix(prefix, downstream)) return downstream;
+ }
+
+ // IpServer may use manually-defined address (mStaticIpv4ServerAddr) which does not include
+ // in mCachedAddresses.
for (IpServer downstream : mDownstreams) {
- final IpPrefix prefix = getDownstreamPrefix(downstream);
- if (source.equals(prefix)) return true;
+ final IpPrefix target = getDownstreamPrefix(downstream);
+ if (target == null) continue;
+
+ if (isConflictPrefix(prefix, target)) return target;
}
- return false;
+
+ return null;
}
private IpPrefix getDownstreamPrefix(final IpServer downstream) {
final LinkAddress address = downstream.getAddress();
if (address == null) return null;
- return PrefixUtils.asIpPrefix(address);
+ return asIpPrefix(address);
}
void dump(final IndentingPrintWriter pw) {
+ pw.println("mTetheringPrefixes:");
+ pw.increaseIndent();
+ for (IpPrefix prefix : mTetheringPrefixes) {
+ pw.println(prefix);
+ }
+ pw.decreaseIndent();
+
pw.println("mUpstreamPrefixMap:");
pw.increaseIndent();
for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i));
}
pw.decreaseIndent();
+
pw.println("mDownstreams:");
pw.increaseIndent();
for (IpServer ipServer : mDownstreams) {
pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress());
}
pw.decreaseIndent();
+
+ pw.println("mCachedAddresses:");
+ pw.increaseIndent();
+ for (int i = 0; i < mCachedAddresses.size(); i++) {
+ pw.println(mCachedAddresses.keyAt(i) + " - " + mCachedAddresses.valueAt(i));
+ }
+ pw.decreaseIndent();
}
}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index b946cd9cfc8a..58d8cdb6f2f3 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -327,7 +327,7 @@ public class Tethering {
// It is OK for the configuration to be passed to the PrivateAddressCoordinator at
// construction time because the only part of the configuration it uses is
// shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that.
- mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext, mConfig);
+ mPrivateAddressCoordinator = mDeps.getPrivateAddressCoordinator(mContext, mConfig);
// Must be initialized after tethering configuration is loaded because BpfCoordinator
// constructor needs to use the configuration.
@@ -1680,14 +1680,6 @@ public class Tethering {
}
}
- private void addUpstreamPrefixes(final UpstreamNetworkState ns) {
- mPrivateAddressCoordinator.updateUpstreamPrefix(ns.network, ns.linkProperties);
- }
-
- private void removeUpstreamPrefixes(final UpstreamNetworkState ns) {
- mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
- }
-
@VisibleForTesting
void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
@@ -1698,10 +1690,10 @@ public class Tethering {
final UpstreamNetworkState ns = (UpstreamNetworkState) o;
switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
- addUpstreamPrefixes(ns);
+ mPrivateAddressCoordinator.updateUpstreamPrefix(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LOST:
- removeUpstreamPrefixes(ns);
+ mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
break;
}
@@ -2106,7 +2098,7 @@ public class Tethering {
}
private boolean hasCallingPermission(@NonNull String permission) {
- return mContext.checkCallingPermission(permission) == PERMISSION_GRANTED;
+ return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
}
/** Unregister tethering event callback */
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 131a5fbf2abe..45b914178e97 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -156,4 +156,12 @@ public abstract class TetheringDependencies {
public boolean isTetheringDenied() {
return TextUtils.equals(SystemProperties.get("ro.tether.denied"), "true");
}
+
+ /**
+ * Get a reference to PrivateAddressCoordinator to be used by Tethering.
+ */
+ public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
+ TetheringConfiguration cfg) {
+ return new PrivateAddressCoordinator(ctx, cfg);
+ }
}
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index ed69b7d63cb4..02bab9ba353e 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -22,7 +22,6 @@ java_defaults {
static_libs: [
"NetworkStackApiStableLib",
"androidx.test.rules",
- "frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"net-tests-utils",
"testables",
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 64be2d9a5599..d206ea0b4d45 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -553,7 +553,6 @@ public class EthernetTetheringTest {
TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
TestNetworkInterface iface = tnm.createTapInterface();
Log.d(TAG, "Created test interface " + iface.getInterfaceName());
- assertNotNull(NetworkInterface.getByName(iface.getInterfaceName()));
return iface;
}
diff --git a/packages/Tethering/tests/privileged/Android.bp b/packages/Tethering/tests/privileged/Android.bp
index a0fb24603a93..9217345dc2f5 100644
--- a/packages/Tethering/tests/privileged/Android.bp
+++ b/packages/Tethering/tests/privileged/Android.bp
@@ -14,8 +14,22 @@
// limitations under the License.
//
+java_defaults {
+ name: "TetheringPrivilegedTestsJniDefaults",
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ "libtetherutilsjni",
+ ],
+ jni_uses_sdk_apis: true,
+ visibility: ["//visibility:private"],
+}
+
android_test {
name: "TetheringPrivilegedTests",
+ defaults: [
+ "TetheringPrivilegedTestsJniDefaults",
+ ],
srcs: [
"src/**/*.java",
"src/**/*.kt",
@@ -23,8 +37,13 @@ android_test {
certificate: "networkstack",
platform_apis: true,
test_suites: [
- "general-tests",
+ "device-tests",
"mts",
],
+ static_libs: [
+ "androidx.test.rules",
+ "net-tests-utils",
+ "TetheringApiCurrentLib",
+ ],
compile_multilib: "both",
}
diff --git a/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
new file mode 100644
index 000000000000..42a91aa9acd5
--- /dev/null
+++ b/packages/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.net.ip;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.IpUtils.icmpv6Checksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.MacAddress;
+import android.net.util.InterfaceParams;
+import android.net.util.TetheringUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.TapPacketReader;
+import com.android.testutils.TapPacketReaderRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DadProxyTest {
+ private static final int DATA_BUFFER_LEN = 4096;
+ private static final int PACKET_TIMEOUT_MS = 5_000;
+
+ // Start the readers manually on a common handler shared with DadProxy, for simplicity
+ @Rule
+ public final TapPacketReaderRule mUpstreamReader = new TapPacketReaderRule(
+ DATA_BUFFER_LEN, false /* autoStart */);
+ @Rule
+ public final TapPacketReaderRule mTetheredReader = new TapPacketReaderRule(
+ DATA_BUFFER_LEN, false /* autoStart */);
+
+ private InterfaceParams mUpstreamParams, mTetheredParams;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader;
+
+ private static INetd sNetd;
+
+ @BeforeClass
+ public static void setupOnce() {
+ System.loadLibrary("tetherutilsjni");
+
+ final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+ final IBinder netdIBinder =
+ (IBinder) inst.getContext().getSystemService(Context.NETD_SERVICE);
+ sNetd = INetd.Stub.asInterface(netdIBinder);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ setupTapInterfaces();
+
+ // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ DadProxy mProxy = setupProxy();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mUpstreamReader.stop();
+ mTetheredReader.stop();
+
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread.join(PACKET_TIMEOUT_MS);
+ }
+
+ if (mTetheredParams != null) {
+ sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
+ }
+ if (mUpstreamParams != null) {
+ sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
+ }
+ }
+
+ private void setupTapInterfaces() {
+ // Create upstream test iface.
+ mUpstreamReader.start(mHandler);
+ mUpstreamParams = InterfaceParams.getByName(mUpstreamReader.iface.getInterfaceName());
+ assertNotNull(mUpstreamParams);
+ mUpstreamPacketReader = mUpstreamReader.getReader();
+
+ // Create tethered test iface.
+ mTetheredReader.start(mHandler);
+ mTetheredParams = InterfaceParams.getByName(mTetheredReader.getIface().getInterfaceName());
+ assertNotNull(mTetheredParams);
+ mTetheredPacketReader = mTetheredReader.getReader();
+ }
+
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ICMPV6_NA_NS_LEN = 24;
+ private static final int LL_TARGET_OPTION_LEN = 8;
+ private static final int ICMPV6_CHECKSUM_OFFSET = 2;
+ private static final int ETHER_TYPE_IPV6 = 0x86dd;
+
+ private static ByteBuffer createDadPacket(int type) {
+ // Refer to buildArpPacket()
+ int icmpLen = ICMPV6_NA_NS_LEN
+ + (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT
+ ? LL_TARGET_OPTION_LEN : 0);
+ final ByteBuffer buf = ByteBuffer.allocate(icmpLen + IPV6_HEADER_LEN + ETH_HEADER_LEN);
+
+ // Ethernet header.
+ final MacAddress srcMac = MacAddress.fromString("33:33:ff:66:77:88");
+ buf.put(srcMac.toByteArray());
+ final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
+ buf.put(dstMac.toByteArray());
+ buf.putShort((short) ETHER_TYPE_IPV6);
+
+ // IPv6 header
+ byte[] version = {(byte) 0x60, 0x00, 0x00, 0x00};
+ buf.put(version); // Version
+ buf.putShort((byte) icmpLen); // Length
+ buf.put((byte) IPPROTO_ICMPV6); // Next header
+ buf.put((byte) 0xff); // Hop limit
+
+ final byte[] target =
+ InetAddresses.parseNumericAddress("fe80::1122:3344:5566:7788").getAddress();
+ final byte[] src;
+ final byte[] dst;
+ if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION) {
+ src = InetAddresses.parseNumericAddress("::").getAddress();
+ dst = InetAddresses.parseNumericAddress("ff02::1:ff66:7788").getAddress();
+ } else {
+ src = target;
+ dst = TetheringUtils.ALL_NODES;
+ }
+ buf.put(src);
+ buf.put(dst);
+
+ // ICMPv6 Header
+ buf.put((byte) type); // Type
+ buf.put((byte) 0x00); // Code
+ buf.putShort((short) 0); // Checksum
+ buf.putInt(0); // Reserved
+ buf.put(target);
+
+ if (type == NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT) {
+ //NA packet has LL target address
+ //ICMPv6 Option
+ buf.put((byte) 0x02); // Type
+ buf.put((byte) 0x01); // Length
+ byte[] ll_target = MacAddress.fromString("01:02:03:04:05:06").toByteArray();
+ buf.put(ll_target);
+ }
+
+ // Populate checksum field
+ final int transportOffset = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ final short checksum = icmpv6Checksum(buf, ETH_HEADER_LEN, transportOffset, icmpLen);
+ buf.putShort(transportOffset + ICMPV6_CHECKSUM_OFFSET, checksum);
+
+ buf.flip();
+ return buf;
+ }
+
+ private DadProxy setupProxy() throws Exception {
+ DadProxy proxy = new DadProxy(mHandler, mTetheredParams);
+ mHandler.post(() -> proxy.setUpstreamIface(mUpstreamParams));
+
+ // Upstream iface is added to local network to simplify test case.
+ // Otherwise the test needs to create and destroy a network for the upstream iface.
+ sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
+ sNetd.networkAddInterface(INetd.LOCAL_NET_ID, mTetheredParams.name);
+
+ return proxy;
+ }
+
+ // TODO: change to assert.
+ private boolean waitForPacket(ByteBuffer packet, TapPacketReader reader) {
+ byte[] p;
+
+ while ((p = reader.popPacket(PACKET_TIMEOUT_MS)) != null) {
+ final ByteBuffer buffer = ByteBuffer.wrap(p);
+
+ if (buffer.compareTo(packet) == 0) return true;
+ }
+ return false;
+ }
+
+ private void updateDstMac(ByteBuffer buf, MacAddress mac) {
+ buf.put(mac.toByteArray());
+ buf.rewind();
+ }
+ private void updateSrcMac(ByteBuffer buf, InterfaceParams ifaceParams) {
+ buf.position(ETHER_SRC_ADDR_OFFSET);
+ buf.put(ifaceParams.macAddr.toByteArray());
+ buf.rewind();
+ }
+
+ @Test
+ public void testNaForwardingFromUpstreamToTether() throws Exception {
+ ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
+
+ mUpstreamPacketReader.sendResponse(na);
+ updateDstMac(na, MacAddress.fromString("33:33:00:00:00:01"));
+ updateSrcMac(na, mTetheredParams);
+ assertTrue(waitForPacket(na, mTetheredPacketReader));
+ }
+
+ @Test
+ // TODO: remove test once DAD works in both directions.
+ public void testNaForwardingFromTetherToUpstream() throws Exception {
+ ByteBuffer na = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_ADVERTISEMENT);
+
+ mTetheredPacketReader.sendResponse(na);
+ updateDstMac(na, MacAddress.fromString("33:33:00:00:00:01"));
+ updateSrcMac(na, mTetheredParams);
+ assertFalse(waitForPacket(na, mUpstreamPacketReader));
+ }
+
+ @Test
+ public void testNsForwardingFromTetherToUpstream() throws Exception {
+ ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
+
+ mTetheredPacketReader.sendResponse(ns);
+ updateSrcMac(ns, mUpstreamParams);
+ assertTrue(waitForPacket(ns, mUpstreamPacketReader));
+ }
+
+ @Test
+ // TODO: remove test once DAD works in both directions.
+ public void testNsForwardingFromUpstreamToTether() throws Exception {
+ ByteBuffer ns = createDadPacket(NeighborPacketForwarder.ICMPV6_NEIGHBOR_SOLICITATION);
+
+ mUpstreamPacketReader.sendResponse(ns);
+ updateSrcMac(ns, mUpstreamParams);
+ assertFalse(waitForPacket(ns, mTetheredPacketReader));
+ }
+}
diff --git a/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
new file mode 100644
index 000000000000..57c28fc67cc3
--- /dev/null
+++ b/packages/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.StructNlMsgHdr;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.NativeHandle;
+import android.system.Os;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackSocketTest {
+ private static final long TIMEOUT = 500;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private final SharedLog mLog = new SharedLog("privileged-test");
+
+ private OffloadHardwareInterface mOffloadHw;
+ private OffloadHardwareInterface.Dependencies mDeps;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
+ if (Looper.myLooper() == null) Looper.prepare();
+
+ mDeps = new OffloadHardwareInterface.Dependencies(mLog);
+ mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
+ }
+
+ @Test
+ public void testIpv4ConntrackSocket() throws Exception {
+ // Set up server and connect.
+ final InetSocketAddress anyAddress = new InetSocketAddress(
+ InetAddress.getByName("127.0.0.1"), 0);
+ final ServerSocket serverSocket = new ServerSocket();
+ serverSocket.bind(anyAddress);
+ final SocketAddress theAddress = serverSocket.getLocalSocketAddress();
+
+ // Make a connection to the server.
+ final Socket socket = new Socket();
+ socket.connect(theAddress);
+ final Socket acceptedSocket = serverSocket.accept();
+
+ final NativeHandle handle = mDeps.createConntrackSocket(
+ NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+ mOffloadHw.sendIpv4NfGenMsg(handle,
+ (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ (short) (NLM_F_REQUEST | NLM_F_DUMP));
+
+ boolean foundConntrackEntry = false;
+ ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE);
+ buffer.order(ByteOrder.nativeOrder());
+
+ try {
+ while (Os.read(handle.getFileDescriptor(), buffer) > 0) {
+ buffer.flip();
+
+ // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr
+ // so we can confirm that the conntrack added is for the TCP connection above.
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer);
+ assertNotNull(nlmsghdr);
+
+ // As long as 1 conntrack entry is found test case will pass, even if it's not
+ // the from the TCP connection above.
+ if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) {
+ foundConntrackEntry = true;
+ break;
+ }
+ }
+ } finally {
+ socket.close();
+ serverSocket.close();
+ }
+ assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message",
+ foundConntrackEntry);
+ }
+}
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index ec2d2b02004e..7ed89632a861 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -9,3 +9,8 @@ rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.
rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
+
+# TODO: either stop using frameworks-base-testutils or remove the unit test classes it contains.
+# TestableLooper from "testables" can be used instead of TestLooper from frameworks-base-testutils.
+zap android.os.test.TestLooperTest*
+zap com.android.test.filters.SelectTestTests* \ No newline at end of file
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 3b72b5b47191..2eb75895ac3e 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -47,6 +47,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -86,6 +87,7 @@ import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
+import android.os.Build;
import android.os.Handler;
import android.os.RemoteException;
import android.os.test.TestLooper;
@@ -100,8 +102,12 @@ import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -120,6 +126,9 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IpServerTest {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final String IFACE_NAME = "testnet1";
private static final String UPSTREAM_IFACE = "upstream0";
private static final String UPSTREAM_IFACE2 = "upstream1";
@@ -132,6 +141,11 @@ public class IpServerTest {
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
+ private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams(
+ UPSTREAM_IFACE, UPSTREAM_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
+ private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams(
+ UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.ALL_ZEROS_ADDRESS,
+ 1500 /* defaultMtu */);
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
@@ -142,6 +156,7 @@ public class IpServerTest {
@Mock private IpServer.Callback mCallback;
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
+ @Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRaDaemon;
@Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IpServer.Dependencies mDependencies;
@@ -165,8 +180,11 @@ public class IpServerTest {
private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
boolean usingBpfOffload) throws Exception {
+ when(mDependencies.getDadProxy(any(), any())).thenReturn(mDadProxy);
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+ when(mDependencies.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS);
+ when(mDependencies.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2);
when(mDependencies.getIfindex(eq(UPSTREAM_IFACE))).thenReturn(UPSTREAM_IFINDEX);
when(mDependencies.getIfindex(eq(UPSTREAM_IFACE2))).thenReturn(UPSTREAM_IFINDEX2);
@@ -213,7 +231,8 @@ public class IpServerTest {
dispatchTetherConnectionChanged(upstreamIface, lp, 0);
}
reset(mNetd, mCallback, mAddressCoordinator);
- when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+ mTestAddress);
}
private void setUpDhcpServer() throws Exception {
@@ -233,7 +252,8 @@ public class IpServerTest {
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
- when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+ mTestAddress);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
mBpfCoordinator = spy(new BpfCoordinator(
@@ -355,7 +375,7 @@ public class IpServerTest {
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -376,7 +396,7 @@ public class IpServerTest {
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -590,7 +610,7 @@ public class IpServerTest {
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -603,11 +623,12 @@ public class IpServerTest {
// Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
// onNewPrefixRequest callback.
final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
- when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(newAddress);
+ when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
+ newAddress);
eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
mLooper.dispatchAll();
- inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(false));
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
verifyNoMoreInteractions(mCallback);
@@ -1103,4 +1124,78 @@ public class IpServerTest {
}
return true;
}
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void dadProxyUpdates() throws Exception {
+ InOrder inOrder = inOrder(mDadProxy);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS);
+
+ // Add an upstream without IPv6.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(null);
+
+ // Add IPv6 to the upstream.
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS);
+
+ // Change upstream.
+ // New linkproperties is needed, otherwise changing the iface has no impact.
+ LinkProperties lp2 = new LinkProperties();
+ lp2.setInterfaceName(UPSTREAM_IFACE2);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS2);
+
+ // Lose IPv6 on the upstream...
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE2, null, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(null);
+
+ // ... and regain it on a different upstream.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS);
+
+ // Lose upstream.
+ dispatchTetherConnectionChanged(null, null, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(null);
+
+ // Regain upstream.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, 0);
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS);
+
+ // Stop tethering.
+ mIpServer.stop();
+ mLooper.dispatchAll();
+ }
+
+ private void checkDadProxyEnabled(boolean expectEnabled) throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+ InOrder inOrder = inOrder(mDadProxy);
+ // Add IPv6 to the upstream.
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ if (expectEnabled) {
+ inOrder.verify(mDadProxy).setUpstreamIface(UPSTREAM_IFACE_PARAMS);
+ } else {
+ inOrder.verifyNoMoreInteractions();
+ }
+ // Stop tethering.
+ mIpServer.stop();
+ mLooper.dispatchAll();
+ if (expectEnabled) {
+ inOrder.verify(mDadProxy).stop();
+ }
+ else {
+ verify(mDependencies, never()).getDadProxy(any(), any());
+ }
+ }
+ @Test @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testDadProxyUpdates_DisabledUpToR() throws Exception {
+ checkDadProxyEnabled(false);
+ }
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testDadProxyUpdates_EnabledAfterR() throws Exception {
+ checkDadProxyEnabled(true);
+ }
}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index c543fad62dba..38b19dd3da5c 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -17,8 +17,9 @@
package com.android.networkstack.tethering;
import static android.net.util.TetheringUtils.uint16;
-import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -35,14 +36,15 @@ import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.netlink.StructNfGenMsg;
import android.net.netlink.StructNlMsgHdr;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.NativeHandle;
import android.os.test.TestLooper;
import android.system.ErrnoException;
-import android.system.OsConstants;
import android.system.Os;
+import android.system.OsConstants;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -55,8 +57,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
-import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
@RunWith(AndroidJUnit4.class)
@@ -218,7 +220,7 @@ public final class OffloadHardwareInterfaceTest {
}
@Test
- public void testNetlinkMessage() throws Exception {
+ public void testSendIpv4NfGenMsg() throws Exception {
FileDescriptor writeSocket = new FileDescriptor();
FileDescriptor readSocket = new FileDescriptor();
try {
@@ -229,17 +231,25 @@ public final class OffloadHardwareInterfaceTest {
}
when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket);
- mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS);
+ mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS);
+
+ ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen.
+ buffer.order(ByteOrder.nativeOrder());
- ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE);
int read = Os.read(readSocket, buffer);
+ final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+ assertEquals(expectedLen, read);
buffer.flip();
- assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt());
+ assertEquals(expectedLen, buffer.getInt());
assertEquals(TEST_TYPE, buffer.getShort());
assertEquals(TEST_FLAGS, buffer.getShort());
- assertEquals(1 /* seq */, buffer.getInt());
+ assertEquals(0 /* seq */, buffer.getInt());
assertEquals(0 /* pid */, buffer.getInt());
+ assertEquals(AF_INET, buffer.get()); // nfgen_family
+ assertEquals(0 /* error */, buffer.get()); // version
+ assertEquals(0 /* error */, buffer.getShort()); // res_id
+ assertEquals(expectedLen, buffer.position());
}
private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 8e93c2e447b3..da13e341fb54 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -15,10 +15,15 @@
*/
package com.android.networkstack.tethering;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.util.PrefixUtils.asIpPrefix;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -30,14 +35,12 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.ip.IpServer;
-import android.net.util.NetworkConstants;
-import android.net.util.PrefixUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -48,13 +51,13 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class PrivateAddressCoordinatorTest {
- private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
- private static final String TEST_WIFI_IFNAME = "test_wlan0";
+ private static final String TEST_IFNAME = "test0";
@Mock private IpServer mHotspotIpServer;
@Mock private IpServer mUsbIpServer;
@@ -65,11 +68,22 @@ public final class PrivateAddressCoordinatorTest {
@Mock private TetheringConfiguration mConfig;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
- private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+ private final LinkAddress mBluetoothAddress = new LinkAddress("192.168.44.1/24");
private final LinkAddress mLegacyWifiP2pAddress = new LinkAddress("192.168.49.1/24");
private final Network mWifiNetwork = new Network(1);
private final Network mMobileNetwork = new Network(2);
- private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork};
+ private final Network mVpnNetwork = new Network(3);
+ private final Network mMobileNetwork2 = new Network(4);
+ private final Network mMobileNetwork3 = new Network(5);
+ private final Network mMobileNetwork4 = new Network(6);
+ private final Network mMobileNetwork5 = new Network(7);
+ private final Network mMobileNetwork6 = new Network(8);
+ private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork, mVpnNetwork,
+ mMobileNetwork2, mMobileNetwork3, mMobileNetwork4, mMobileNetwork5, mMobileNetwork6};
+ private final ArrayList<IpPrefix> mTetheringPrefixes = new ArrayList<>(Arrays.asList(
+ new IpPrefix("192.168.0.0/16"),
+ new IpPrefix("172.16.0.0/12"),
+ new IpPrefix("10.0.0.0/8")));
private void setUpIpServers() throws Exception {
when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
@@ -86,194 +100,445 @@ public final class PrivateAddressCoordinatorTest {
when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false);
setUpIpServers();
- mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
+ mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig,
+ mTetheringPrefixes));
}
@Test
- public void testDownstreamPrefixRequest() throws Exception {
- LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
- assertNotEquals(hotspotPrefix, mBluetoothPrefix);
+ public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
+ final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
+ final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, false /* useLastAddress */);
+ final IpPrefix hotspotPrefix = asIpPrefix(address);
+ assertNotEquals(hotspotPrefix, bluetoothPrefix);
+ when(mHotspotIpServer.getAddress()).thenReturn(address);
- address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix testDupRequest = PrefixUtils.asIpPrefix(address);
+ final LinkAddress newAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, false /* useLastAddress */);
+ final IpPrefix testDupRequest = asIpPrefix(newAddress);
assertNotEquals(hotspotPrefix, testDupRequest);
- assertNotEquals(mBluetoothPrefix, testDupRequest);
+ assertNotEquals(bluetoothPrefix, testDupRequest);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer);
- final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
- assertNotEquals(usbPrefix, mBluetoothPrefix);
+ final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, false /* useLastAddress */);
+ final IpPrefix usbPrefix = asIpPrefix(usbAddress);
+ assertNotEquals(usbPrefix, bluetoothPrefix);
assertNotEquals(usbPrefix, hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
}
@Test
- public void testRequestDownstreamAddress() throws Exception {
- LinkAddress expectedAddress = new LinkAddress("192.168.43.42/24");
- int fakeSubAddr = 0x2b00;
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+ public void testSanitizedAddress() throws Exception {
+ int fakeSubAddr = 0x2b00; // 43.0.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- assertEquals(actualAddress, expectedAddress);
+ mHotspotIpServer, false /* useLastAddress */);
+ assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- fakeSubAddr = 0x2b01;
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+ fakeSubAddr = 0x2d01; // 45.1.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- assertEquals(actualAddress, expectedAddress);
+ mHotspotIpServer, false /* useLastAddress */);
+ assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- fakeSubAddr = 0x2bff;
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+ fakeSubAddr = 0x2eff; // 46.255.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- assertEquals(actualAddress, expectedAddress);
+ mHotspotIpServer, false /* useLastAddress */);
+ assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- expectedAddress = new LinkAddress("192.168.43.5/24");
- fakeSubAddr = 0x2b05;
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+ fakeSubAddr = 0x2f05; // 47.5.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- assertEquals(actualAddress, expectedAddress);
+ mHotspotIpServer, false /* useLastAddress */);
+ assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
}
- private int getBluetoothSubAddress() {
- final byte[] rawAddress = mBluetoothPrefix.getRawAddress();
- int bluetoothSubNet = rawAddress[2] & 0xff;
- return (bluetoothSubNet << 8) + 0x5;
- }
-
@Test
- public void testReserveBluetoothPrefix() throws Exception {
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(getBluetoothSubAddress());
- LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
- assertNotEquals("Should not get reserved prefix: ", mBluetoothPrefix, hotspotPrefix);
+ public void testReservedPrefix() throws Exception {
+ // - Test bluetooth prefix is reserved.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
+ getSubAddress(mBluetoothAddress.getAddress().getAddress()));
+ final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, false /* useLastAddress */);
+ final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
+ assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+ // - Test previous enabled hotspot prefix(cached prefix) is reserved.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
+ getSubAddress(hotspotAddress.getAddress().getAddress()));
+ final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, false /* useLastAddress */);
+ final IpPrefix usbPrefix = asIpPrefix(usbAddress);
+ assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
+ assertNotEquals(hotspotPrefix, usbPrefix);
+ mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+
+ // - Test wifi p2p prefix is reserved.
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
+ getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
+ final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mEthernetIpServer, false /* useLastAddress */);
+ final IpPrefix etherPrefix = asIpPrefix(etherAddress);
+ assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
+ assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
+ assertNotEquals(hotspotPrefix, etherPrefix);
+ mPrivateAddressCoordinator.releaseDownstream(mEthernetIpServer);
}
@Test
- public void testNoConflictDownstreamPrefix() throws Exception {
- final int fakeHotspotSubAddr = 0x2b05;
- final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
- LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
- assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
- when(mHotspotIpServer.getAddress()).thenReturn(address);
-
- address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer);
- final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
- assertNotEquals(predefinedPrefix, usbPrefix);
+ public void testRequestLastDownstreamAddress() throws Exception {
+ final int fakeHotspotSubAddr = 0x2b05; // 43.5
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
+ final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true /* useLastAddress */);
+ assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
+ when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddress);
+
+ final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, true /* useLastAddress */);
+ assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
- address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer);
- final IpPrefix allowUseFreePrefix = PrefixUtils.asIpPrefix(address);
- assertEquals("Fail to reselect available prefix: ", predefinedPrefix, allowUseFreePrefix);
- }
- private LinkProperties buildUpstreamLinkProperties(boolean withIPv4, boolean withIPv6,
- boolean isMobile) {
- final String testIface;
- final String testIpv4Address;
- if (isMobile) {
- testIface = TEST_MOBILE_IFNAME;
- testIpv4Address = "10.0.0.1";
- } else {
- testIface = TEST_WIFI_IFNAME;
- testIpv4Address = "192.168.43.5";
- }
+ final int newFakeSubAddr = 0x3c05;
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
+
+ final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true /* useLastAddress */);
+ assertEquals(hotspotAddress, newHotspotAddress);
+ final LinkAddress newUsbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, true /* useLastAddress */);
+ assertEquals(usbAddress, newUsbAddress);
+
+ // BUG: the code should detect a conflict, but it doesn't.
+ // Regression introduced in r.android.com/168169687.
+ // Ensure conflict notification works when using cached address.
+ when(mHotspotIpServer.getAddress()).thenReturn(newHotspotAddress);
+ when(mUsbIpServer.getAddress()).thenReturn(usbAddress);
+ final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+ new LinkAddress("192.168.88.23/16"), null,
+ makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+ verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ }
+ private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
+ final LinkAddress v4Addr, final LinkAddress v6Addr, final NetworkCapabilities cap) {
final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(testIface);
+ prop.setInterfaceName(TEST_IFNAME);
+ if (v4Addr != null) prop.addLinkAddress(v4Addr);
- if (withIPv4) {
- prop.addLinkAddress(
- new LinkAddress(InetAddresses.parseNumericAddress(testIpv4Address),
- NetworkConstants.IPV4_ADDR_BITS));
- }
+ if (v6Addr != null) prop.addLinkAddress(v6Addr);
- if (withIPv6) {
- prop.addLinkAddress(
- new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"),
- NetworkConstants.RFC7421_PREFIX_LENGTH));
+ return new UpstreamNetworkState(prop, cap, network);
+ }
+
+ private NetworkCapabilities makeNetworkCapabilities(final int transportType) {
+ final NetworkCapabilities cap = new NetworkCapabilities();
+ cap.addTransportType(transportType);
+ if (transportType == TRANSPORT_VPN) {
+ cap.removeCapability(NET_CAPABILITY_NOT_VPN);
}
- return prop;
+
+ return cap;
}
@Test
public void testNoConflictUpstreamPrefix() throws Exception {
- final int fakeHotspotSubId = 43;
- final int fakeHotspotSubAddr = 0x2b05;
+ final int fakeHotspotSubAddr = 0x2b05; // 43.5
final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
// Force always get subAddress "43.5" for conflict testing.
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
- // 1. Enable hotspot with prefix 192.168.43.0/24
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
+ // - Enable hotspot with prefix 192.168.43.0/24
final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(hotspotAddr);
+ mHotspotIpServer, true /* useLastAddress */);
+ final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr);
- // 2. Update v6 only mobile network, hotspot prefix should not be removed.
- List<String> testConflicts;
- final LinkProperties v6OnlyMobileProp = buildUpstreamLinkProperties(false, true, true);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v6OnlyMobileProp);
+ // - test mobile network with null NetworkCapabilities. Ideally this should not happen
+ // because NetworkCapabilities update should always happen before LinkProperties update
+ // and the UpstreamNetworkState update, just make sure no crash in this case.
+ final UpstreamNetworkState noCapUpstream = buildUpstreamNetworkState(mMobileNetwork,
+ new LinkAddress("10.0.0.8/24"), null, null);
+ mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
+ verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ // - test mobile upstream with no address.
+ final UpstreamNetworkState noAddress = buildUpstreamNetworkState(mMobileNetwork,
+ null, null, makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(noCapUpstream);
+ verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ // - Update v6 only mobile network, hotspot prefix should not be removed.
+ final UpstreamNetworkState v6OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
+ null, new LinkAddress("2001:db8::/64"),
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyMobile);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
mPrivateAddressCoordinator.removeUpstreamPrefix(mMobileNetwork);
- // 3. Update v4 only mobile network, hotspot prefix should not be removed.
- final LinkProperties v4OnlyMobileProp = buildUpstreamLinkProperties(true, false, true);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v4OnlyMobileProp);
+ // - Update v4 only mobile network, hotspot prefix should not be removed.
+ final UpstreamNetworkState v4OnlyMobile = buildUpstreamNetworkState(mMobileNetwork,
+ new LinkAddress("10.0.0.8/24"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyMobile);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // 4. Update v4v6 mobile network, hotspot prefix should not be removed.
- final LinkProperties v4v6MobileProp = buildUpstreamLinkProperties(true, true, true);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v4v6MobileProp);
+ // - Update v4v6 mobile network, hotspot prefix should not be removed.
+ final UpstreamNetworkState v4v6Mobile = buildUpstreamNetworkState(mMobileNetwork,
+ new LinkAddress("10.0.0.8/24"), new LinkAddress("2001:db8::/64"),
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v4v6Mobile);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // 5. Update v6 only wifi network, hotspot prefix should not be removed.
- final LinkProperties v6OnlyWifiProp = buildUpstreamLinkProperties(false, true, false);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v6OnlyWifiProp);
+ // - Update v6 only wifi network, hotspot prefix should not be removed.
+ final UpstreamNetworkState v6OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
+ null, new LinkAddress("2001:db8::/64"), makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v6OnlyWifi);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
- // 6. Update v4 only wifi network, it conflict with hotspot prefix.
- final LinkProperties v4OnlyWifiProp = buildUpstreamLinkProperties(true, false, false);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v4OnlyWifiProp);
+ // - Update vpn network, it conflict with hotspot prefix but VPN networks are ignored.
+ final UpstreamNetworkState v4OnlyVpn = buildUpstreamNetworkState(mVpnNetwork,
+ new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_VPN));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyVpn);
+ verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ // - Update v4 only wifi network, it conflict with hotspot prefix.
+ final UpstreamNetworkState v4OnlyWifi = buildUpstreamNetworkState(mWifiNetwork,
+ new LinkAddress("192.168.43.5/24"), null, makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
reset(mHotspotIpServer);
- // 7. Restart hotspot again and its prefix is different previous.
+ // - Restart hotspot again and its prefix is different previous.
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
final LinkAddress hotspotAddr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix2 = PrefixUtils.asIpPrefix(hotspotAddr2);
+ mHotspotIpServer, true /* useLastAddress */);
+ final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
assertNotEquals(hotspotPrefix, hotspotPrefix2);
when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr2);
- mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v4OnlyWifiProp);
+ mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- // 7. Usb tethering can be enabled and its prefix is different with conflict one.
+ // - Usb tethering can be enabled and its prefix is different with conflict one.
final LinkAddress usbAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer);
- final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(usbAddr);
+ mUsbIpServer, true /* useLastAddress */);
+ final IpPrefix usbPrefix = asIpPrefix(usbAddr);
assertNotEquals(predefinedPrefix, usbPrefix);
assertNotEquals(hotspotPrefix2, usbPrefix);
when(mUsbIpServer.getAddress()).thenReturn(usbAddr);
- // 8. Disable wifi upstream, then wifi's prefix can be selected again.
+ // - Disable wifi upstream, then wifi's prefix can be selected again.
mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
final LinkAddress ethAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mEthernetIpServer);
- final IpPrefix ethPrefix = PrefixUtils.asIpPrefix(ethAddr);
+ mEthernetIpServer, true /* useLastAddress */);
+ final IpPrefix ethPrefix = asIpPrefix(ethAddr);
assertEquals(predefinedPrefix, ethPrefix);
}
+ @Test
+ public void testChooseAvailablePrefix() throws Exception {
+ final int randomAddress = 0x8605; // 134.5
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
+ final LinkAddress addr0 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr0);
+ final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+ new LinkAddress("192.168.134.13/26"), null,
+ makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+
+ // Check whether return address is next prefix of 192.168.134.0/24.
+ final LinkAddress addr1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr1);
+ final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
+ new LinkAddress("192.168.149.16/19"), null,
+ makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream2);
+
+
+ // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
+ final LinkAddress addr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr2);
+ final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
+ new LinkAddress("192.168.129.53/18"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ // Update another conflict upstream which is covered by the previous one (but not the first
+ // one) and verify whether this would affect the result.
+ final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
+ new LinkAddress("192.168.170.7/19"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+
+ // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
+ final LinkAddress addr3 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr3);
+ final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
+ new LinkAddress("192.168.188.133/17"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+
+ // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
+ // 192.168.134/24 ~ 192.168.255.255/24 is not available.
+ final LinkAddress addr4 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr4);
+ final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
+ new LinkAddress("192.168.3.59/21"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+
+ // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
+ final LinkAddress addr5 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr5);
+ final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
+ new LinkAddress("192.168.68.43/21"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+
+ // Update an upstream that does *not* conflict, check whether return the same address
+ // 192.168.5/24.
+ final LinkAddress addr6 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr6);
+ final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
+ new LinkAddress("192.168.10.97/21"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
+
+ // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
+ final LinkAddress addr7 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr7);
+ final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
+ new LinkAddress("192.168.0.0/17"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
+
+ // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
+ final LinkAddress addr8 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
+ when(mHotspotIpServer.getAddress()).thenReturn(addr6);
+ }
+
+ @Test
+ public void testChoosePrefixFromDifferentRanges() throws Exception {
+ final int randomAddress = 0x1f2b2a; // 31.43.42
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
+ final LinkAddress classC1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
+ when(mHotspotIpServer.getAddress()).thenReturn(classC1);
+ final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
+ new LinkAddress("192.168.88.23/17"), null,
+ makeNetworkCapabilities(TRANSPORT_WIFI));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+ // Check whether return address is next address of prefix 192.168.128.0/17.
+ final LinkAddress classC2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
+ when(mHotspotIpServer.getAddress()).thenReturn(classC2);
+ final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
+ new LinkAddress("192.1.2.3/8"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+ // Check whether return address is under prefix 172.16.0.0/12.
+ final LinkAddress classB1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
+ when(mHotspotIpServer.getAddress()).thenReturn(classB1);
+ final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
+ new LinkAddress("172.28.123.100/14"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+
+ // 172.28.0.0 ~ 172.31.255.255 is not available.
+ // Check whether return address is next address of prefix 172.16.0.0/14.
+ final LinkAddress classB2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
+ when(mHotspotIpServer.getAddress()).thenReturn(classB2);
+
+ // Check whether new downstream is next address of address 172.16.0.42/24.
+ final LinkAddress classB3 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
+ when(mUsbIpServer.getAddress()).thenReturn(classB3);
+ final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
+ new LinkAddress("172.16.0.1/24"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream3);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+ verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+
+ // Check whether return address is next address of prefix 172.16.1.42/24.
+ final LinkAddress classB4 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
+ when(mHotspotIpServer.getAddress()).thenReturn(classB4);
+ final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
+ new LinkAddress("172.16.0.1/13"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+ verifyNotifyConflictAndRelease(mUsbIpServer);
+
+ // Check whether return address is next address of prefix 172.16.0.1/13.
+ final LinkAddress classB5 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
+ when(mHotspotIpServer.getAddress()).thenReturn(classB5);
+ // Check whether return address is next address of prefix 172.24.0.42/24.
+ final LinkAddress classB6 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
+ when(mUsbIpServer.getAddress()).thenReturn(classB6);
+ final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
+ new LinkAddress("172.24.0.1/12"), null,
+ makeNetworkCapabilities(TRANSPORT_CELLULAR));
+ mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream5);
+ verifyNotifyConflictAndRelease(mHotspotIpServer);
+ verifyNotifyConflictAndRelease(mUsbIpServer);
+
+ // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
+ final LinkAddress classA1 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mHotspotIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
+ when(mHotspotIpServer.getAddress()).thenReturn(classA1);
+ // Check whether new downstream is next address of address 10.31.43.42/24.
+ final LinkAddress classA2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+ mUsbIpServer, true/* useLastAddress */);
+ assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
+ }
+
+ private void verifyNotifyConflictAndRelease(final IpServer ipServer) throws Exception {
+ verify(ipServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ mPrivateAddressCoordinator.releaseDownstream(ipServer);
+ reset(ipServer);
+ setUpIpServers();
+ }
+
private int getSubAddress(final byte... ipv4Address) {
assertEquals(4, ipv4Address.length);
@@ -283,16 +548,16 @@ public final class PrivateAddressCoordinatorTest {
private void assertReseveredWifiP2pPrefix() throws Exception {
LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer);
- final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
- final IpPrefix legacyWifiP2pPrefix = PrefixUtils.asIpPrefix(mLegacyWifiP2pAddress);
+ mHotspotIpServer, true /* useLastAddress */);
+ final IpPrefix hotspotPrefix = asIpPrefix(address);
+ final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
}
@Test
public void testEnableLegacyWifiP2PAddress() throws Exception {
- when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
// No matter #shouldEnableWifiP2pDedicatedIp() is enabled or not, legacy wifi p2p prefix
// is resevered.
@@ -303,7 +568,7 @@ public final class PrivateAddressCoordinatorTest {
// If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mWifiP2pIpServer);
+ mWifiP2pIpServer, true /* useLastAddress */);
assertEquals(mLegacyWifiP2pAddress, address);
mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 1fe3840b51a8..20e94b256ad1 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -24,6 +24,9 @@ import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
@@ -110,6 +113,7 @@ import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpServer;
+import android.net.ip.DadProxy;
import android.net.ip.IpNeighborMonitor;
import android.net.ip.IpServer;
import android.net.ip.RouterAdvertisementDaemon;
@@ -178,6 +182,7 @@ public class TetheringTest {
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
private static final String TEST_NCM_IFNAME = "test_ncm0";
private static final String TEST_ETH_IFNAME = "test_eth0";
+ private static final String TEST_BT_IFNAME = "test_pan0";
private static final String TETHERING_NAME = "Tethering";
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
@@ -196,6 +201,7 @@ public class TetheringTest {
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
@Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
+ @Mock private DadProxy mDadProxy;
@Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
@Mock private IpNeighborMonitor mIpNeighborMonitor;
@Mock private IDhcpServer mDhcpServer;
@@ -228,6 +234,7 @@ public class TetheringTest {
private TetheringConfiguration mConfig;
private EntitlementManager mEntitleMgr;
private OffloadController mOffloadCtrl;
+ private PrivateAddressCoordinator mPrivateAddressCoordinator;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -280,6 +287,12 @@ public class TetheringTest {
public class MockIpServerDependencies extends IpServer.Dependencies {
@Override
+ public DadProxy getDadProxy(
+ Handler handler, InterfaceParams ifParams) {
+ return mDadProxy;
+ }
+
+ @Override
public RouterAdvertisementDaemon getRouterAdvertisementDaemon(
InterfaceParams ifParams) {
return mRouterAdvertisementDaemon;
@@ -438,6 +451,18 @@ public class TetheringTest {
public boolean isTetheringDenied() {
return false;
}
+
+
+ @Override
+ public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
+ TetheringConfiguration cfg) {
+ final ArrayList<IpPrefix> prefixPool = new ArrayList<>(Arrays.asList(
+ new IpPrefix("192.168.0.0/16"),
+ new IpPrefix("172.16.0.0/12"),
+ new IpPrefix("10.0.0.0/8")));
+ mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(ctx, cfg, prefixPool));
+ return mPrivateAddressCoordinator;
+ }
}
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -832,6 +857,7 @@ public class TetheringTest {
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
+ verify(mDadProxy, never()).setUpstreamIface(notNull());
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -858,6 +884,8 @@ public class TetheringTest {
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
+ // TODO: add interfaceParams to compare in verify.
+ verify(mDadProxy, times(1)).setUpstreamIface(notNull());
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
@@ -874,6 +902,7 @@ public class TetheringTest {
any(), any());
sendIPv6TetherUpdates(upstreamState);
+ verify(mDadProxy, times(1)).setUpstreamIface(notNull());
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
@@ -891,6 +920,7 @@ public class TetheringTest {
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
+ verify(mDadProxy, times(1)).setUpstreamIface(notNull());
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
@@ -1862,27 +1892,36 @@ public class TetheringTest {
sendConfigurationChanged();
}
- private static UpstreamNetworkState buildV4WifiUpstreamState(final String ipv4Address,
- final int prefixLength, final Network network) {
+ private static UpstreamNetworkState buildV4UpstreamState(final LinkAddress address,
+ final Network network, final String iface, final int transportType) {
final LinkProperties prop = new LinkProperties();
- prop.setInterfaceName(TEST_WIFI_IFNAME);
+ prop.setInterfaceName(iface);
- prop.addLinkAddress(
- new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address),
- prefixLength));
+ prop.addLinkAddress(address);
final NetworkCapabilities capabilities = new NetworkCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ .addTransportType(transportType);
return new UpstreamNetworkState(prop, capabilities, network);
}
+ private void updateV4Upstream(final LinkAddress ipv4Address, final Network network,
+ final String iface, final int transportType) {
+ final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface,
+ transportType);
+ mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
+ Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
+ UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
+ 0,
+ upstream);
+ mLooper.dispatchAll();
+ }
+
@Test
public void testHandleIpConflict() throws Exception {
final Network wifiNetwork = new Network(200);
final Network[] allNetworks = { wifiNetwork };
when(mCm.getAllNetworks()).thenReturn(allNetworks);
- UpstreamNetworkState upstreamNetwork = null;
- runUsbTethering(upstreamNetwork);
+ runUsbTethering(null);
final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture());
@@ -1890,13 +1929,10 @@ public class TetheringTest {
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
reset(mNetd, mUsbManager);
- upstreamNetwork = buildV4WifiUpstreamState(ipv4Address, 30, wifiNetwork);
- mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
- Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
- UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
- 0,
- upstreamNetwork);
- mLooper.dispatchAll();
+
+ // Cause a prefix conflict by assigning a /30 out of the downstream's /24 to the upstream.
+ updateV4Upstream(new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address), 30),
+ wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI);
// verify turn off usb tethering
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
mTethering.interfaceRemoved(TEST_USB_IFNAME);
@@ -1908,9 +1944,10 @@ public class TetheringTest {
@Test
public void testNoAddressAvailable() throws Exception {
final Network wifiNetwork = new Network(200);
- final Network[] allNetworks = { wifiNetwork };
+ final Network btNetwork = new Network(201);
+ final Network mobileNetwork = new Network(202);
+ final Network[] allNetworks = { wifiNetwork, btNetwork, mobileNetwork };
when(mCm.getAllNetworks()).thenReturn(allNetworks);
- final String upstreamAddress = "192.168.0.100";
runUsbTethering(null);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -1927,13 +1964,13 @@ public class TetheringTest {
mLooper.dispatchAll();
reset(mUsbManager, mEm);
- final UpstreamNetworkState upstreamNetwork = buildV4WifiUpstreamState(
- upstreamAddress, 16, wifiNetwork);
- mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
- Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
- UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
- 0,
- upstreamNetwork);
+ updateV4Upstream(new LinkAddress("192.168.0.100/16"), wifiNetwork, TEST_WIFI_IFNAME,
+ TRANSPORT_WIFI);
+ updateV4Upstream(new LinkAddress("172.16.0.0/12"), btNetwork, TEST_BT_IFNAME,
+ TRANSPORT_BLUETOOTH);
+ updateV4Upstream(new LinkAddress("10.0.0.0/8"), mobileNetwork, TEST_MOBILE_IFNAME,
+ TRANSPORT_CELLULAR);
+
mLooper.dispatchAll();
// verify turn off usb tethering
verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
index 6842dde36264..d719a97e28f2 100644
--- a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
+++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml
@@ -21,7 +21,6 @@
android:versionName="1.0">
<overlay
android:targetPackage="android"
- android:targetName="IconShapeCustomization"
android:category="android.theme.customization.adaptive_icon_shape"
android:priority="1"/>
diff --git a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
index 2465fe015538..e7eeb30501b5 100644
--- a/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
+++ b/packages/overlays/IconShapePebbleOverlay/res/values/config.xml
@@ -18,7 +18,7 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. -->
- <string name="config_icon_mask" translatable="false">"MM55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string>
+ <string name="config_icon_mask" translatable="false">"M55,0 C25,0 0,25 0,50 0,78 28,100 55,100 85,100 100,85 100,58 100,30 86,0 55,0 Z"</string>
<!-- Flag indicating whether round icons should be parsed from the application manifest. -->
<bool name="config_useRoundIcon">false</bool>
<!-- Corner radius of system dialogs -->
diff --git a/packages/services/PacProcessor/Android.bp b/packages/services/PacProcessor/Android.bp
index 494a8187886d..1fd972ce4b3d 100644
--- a/packages/services/PacProcessor/Android.bp
+++ b/packages/services/PacProcessor/Android.bp
@@ -19,7 +19,6 @@ android_app {
srcs: ["src/**/*.java"],
platform_apis: true,
certificate: "platform",
- jni_libs: ["libjni_pacprocessor"],
}
filegroup {
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
deleted file mode 100644
index d969c69cfaf1..000000000000
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2013 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.
- */
-
-#define LOG_TAG "PacProcessor"
-
-#include <stdlib.h>
-#include <string>
-
-#include <utils/Log.h>
-#include <utils/Mutex.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-
-#include "proxy_resolver_v8_wrapper.h"
-
-namespace android {
-
-ProxyResolverV8Handle* proxyResolver = NULL;
-bool pacSet = false;
-
-std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
- const jchar* str = env->GetStringCritical(jstr, 0);
- std::u16string str16(reinterpret_cast<const char16_t*>(str),
- env->GetStringLength(jstr));
- env->ReleaseStringCritical(jstr, str);
- return str16;
-}
-
-jstring string16ToJstring(JNIEnv* env, std::u16string string) {
- const char16_t* str = string.data();
- size_t len = string.length();
-
- return env->NewString(reinterpret_cast<const jchar*>(str), len);
-}
-
-static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
- jobject) {
- if (proxyResolver == NULL) {
- proxyResolver = ProxyResolverV8Handle_new();
- pacSet = false;
- return JNI_FALSE;
- }
- return JNI_TRUE;
-}
-
-static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
- jobject) {
- if (proxyResolver != NULL) {
- ProxyResolverV8Handle_delete(proxyResolver);
- proxyResolver = NULL;
- return JNI_FALSE;
- }
- return JNI_TRUE;
-}
-
-static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
- jstring script) {
- std::u16string script16 = jstringToString16(env, script);
-
- if (proxyResolver == NULL) {
- ALOGE("V8 Parser not started when setting PAC script");
- return JNI_TRUE;
- }
-
- if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
- ALOGE("Unable to set PAC script");
- return JNI_TRUE;
- }
- pacSet = true;
-
- return JNI_FALSE;
-}
-
-static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
- jstring url, jstring host) {
- std::u16string url16 = jstringToString16(env, url);
- std::u16string host16 = jstringToString16(env, host);
-
- if (proxyResolver == NULL) {
- ALOGE("V8 Parser not initialized when running PAC script");
- return NULL;
- }
-
- if (!pacSet) {
- ALOGW("Attempting to run PAC with no script set");
- return NULL;
- }
-
- std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
- ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
- if (result.get() == NULL) {
- ALOGE("Error Running PAC");
- return NULL;
- }
-
- std::u16string ret(result.get());
- jstring jret = string16ToJstring(env, ret);
-
- return jret;
-}
-
-static const JNINativeMethod gMethods[] = {
- { "createV8ParserNativeLocked", "()Z",
- (void*)com_android_pacprocessor_PacNative_createV8ParserNativeLocked},
- { "destroyV8ParserNativeLocked", "()Z",
- (void*)com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked},
- { "setProxyScriptNativeLocked", "(Ljava/lang/String;)Z",
- (void*)com_android_pacprocessor_PacNative_setProxyScriptNativeLocked},
- { "makeProxyRequestNativeLocked", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
- (void*)com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked},
-};
-
-int register_com_android_pacprocessor_PacNative(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/pacprocessor/PacNative",
- gMethods, NELEM(gMethods));
-}
-
-} /* namespace android */
diff --git a/packages/services/PacProcessor/jni/jni_init.cpp b/packages/services/PacProcessor/jni/jni_init.cpp
deleted file mode 100644
index de844c87ec0b..000000000000
--- a/packages/services/PacProcessor/jni/jni_init.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2013 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.
- */
-
-#define LOG_TAG "PacProcessor"
-
-#include <utils/Log.h>
-#include "jni.h"
-
-namespace android {
- extern int register_com_android_pacprocessor_PacNative(JNIEnv *env);
-}
-
-using namespace android;
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
- JNIEnv *env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- ALOGE("ERROR: GetEnv failed");
- return -1;
- }
-
- register_com_android_pacprocessor_PacNative(env);
-
- return JNI_VERSION_1_6;
-}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/LibpacInterface.java b/packages/services/PacProcessor/src/com/android/pacprocessor/LibpacInterface.java
deleted file mode 100644
index 103ef7811ff0..000000000000
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/LibpacInterface.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 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.pacprocessor;
-
-/**
- * Common interface for both Android's and WebView's implementation of PAC processor.
- *
- * @hide
- */
-interface LibpacInterface {
- default boolean startPacSupport() {
- return true;
- }
-
- default boolean stopPacSupport() {
- return true;
- }
-
- boolean setCurrentProxyScript(String script);
- String makeProxyRequest(String url, String host);
-}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
deleted file mode 100644
index 9c0cfc27bd14..000000000000
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * Copyright (c) 2013, 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.pacprocessor;
-
-import android.util.Log;
-
-/**
- * @hide
- */
-public class PacNative implements LibpacInterface {
- private static final String TAG = "PacProxy";
-
- private static final PacNative sInstance = new PacNative();
-
- private String mCurrentPac;
-
- private boolean mIsActive;
-
- // Only make native calls from inside synchronized blocks.
- private native boolean createV8ParserNativeLocked();
- private native boolean destroyV8ParserNativeLocked();
-
- private native boolean setProxyScriptNativeLocked(String script);
-
- private native String makeProxyRequestNativeLocked(String url, String host);
-
- static {
- System.loadLibrary("jni_pacprocessor");
- }
-
- private PacNative() {
-
- }
-
- public static PacNative getInstance() {
- return sInstance;
- }
-
- @Override
- public synchronized boolean startPacSupport() {
- if (createV8ParserNativeLocked()) {
- Log.e(TAG, "Unable to Create v8 Proxy Parser.");
- return false;
- }
- mIsActive = true;
- return true;
- }
-
- @Override
- public synchronized boolean stopPacSupport() {
- if (mIsActive) {
- if (destroyV8ParserNativeLocked()) {
- Log.e(TAG, "Unable to Destroy v8 Proxy Parser.");
- return false;
- }
- mIsActive = false;
- }
- return true;
- }
-
- @Override
- public synchronized boolean setCurrentProxyScript(String script) {
- if (setProxyScriptNativeLocked(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
- return false;
- }
- return true;
- }
-
- @Override
- public synchronized String makeProxyRequest(String url, String host) {
- String ret = makeProxyRequestNativeLocked(url, host);
- if ((ret == null) || (ret.length() == 0)) {
- Log.e(TAG, "v8 Proxy request failed.");
- ret = null;
- }
- return ret;
- }
-
- public synchronized boolean isActive() {
- return mIsActive;
- }
-}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
index 5a7de9f70b49..46bda06d0e04 100644
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -17,13 +17,14 @@ package com.android.pacprocessor;
import android.app.Service;
import android.content.Intent;
-import android.content.res.Resources;
import android.os.Binder;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
+import android.webkit.PacProcessor;
+import com.android.internal.annotations.GuardedBy;
import com.android.net.IProxyService;
import java.net.MalformedURLException;
@@ -31,24 +32,21 @@ import java.net.URL;
public class PacService extends Service {
private static final String TAG = "PacService";
- private static final boolean sUseWebViewPacProcessor = Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_useWebViewPacProcessor);
- private final LibpacInterface mLibpac = sUseWebViewPacProcessor
- ? PacWebView.getInstance()
- : PacNative.getInstance();
+ private Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final PacProcessor mPacProcessor = PacProcessor.getInstance();
private ProxyServiceStub mStub = new ProxyServiceStub();
@Override
public void onCreate() {
super.onCreate();
- mLibpac.startPacSupport();
}
@Override
public void onDestroy() {
- mLibpac.stopPacSupport();
super.onDestroy();
}
@@ -74,7 +72,10 @@ public class PacService extends Service {
throw new IllegalArgumentException("Invalid host was passed");
}
}
- return mLibpac.makeProxyRequest(url, host);
+
+ synchronized (mLock) {
+ return mPacProcessor.findProxyForUrl(url);
+ }
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid URL was passed");
}
@@ -86,7 +87,11 @@ public class PacService extends Service {
Log.e(TAG, "Only system user is allowed to call setPacFile");
throw new SecurityException();
}
- mLibpac.setCurrentProxyScript(script);
+ synchronized (mLock) {
+ if (!mPacProcessor.setProxyScript(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ }
+ }
}
}
}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
deleted file mode 100644
index 4dd00f1ff01b..000000000000
--- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacWebView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.pacprocessor;
-
-import android.util.Log;
-import android.webkit.PacProcessor;
-
-/**
- * @hide
- */
-public class PacWebView implements LibpacInterface {
- private static final String TAG = "PacWebView";
-
- private static final PacWebView sInstance = new PacWebView();
- private PacProcessor mProcessor = PacProcessor.getInstance();
-
- public static PacWebView getInstance() {
- return sInstance;
- }
-
- @Override
- public synchronized boolean setCurrentProxyScript(String script) {
- if (!mProcessor.setProxyScript(script)) {
- Log.e(TAG, "Unable to parse proxy script.");
- return false;
- }
- return true;
- }
-
- @Override
- public synchronized String makeProxyRequest(String url, String host) {
- return mProcessor.findProxyForUrl(url);
- }
-}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 34d2b73ac2db..15bd4dc66ccc 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -252,6 +252,10 @@ message SystemMessage {
// Package: android
NOTE_ID_WIFI_SIM_REQUIRED = 60;
+ // TODO: remove this notification after feature development is done
+ // Inform the user a foreground service is restricted from BG-launch.
+ NOTE_FOREGROUND_SERVICE_BG_LAUNCH = 61;
+
// Display the Android Debug Protocol status
// Package: android
NOTE_ADB_WIFI_ACTIVE = 62;
diff --git a/services/Android.bp b/services/Android.bp
index ef52c2aff002..33205c0d20fe 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -3,6 +3,13 @@ java_defaults {
plugins: [
"error_prone_android_framework",
],
+ errorprone: {
+ javacflags: [
+ "-Xep:AndroidFrameworkBinderIdentity:ERROR",
+ "-Xep:AndroidFrameworkCompatChange:ERROR",
+ "-Xep:AndroidFrameworkUid:ERROR",
+ ],
+ },
}
filegroup {
@@ -28,6 +35,7 @@ filegroup {
":services.coverage-sources",
":services.devicepolicy-sources",
":services.midi-sources",
+ ":services.musicsearch-sources",
":services.net-sources",
":services.print-sources",
":services.profcollect-sources",
@@ -71,6 +79,7 @@ java_library {
"services.coverage",
"services.devicepolicy",
"services.midi",
+ "services.musicsearch",
"services.net",
"services.people",
"services.print",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 80e9703e0e62..d71b919818ea 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -119,8 +119,8 @@ import com.android.internal.util.IntPair;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
+import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
-import com.android.server.accessibility.magnification.MagnificationTransitionController;
import com.android.server.accessibility.magnification.WindowMagnificationManager;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -219,16 +219,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// Lazily initialized - access through getSystemActionPerfomer()
private SystemActionPerformer mSystemActionPerformer;
- private FullScreenMagnificationController mFullScreenMagnificationController;
-
private InteractionBridge mInteractionBridge;
private AlertDialog mEnableTouchExplorationDialog;
private AccessibilityInputFilter mInputFilter;
- private WindowMagnificationManager mWindowMagnificationMgr;
-
private boolean mHasInputFilter;
private KeyEventDispatcher mKeyEventDispatcher;
@@ -259,7 +255,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private Point mTempPoint = new Point();
private boolean mIsAccessibilityButtonShown;
- private MagnificationTransitionController mMagnificationTransitionController;
+ private MagnificationController mMagnificationController;
private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
@@ -292,7 +288,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
SystemActionPerformer systemActionPerformer,
AccessibilityWindowManager a11yWindowManager,
AccessibilityDisplayListener a11yDisplayListener,
- WindowMagnificationManager windowMagnificationMgr) {
+ MagnificationController magnificationController) {
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
@@ -303,8 +299,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mSystemActionPerformer = systemActionPerformer;
mA11yWindowManager = a11yWindowManager;
mA11yDisplayListener = a11yDisplayListener;
- mWindowMagnificationMgr = windowMagnificationMgr;
- mMagnificationTransitionController = new MagnificationTransitionController(this, mLock);
+ mMagnificationController = magnificationController;
init();
}
@@ -324,7 +319,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
- mMagnificationTransitionController = new MagnificationTransitionController(this, mLock);
+ mMagnificationController = new MagnificationController(this, mLock, mContext);
init();
}
@@ -946,10 +941,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (resolvedUserId != mCurrentUserId) {
return null;
}
- if (mA11yWindowManager.findA11yWindowInfoByIdLocked(windowId) == null) {
+ final AccessibilityWindowInfo accessibilityWindowInfo = mA11yWindowManager
+ .findA11yWindowInfoByIdLocked(windowId);
+ if (accessibilityWindowInfo == null) {
return null;
}
- return mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ // We use AccessibilityWindowInfo#getId instead of windowId. When the windowId comes
+ // from an embedded hierarchy, the system can't find correct window token because
+ // embedded hierarchy doesn't have windowInfo. Calling
+ // AccessibilityWindowManager#findA11yWindowInfoByIdLocked can look for its parent's
+ // windowInfo, so it is safer to use AccessibilityWindowInfo#getId
+ // to get window token to find real window.
+ return mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(userId,
+ accessibilityWindowInfo.getId());
}
}
@@ -1195,9 +1199,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// The user changed.
mCurrentUserId = userId;
- if (mWindowMagnificationMgr != null) {
- mWindowMagnificationMgr.setUserId(mCurrentUserId);
- }
+ mMagnificationController.updateUserIdIfNeeded(mCurrentUserId);
AccessibilityUserState userState = getCurrentUserStateLocked();
readConfigurationForUserStateLocked(userState);
@@ -1554,7 +1556,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (fallBackMagnificationModeSettingsLocked(userState)) {
return;
}
- mMagnificationTransitionController.transitionMagnificationModeLocked(
+ mMagnificationController.transitionMagnificationModeLocked(
Display.DEFAULT_DISPLAY, userState.getMagnificationModeLocked(),
this::onMagnificationTransitionEndedLocked);
}
@@ -2080,9 +2082,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) {
- final long identity = Binder.clearCallingIdentity();
final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
|| userState.isHandlingAccessibilityEventsLocked();
+ final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
@@ -2310,13 +2312,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
- if (mFullScreenMagnificationController != null) {
- mFullScreenMagnificationController.setUserId(userState.mUserId);
- }
-
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()
- && mFullScreenMagnificationController != null) {
- mFullScreenMagnificationController.unregisterAll();
+ && mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ getFullScreenMagnificationController().unregisterAll();
return;
}
@@ -2339,8 +2337,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final int displayId = display.getDisplayId();
if (userHasListeningMagnificationServicesLocked(userState, displayId)) {
getFullScreenMagnificationController().register(displayId);
- } else if (mFullScreenMagnificationController != null) {
- mFullScreenMagnificationController.unregister(displayId);
+ } else if (mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
+ getFullScreenMagnificationController().unregister(displayId);
}
}
}
@@ -2395,8 +2393,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
int numServices = services.size();
for (int i = 0; i < numServices; i++) {
if (services.get(i).isCapturingFingerprintGestures()) {
- final long identity = Binder.clearCallingIdentity();
IFingerprintService service = null;
+ final long identity = Binder.clearCallingIdentity();
try {
service = IFingerprintService.Stub.asInterface(
ServiceManager.getService(Context.FINGERPRINT_SERVICE));
@@ -2990,10 +2988,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
public WindowMagnificationManager getWindowMagnificationMgr() {
synchronized (mLock) {
- if (mWindowMagnificationMgr == null) {
- mWindowMagnificationMgr = new WindowMagnificationManager(mContext, mCurrentUserId);
- }
- return mWindowMagnificationMgr;
+ return mMagnificationController.getWindowMagnificationMgr();
}
}
@@ -3060,12 +3055,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public FullScreenMagnificationController getFullScreenMagnificationController() {
synchronized (mLock) {
- if (mFullScreenMagnificationController == null) {
- mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
- this, mLock);
- mFullScreenMagnificationController.setUserId(mCurrentUserId);
- }
- return mFullScreenMagnificationController;
+ return mMagnificationController.getFullScreenMagnificationController();
}
}
@@ -3307,9 +3297,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
}
- if (mFullScreenMagnificationController != null) {
- mFullScreenMagnificationController.onDisplayRemoved(displayId);
- }
+ mMagnificationController.onDisplayRemoved(displayId);
mA11yWindowManager.stopTrackingWindows(displayId);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 41f32075fb77..d7664312e2e6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -456,7 +456,7 @@ public class AccessibilitySecurityPolicy {
}
private boolean isShellAllowedToRetrieveWindowLocked(int userId, int windowId) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
IBinder windowToken = mAccessibilityWindowManager
.getWindowTokenForUserAndWindowIdLocked(userId, windowId);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index ee03b150b519..ff794691d2b4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -707,12 +707,12 @@ public class AccessibilityWindowManager {
case WindowManager.LayoutParams.TYPE_PHONE:
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
case WindowManager.LayoutParams.TYPE_TOAST:
- case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: {
+ case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+ case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: {
return AccessibilityWindowInfo.TYPE_APPLICATION;
}
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
- case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: {
+ case WindowManager.LayoutParams.TYPE_INPUT_METHOD: {
return AccessibilityWindowInfo.TYPE_INPUT_METHOD;
}
@@ -730,7 +730,8 @@ public class AccessibilityWindowManager {
case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
- case WindowManager.LayoutParams.TYPE_SCREENSHOT: {
+ case WindowManager.LayoutParams.TYPE_SCREENSHOT:
+ case WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY: {
return AccessibilityWindowInfo.TYPE_SYSTEM;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
index 96418aac7ffa..c9ec16edc54e 100644
--- a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
@@ -118,7 +118,7 @@ public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallba
public boolean isFingerprintGestureDetectionAvailable() {
if (!mHardwareSupportsGestures) return false;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return !mFingerprintService.isClientActive();
} catch (RemoteException re) {
@@ -173,7 +173,7 @@ public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallba
@Override
public boolean handleMessage(Message message) {
if (message.what == MSG_REGISTER) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mFingerprintService.addClientActiveCallback(this);
mRegisteredReadOnlyExceptInHandler = true;
@@ -184,7 +184,7 @@ public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallba
}
return false;
} else if (message.what == MSG_UNREGISTER) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mFingerprintService.removeClientActiveCallback(this);
} catch (RemoteException re) {
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 35054991d3ff..eaf269415fdc 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -308,14 +308,15 @@ public class SystemActionPerformer {
private void sendDownAndUpKeyEvents(int keyCode) {
final long token = Binder.clearCallingIdentity();
-
- // Inject down.
- final long downTime = SystemClock.uptimeMillis();
- sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime);
- sendKeyEventIdentityCleared(
- keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis());
-
- Binder.restoreCallingIdentity(token);
+ try {
+ // Inject down.
+ final long downTime = SystemClock.uptimeMillis();
+ sendKeyEventIdentityCleared(keyCode, KeyEvent.ACTION_DOWN, downTime, downTime);
+ sendKeyEventIdentityCleared(
+ keyCode, KeyEvent.ACTION_UP, downTime, SystemClock.uptimeMillis());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void sendKeyEventIdentityCleared(int keyCode, int action, long downTime, long time) {
@@ -329,22 +330,24 @@ public class SystemActionPerformer {
private void expandNotifications() {
final long token = Binder.clearCallingIdentity();
-
- StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
- android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.expandNotificationsPanel();
-
- Binder.restoreCallingIdentity(token);
+ try {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
+ android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.expandNotificationsPanel();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void expandQuickSettings() {
final long token = Binder.clearCallingIdentity();
-
- StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
- android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.expandSettingsPanel();
-
- Binder.restoreCallingIdentity(token);
+ try {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext.getSystemService(
+ android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.expandSettingsPanel();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private boolean openRecents() {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
index a860db389d1e..14af8c63073e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java
@@ -19,6 +19,7 @@ package com.android.server.accessibility.gestures;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
@@ -27,11 +28,13 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
@@ -140,6 +143,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this));
mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
new MultiFingerMultiTapAndHold(
@@ -153,9 +159,17 @@ class GestureManifold implements GestureMatcher.StateChangeListener {
new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this));
mMultiFingerGestures.add(
new MultiFingerMultiTapAndHold(
+ mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD, this));
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
+ mMultiFingerGestures.add(
+ new MultiFingerMultiTapAndHold(
+ mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP_AND_HOLD, this));
+ mMultiFingerGestures.add(
+ new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this));
// Four-finger taps.
mMultiFingerGestures.add(
new MultiFingerMultiTap(mContext, 4, 1, GESTURE_4_FINGER_SINGLE_TAP, this));
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
index e15c4951d2a2..46b46288cf84 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java
@@ -162,6 +162,7 @@ class MultiFingerMultiTap extends GestureMatcher {
// Accept down only before target number of fingers are down
// or the finger count is not more than target.
if ((currentFingerCount > mTargetFingerCount) || mIsTargetFingerCountReached) {
+ mIsTargetFingerCountReached = false;
cancelGesture(event, rawEvent, policyFlags);
return;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index c583dcc2b1be..189a3908d6bf 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -79,6 +79,8 @@ public class FullScreenMagnificationController {
private final ScreenStateObserver mScreenStateObserver;
+ private final MagnificationRequestObserver mMagnificationRequestObserver;
+
private int mUserId;
private final long mMainThreadId;
@@ -479,6 +481,8 @@ public class FullScreenMagnificationController {
sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback);
if (isMagnifying() && (id != INVALID_ID)) {
mIdOfLastServiceToMagnify = id;
+ mMagnificationRequestObserver.onRequestMagnificationSpec(mDisplayId,
+ mIdOfLastServiceToMagnify);
}
return changed;
}
@@ -609,22 +613,27 @@ public class FullScreenMagnificationController {
* FullScreenMagnificationController Constructor
*/
public FullScreenMagnificationController(@NonNull Context context,
- @NonNull AccessibilityManagerService ams, @NonNull Object lock) {
+ @NonNull AccessibilityManagerService ams, @NonNull Object lock,
+ @NonNull MagnificationRequestObserver magnificationRequestObserver) {
this(new ControllerContext(context, ams,
LocalServices.getService(WindowManagerInternal.class),
new Handler(context.getMainLooper()),
- context.getResources().getInteger(R.integer.config_longAnimTime)), lock);
+ context.getResources().getInteger(R.integer.config_longAnimTime)), lock,
+ magnificationRequestObserver);
}
/**
* Constructor for tests
*/
@VisibleForTesting
- public FullScreenMagnificationController(@NonNull ControllerContext ctx, @NonNull Object lock) {
+ public FullScreenMagnificationController(@NonNull ControllerContext ctx,
+ @NonNull Object lock,
+ @NonNull MagnificationRequestObserver magnificationRequestObserver) {
mControllerCtx = ctx;
mLock = lock;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
+ mMagnificationRequestObserver = magnificationRequestObserver;
}
/**
@@ -1487,4 +1496,16 @@ public class FullScreenMagnificationController {
private static MagnificationAnimationCallback transformToStubCallback(boolean animate) {
return animate ? STUB_ANIMATION_CALLBACK : null;
}
+
+ interface MagnificationRequestObserver {
+
+ /**
+ * Called when the {@link MagnificationSpec} is changed with non-default
+ * scale by the service.
+ *
+ * @param displayId the logical display id
+ * @param serviceId the ID of the service requesting the change
+ */
+ void onRequestMagnificationSpec(int displayId, int serviceId);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index af4b34f9613a..b3fc07a8ab9c 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationTransitionController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -29,21 +30,26 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilityManagerService;
/**
- * Handles magnification mode transition.
+ * Handles all magnification controllers initialization, generic interactions
+ * and magnification mode transition.
*/
-public class MagnificationTransitionController {
+public class MagnificationController {
private static final boolean DEBUG = false;
private static final String TAG = "MagnificationController";
private final AccessibilityManagerService mAms;
private final PointF mTempPoint = new PointF();
private final Object mLock;
+ private final Context mContext;
private final SparseArray<DisableMagnificationCallback>
mMagnificationEndRunnableSparseArray = new SparseArray();
+ private FullScreenMagnificationController mFullScreenMagnificationController;
+ private WindowMagnificationManager mWindowMagnificationMgr;
/**
* A callback to inform the magnification transition result.
@@ -56,9 +62,20 @@ public class MagnificationTransitionController {
void onResult(boolean success);
}
- public MagnificationTransitionController(AccessibilityManagerService ams, Object lock) {
+ public MagnificationController(AccessibilityManagerService ams, Object lock,
+ Context context) {
mAms = ams;
mLock = lock;
+ mContext = context;
+ }
+
+ @VisibleForTesting
+ public MagnificationController(AccessibilityManagerService ams, Object lock,
+ Context context, FullScreenMagnificationController fullScreenMagnificationController,
+ WindowMagnificationManager windowMagnificationManager) {
+ this(ams, lock, context);
+ mFullScreenMagnificationController = fullScreenMagnificationController;
+ mWindowMagnificationMgr = windowMagnificationManager;
}
/**
@@ -97,7 +114,7 @@ public class MagnificationTransitionController {
}
final FullScreenMagnificationController screenMagnificationController =
getFullScreenMagnificationController();
- final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationManager();
+ final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr();
final float scale = windowMagnificationMgr.getPersistedScale();
final DisableMagnificationCallback animationEndCallback =
new DisableMagnificationCallback(transitionCallBack, displayId, targetMode,
@@ -111,6 +128,52 @@ public class MagnificationTransitionController {
setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
}
+ void onRequestMagnificationSpec(int displayId, int serviceId) {
+ synchronized (mLock) {
+ if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) {
+ return;
+ }
+ if (mWindowMagnificationMgr == null
+ || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
+ return;
+ }
+ mWindowMagnificationMgr.disableWindowMagnification(displayId, false);
+ }
+ }
+
+ /**
+ * Updates the active user ID of {@link FullScreenMagnificationController} and {@link
+ * WindowMagnificationManager}.
+ *
+ * @param userId the currently active user ID
+ */
+ public void updateUserIdIfNeeded(int userId) {
+ synchronized (mLock) {
+ if (mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.setUserId(userId);
+ }
+ if (mWindowMagnificationMgr != null) {
+ mWindowMagnificationMgr.setUserId(userId);
+ }
+ }
+ }
+
+ /**
+ * Removes the magnification instance with given id.
+ *
+ * @param displayId The logical display id.
+ */
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLock) {
+ if (mFullScreenMagnificationController != null) {
+ mFullScreenMagnificationController.onDisplayRemoved(displayId);
+ }
+ if (mWindowMagnificationMgr != null) {
+ mWindowMagnificationMgr.onDisplayRemoved(displayId);
+ }
+ }
+ }
+
private DisableMagnificationCallback getDisableMagnificationEndRunnableLocked(
int displayId) {
return mMagnificationEndRunnableSparseArray.get(displayId);
@@ -125,31 +188,63 @@ public class MagnificationTransitionController {
}
}
- private FullScreenMagnificationController getFullScreenMagnificationController() {
- return mAms.getFullScreenMagnificationController();
+ /**
+ * Getter of {@link FullScreenMagnificationController}.
+ *
+ * @return {@link FullScreenMagnificationController}.
+ */
+ public FullScreenMagnificationController getFullScreenMagnificationController() {
+ synchronized (mLock) {
+ if (mFullScreenMagnificationController == null) {
+ mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
+ mAms, mLock, this::onRequestMagnificationSpec);
+ mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
+ }
+ }
+ return mFullScreenMagnificationController;
+ }
+
+ /**
+ * Is {@link #mFullScreenMagnificationController} is initialized.
+ * @return {code true} if {@link #mFullScreenMagnificationController} is initialized.
+ */
+ public boolean isFullScreenMagnificationControllerInitialized() {
+ synchronized (mLock) {
+ return mFullScreenMagnificationController != null;
+ }
}
- private WindowMagnificationManager getWindowMagnificationManager() {
- return mAms.getWindowMagnificationMgr();
+ /**
+ * Getter of {@link WindowMagnificationManager}.
+ *
+ * @return {@link WindowMagnificationManager}.
+ */
+ public WindowMagnificationManager getWindowMagnificationMgr() {
+ synchronized (mLock) {
+ if (mWindowMagnificationMgr == null) {
+ mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
+ mAms.getCurrentUserIdLocked());
+ }
+ return mWindowMagnificationMgr;
+ }
}
private @Nullable
PointF getCurrentMagnificationBoundsCenterLocked(int displayId, int targetMode) {
if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
- final WindowMagnificationManager magnificationManager = getWindowMagnificationManager();
- if (!magnificationManager.isWindowMagnifierEnabled(displayId)) {
+ if (mWindowMagnificationMgr == null
+ || !mWindowMagnificationMgr.isWindowMagnifierEnabled(displayId)) {
return null;
}
- mTempPoint.set(magnificationManager.getCenterX(displayId),
- magnificationManager.getCenterY(displayId));
+ mTempPoint.set(mWindowMagnificationMgr.getCenterX(displayId),
+ mWindowMagnificationMgr.getCenterY(displayId));
} else {
- final FullScreenMagnificationController screenMagnificationController =
- getFullScreenMagnificationController();
- if (!screenMagnificationController.isMagnifying(displayId)) {
+ if (mFullScreenMagnificationController == null
+ || !mFullScreenMagnificationController.isMagnifying(displayId)) {
return null;
}
- mTempPoint.set(screenMagnificationController.getCenterX(displayId),
- screenMagnificationController.getCenterY(displayId));
+ mTempPoint.set(mFullScreenMagnificationController.getCenterX(displayId),
+ mFullScreenMagnificationController.getCenterY(displayId));
}
return mTempPoint;
}
@@ -228,7 +323,7 @@ public class MagnificationTransitionController {
mCurrentCenter.y, true,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
} else {
- getWindowMagnificationManager().enableWindowMagnification(mDisplayId,
+ getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
mCurrentScale, mCurrentCenter.x,
mCurrentCenter.y);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index c8e485f503ec..ed3085f97ded 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -452,6 +452,15 @@ public class WindowMagnificationManager implements
return magnifier;
}
+ /**
+ * Removes the window magnifier with given id.
+ *
+ * @param displayId The logical display id.
+ */
+ void onDisplayRemoved(int displayId) {
+ disableWindowMagnification(displayId, true);
+ }
+
private class ConnectionCallback extends IWindowMagnificationConnectionCallback.Stub implements
IBinder.DeathRecipient {
private boolean mExpiredDeathRecipient = false;
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 59ba82e4616a..e9a099ae43a9 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -189,7 +189,7 @@ public class AppPredictionManagerService extends
throw new SecurityException(msg);
}
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
final AppPredictionPerUserService service = getServiceForUserLocked(userId);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 060d0971e391..35312a3a2fac 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -630,8 +630,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
final long identity = Binder.clearCallingIdentity();
try {
if (provider.maskedBySuspendedPackage) {
- UserInfo userInfo = mUserManager.getUserInfo(providerUserId);
- showBadge = userInfo.isManagedProfile();
+ showBadge = mUserManager.hasBadge(providerUserId);
final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
providerPackage, providerUserId);
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
@@ -2408,7 +2407,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
intent.setComponent(provider.info.provider);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
provider.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
PendingIntent.FLAG_UPDATE_CURRENT, provider.info.getProfile());
@@ -3616,10 +3615,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
private boolean isProfileWithLockedParent(int userId) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
UserInfo userInfo = mUserManager.getUserInfo(userId);
- if (userInfo != null && userInfo.isManagedProfile()) {
+ if (userInfo != null && userInfo.isProfile()) {
UserInfo parentInfo = mUserManager.getProfileParent(userId);
if (parentInfo != null
&& !isUserRunningAndUnlocked(parentInfo.getUserHandle().getIdentifier())) {
@@ -3634,7 +3633,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
private boolean isProfileWithUnlockedParent(int userId) {
UserInfo userInfo = mUserManager.getUserInfo(userId);
- if (userInfo != null && userInfo.isManagedProfile()) {
+ if (userInfo != null && userInfo.isProfile()) {
UserInfo parentInfo = mUserManager.getProfileParent(userId);
if (parentInfo != null
&& mUserManager.isUserUnlockingOrUnlocked(parentInfo.getUserHandle())) {
diff --git a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
index 0e99b34e9dd1..bcec9c665527 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentTimeoutParameters.java
@@ -19,6 +19,7 @@ package com.android.server.backup;
import android.content.ContentResolver;
import android.os.Handler;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.KeyValueSettingObserver;
@@ -53,10 +54,18 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
"restore_agent_timeout_millis";
@VisibleForTesting
+ public static final String SETTING_RESTORE_SYSTEM_AGENT_TIMEOUT_MILLIS =
+ "restore_system_agent_timeout_millis";
+
+ @VisibleForTesting
public static final String SETTING_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS =
"restore_agent_finished_timeout_millis";
@VisibleForTesting
+ public static final String SETTING_RESTORE_SESSION_TIMEOUT_MILLIS =
+ "restore_session_timeout_millis";
+
+ @VisibleForTesting
public static final String SETTING_QUOTA_EXCEEDED_TIMEOUT_MILLIS =
"quota_exceeded_timeout_millis";
@@ -71,9 +80,14 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
@VisibleForTesting public static final long DEFAULT_RESTORE_AGENT_TIMEOUT_MILLIS = 60 * 1000;
+ @VisibleForTesting public static final long DEFAULT_RESTORE_SYSTEM_AGENT_TIMEOUT_MILLIS =
+ 180 * 1000;
+
@VisibleForTesting
public static final long DEFAULT_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS = 30 * 1000;
+ @VisibleForTesting public static final long DEFAULT_RESTORE_SESSION_TIMEOUT_MILLIS = 60 * 1000;
+
@VisibleForTesting
public static final long DEFAULT_QUOTA_EXCEEDED_TIMEOUT_MILLIS = 3 * 1000;
@@ -90,6 +104,12 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
private long mRestoreAgentTimeoutMillis;
@GuardedBy("mLock")
+ private long mRestoreSystemAgentTimeoutMillis;
+
+ @GuardedBy("mLock")
+ private long mRestoreSessionTimeoutMillis;
+
+ @GuardedBy("mLock")
private long mRestoreAgentFinishedTimeoutMillis;
@GuardedBy("mLock")
@@ -123,10 +143,18 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
parser.getLong(
SETTING_RESTORE_AGENT_TIMEOUT_MILLIS,
DEFAULT_RESTORE_AGENT_TIMEOUT_MILLIS);
+ mRestoreSystemAgentTimeoutMillis =
+ parser.getLong(
+ SETTING_RESTORE_SYSTEM_AGENT_TIMEOUT_MILLIS,
+ DEFAULT_RESTORE_SYSTEM_AGENT_TIMEOUT_MILLIS);
mRestoreAgentFinishedTimeoutMillis =
parser.getLong(
SETTING_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS,
DEFAULT_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS);
+ mRestoreSessionTimeoutMillis =
+ parser.getLong(
+ SETTING_RESTORE_SESSION_TIMEOUT_MILLIS,
+ DEFAULT_RESTORE_SESSION_TIMEOUT_MILLIS);
mQuotaExceededTimeoutMillis =
parser.getLong(
SETTING_QUOTA_EXCEEDED_TIMEOUT_MILLIS,
@@ -152,9 +180,20 @@ public class BackupAgentTimeoutParameters extends KeyValueSettingObserver {
}
}
- public long getRestoreAgentTimeoutMillis() {
+ /**
+ * @param applicationUid UID of the application for which to get restore timeout
+ * @return restore timeout in milliseconds
+ */
+ public long getRestoreAgentTimeoutMillis(int applicationUid) {
+ synchronized (mLock) {
+ return UserHandle.isCore(applicationUid) ? mRestoreSystemAgentTimeoutMillis :
+ mRestoreAgentTimeoutMillis;
+ }
+ }
+
+ public long getRestoreSessionTimeoutMillis() {
synchronized (mLock) {
- return mRestoreAgentTimeoutMillis;
+ return mRestoreSessionTimeoutMillis;
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 186812bc15c7..75bbec67c66e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -478,7 +478,7 @@ public class BackupManagerService extends IBackupManager.Stub {
if (getUserManager().isUserUnlocked(userId)) {
// Clear calling identity as initialization enforces the system identity but we
// can be coming from shell.
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
startServiceForUser(userId);
} finally {
@@ -1412,8 +1412,8 @@ public class BackupManagerService extends IBackupManager.Stub {
return null;
}
int callingUserId = Binder.getCallingUserHandle().getIdentifier();
- long oldId = Binder.clearCallingIdentity();
final int[] userIds;
+ final long oldId = Binder.clearCallingIdentity();
try {
userIds = getUserManager().getProfileIds(callingUserId, false);
} finally {
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 0e7fc93df7cc..4eb1d9a6a7c5 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -40,7 +40,7 @@ import java.util.function.Consumer;
* <p>This information is persisted to the filesystem so that it is not lost in the event of a
* reboot.
*/
-public final class DataChangedJournal {
+public class DataChangedJournal {
private static final String TAG = "DataChangedJournal";
private static final String FILE_NAME_PREFIX = "journal";
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index e68c07ed73f7..aa8532b67c43 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2889,16 +2889,18 @@ public class UserBackupManagerService {
mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL);
return;
}
- long oldId = Binder.clearCallingIdentity();
- OnTaskFinishedListener listener =
- caller ->
- mTransportManager.disposeOfTransportClient(transportClient, caller);
- mWakelock.acquire();
- Message msg = mBackupHandler.obtainMessage(
- MSG_RUN_CLEAR,
- new ClearParams(transportClient, info, listener));
- mBackupHandler.sendMessage(msg);
- Binder.restoreCallingIdentity(oldId);
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ OnTaskFinishedListener listener = caller -> mTransportManager
+ .disposeOfTransportClient(transportClient, caller);
+ mWakelock.acquire();
+ Message msg = mBackupHandler.obtainMessage(
+ MSG_RUN_CLEAR,
+ new ClearParams(transportClient, info, listener));
+ mBackupHandler.sendMessage(msg);
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
}
}
}
@@ -2910,7 +2912,7 @@ public class UserBackupManagerService {
public void backupNow() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
@@ -2999,7 +3001,7 @@ public class UserBackupManagerService {
}
}
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
if (!mSetupComplete) {
Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup not supported before setup"));
@@ -3156,7 +3158,7 @@ public class UserBackupManagerService {
throw new IllegalStateException("Restore supported only for the device owner");
}
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
if (!mSetupComplete) {
@@ -3287,7 +3289,7 @@ public class UserBackupManagerService {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP,
"acknowledgeAdbBackupOrRestore");
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
AdbParams params;
@@ -3348,7 +3350,7 @@ public class UserBackupManagerService {
Slog.i(TAG, addUserIdToLogMessage(mUserId, "Backup enabled => " + enable));
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
boolean wasEnabled = mEnabled;
synchronized (this) {
@@ -3477,7 +3479,7 @@ public class UserBackupManagerService {
public ComponentName getCurrentTransportComponent() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "getCurrentTransportComponent");
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
return mTransportManager.getCurrentTransportComponent();
} catch (TransportNotRegisteredException e) {
@@ -4088,7 +4090,7 @@ public class UserBackupManagerService {
mActiveRestoreSession = new ActiveRestoreSession(this, packageName, transport,
getEligibilityRulesForOperation(operationType));
mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
- mAgentTimeoutParameters.getRestoreAgentTimeoutMillis());
+ mAgentTimeoutParameters.getRestoreSessionTimeoutMillis());
}
return mActiveRestoreSession;
}
@@ -4165,7 +4167,7 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "isAppEligibleForBackup");
- long oldToken = Binder.clearCallingIdentity();
+ final long oldToken = Binder.clearCallingIdentity();
try {
String callerLogString = "BMS.isAppEligibleForBackup";
TransportClient transportClient =
@@ -4187,7 +4189,7 @@ public class UserBackupManagerService {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "filterAppsEligibleForBackup");
- long oldToken = Binder.clearCallingIdentity();
+ final long oldToken = Binder.clearCallingIdentity();
try {
String callerLogString = "BMS.filterAppsEligibleForBackup";
TransportClient transportClient =
@@ -4221,7 +4223,7 @@ public class UserBackupManagerService {
/** Prints service state for 'dumpsys backup'. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
try {
if (args != null) {
for (String arg : args) {
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 100dbae9f01d..1cb7c11e9499 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -390,7 +390,7 @@ public class BackupHandler extends Handler {
// Done: reset the session timeout clock
removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
sendEmptyMessageDelayed(MSG_RESTORE_SESSION_TIMEOUT,
- mAgentTimeoutParameters.getRestoreAgentTimeoutMillis());
+ mAgentTimeoutParameters.getRestoreSessionTimeoutMillis());
params.listener.onFinished(callerLogString);
}
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 3102b5f4a04d..602dc24541b2 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -98,7 +98,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
return -1;
}
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
TransportClient transportClient =
mTransportManager.getTransportClient(
@@ -173,7 +173,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
synchronized (mBackupManagerService.getQueueLock()) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -265,7 +265,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
synchronized (mBackupManagerService.getQueueLock()) {
for (int i = 0; i < mRestoreSets.length; i++) {
if (token == mRestoreSets[i].token) {
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
return sendRestoreToHandlerLocked(
(transportClient, listener) ->
@@ -341,7 +341,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
}
// So far so good; we're allowed to try to restore this package.
- long oldId = Binder.clearCallingIdentity();
+ final long oldId = Binder.clearCallingIdentity();
try {
// Check whether there is data for it in the current dataset, falling back
// to the ancestral dataset if not.
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 16077cb6082f..5718bdfc4971 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -403,7 +403,8 @@ public class FullRestoreEngine extends RestoreEngine {
final boolean isSharedStorage = pkg.equals(SHARED_BACKUP_AGENT_PACKAGE);
final long timeout = isSharedStorage ?
mAgentTimeoutParameters.getSharedBackupAgentTimeoutMillis() :
- mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
+ mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
+ mTargetApp.uid);
try {
mBackupManagerService.prepareOperationTimeout(token,
timeout,
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 abf11bd542a1..261ebe69a15e 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -46,6 +46,7 @@ import android.content.pm.PackageManagerInternal;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -433,6 +434,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Pull the Package Manager metadata from the restore set first
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
+ mCurrentPackage.applicationInfo = new ApplicationInfo();
+ mCurrentPackage.applicationInfo.uid = Process.SYSTEM_UID;
mPmAgent = backupManagerService.makeMetadataAgent(null);
mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
if (MORE_DEBUG) {
@@ -760,7 +763,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Kick off the restore, checking for hung agents. The timeout or
// the operationComplete() callback will schedule the next step,
// so we do not do that here.
- long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
+ long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
+ app.applicationInfo.uid);
backupManagerService.prepareOperationTimeout(
mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
startedAgentRestore = true;
@@ -1122,7 +1126,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
} else {
// We were invoked via an active restore session, not by the Package
// Manager, so start up the session timeout again.
- long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis();
+ long restoreAgentTimeoutMillis =
+ mAgentTimeoutParameters.getRestoreSessionTimeoutMillis();
backupManagerService.getBackupHandler().sendEmptyMessageDelayed(
MSG_RESTORE_SESSION_TIMEOUT,
restoreAgentTimeoutMillis);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 6ca99d1a244b..032820dc97f8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -317,7 +317,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
AndroidFuture<Association> future = new AndroidFuture<>();
service.startDiscovery(request, callingPackage, callback, future);
return future;
- }).whenComplete(uncheckExceptions((association, err) -> {
+ }).cancelTimeout().whenComplete(uncheckExceptions((association, err) -> {
if (err == null) {
addAssociation(association);
} else {
@@ -391,7 +391,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
checkArgument(getCallingUserId() == userId,
"Must be called by either same user or system");
- mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg);
+ int callingUid = Binder.getCallingUid();
+ if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) {
+ throw new SecurityException(pkg + " doesn't belong to uid " + callingUid);
+ }
}
@Override
@@ -408,7 +411,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
PackageItemInfo.SAFE_LABEL_FLAG_TRIM
| PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE)
.toString());
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 37e18cf8f942..f6c406260741 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -113,6 +113,7 @@ java_library_static {
"android.hardware.weaver-V1.0-java",
"android.hardware.biometrics.face-V1.1-java",
"android.hardware.biometrics.fingerprint-V2.3-java",
+ "android.hardware.biometrics.fingerprint-java",
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 0c56d46151c1..43c54b4a97b3 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -76,6 +76,7 @@ public abstract class PackageManagerInternal {
public static final int PACKAGE_COMPANION = 14;
public static final int PACKAGE_RETAIL_DEMO = 15;
public static final int PACKAGE_OVERLAY_CONFIG_SIGNATURE = 16;
+ public static final int LAST_KNOWN_PACKAGE = PACKAGE_OVERLAY_CONFIG_SIGNATURE;
@IntDef(flag = true, prefix = "RESOLVE_", value = {
RESOLVE_NON_BROWSER_ONLY,
@@ -1016,6 +1017,51 @@ public abstract class PackageManagerInternal {
@NonNull InstalledLoadingProgressCallback callback);
/**
+ * Returns the string representation of a known package. For example,
+ * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
+ *
+ * @param knownPackage The known package.
+ * @return The string representation.
+ */
+ public static @NonNull String knownPackageToString(@KnownPackage int knownPackage) {
+ switch (knownPackage) {
+ case PACKAGE_SYSTEM:
+ return "System";
+ case PACKAGE_SETUP_WIZARD:
+ return "Setup Wizard";
+ case PACKAGE_INSTALLER:
+ return "Installer";
+ case PACKAGE_VERIFIER:
+ return "Verifier";
+ case PACKAGE_BROWSER:
+ return "Browser";
+ case PACKAGE_SYSTEM_TEXT_CLASSIFIER:
+ return "System Text Classifier";
+ case PACKAGE_PERMISSION_CONTROLLER:
+ return "Permission Controller";
+ case PACKAGE_WELLBEING:
+ return "Wellbeing";
+ case PACKAGE_DOCUMENTER:
+ return "Documenter";
+ case PACKAGE_CONFIGURATOR:
+ return "Configurator";
+ case PACKAGE_INCIDENT_REPORT_APPROVER:
+ return "Incident Report Approver";
+ case PACKAGE_APP_PREDICTOR:
+ return "App Predictor";
+ case PACKAGE_WIFI:
+ return "Wi-Fi";
+ case PACKAGE_COMPANION:
+ return "Companion";
+ case PACKAGE_RETAIL_DEMO:
+ return "Retail Demo";
+ case PACKAGE_OVERLAY_CONFIG_SIGNATURE:
+ return "Overlay Config Signature";
+ }
+ return "Unknown";
+ }
+
+ /**
* Callback to listen for loading progress of a package installed on Incremental File System.
*/
public abstract static class InstalledLoadingProgressCallback {
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index b7fed87d570d..958c15c8d432 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -50,5 +50,10 @@ public abstract class BatteryStatsInternal {
* Informs battery stats of binder stats for the given work source UID.
*/
public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
- Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids);
+ Collection<BinderCallsStats.CallStat> callStats);
+
+ /**
+ * Informs battery stats of native thread IDs of threads taking incoming binder calls.
+ */
+ public abstract void noteBinderThreadNativeIds(int[] binderThreadNativeTids);
}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index d92706d2dd19..9455051ad740 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -916,7 +916,7 @@ public final class BatteryService extends SystemService {
mHealthInfo.chargerAcOnline = false;
mHealthInfo.chargerUsbOnline = false;
mHealthInfo.chargerWirelessOnline = false;
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
processValuesFromShellLocked(pw, opts);
@@ -979,7 +979,7 @@ public final class BatteryService extends SystemService {
break;
}
if (update) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
processValuesFromShellLocked(pw, opts);
@@ -996,7 +996,7 @@ public final class BatteryService extends SystemService {
int opts = parseOptions(shell);
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (mUpdatesStopped) {
mUpdatesStopped = false;
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index c9513592ea79..66ac889ff2ca 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -49,6 +49,7 @@ import com.android.internal.util.DumpUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
public class BinderCallsStatsService extends Binder {
@@ -273,7 +274,19 @@ public class BinderCallsStatsService extends Binder {
BatteryStatsInternal batteryStatsInternal = getLocalService(
BatteryStatsInternal.class);
- mBinderCallsStats.setCallStatsObserver(batteryStatsInternal::noteBinderCallStats);
+ mBinderCallsStats.setCallStatsObserver(new BinderInternal.CallStatsObserver() {
+ @Override
+ public void noteCallStats(int workSourceUid, long incrementalCallCount,
+ Collection<BinderCallsStats.CallStat> callStats) {
+ batteryStatsInternal.noteBinderCallStats(workSourceUid,
+ incrementalCallCount, callStats);
+ }
+
+ @Override
+ public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
+ batteryStatsInternal.noteBinderThreadNativeIds(binderThreadNativeTids);
+ }
+ });
// It needs to be called before mService.systemReady to make sure the observer is
// initialized before installing it.
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 1246d1c6b452..d14a2d2b497d 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -610,7 +610,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
Slog.d(TAG, "Persisting Bluetooth Setting: " + value);
}
// waive WRITE_SECURE_SETTINGS permission check
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, value);
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -2696,7 +2696,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
int foregroundUser;
int callingUser = UserHandle.getCallingUserId();
int callingUid = Binder.getCallingUid();
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
UserInfo ui = um.getProfileParent(callingUser);
int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
@@ -2955,7 +2955,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
private boolean isBluetoothDisallowed() {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
return mContext.getSystemService(UserManager.class)
.hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e8324c90a72c..84a242a49ca7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -144,6 +144,7 @@ import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult;
import android.net.util.LinkPropertiesUtils.CompareResult;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
+import android.os.BasicShellCommandHandler;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -160,11 +161,8 @@ import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
-import android.os.ShellCallback;
-import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -1154,7 +1152,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Listen to package add and removal events for all users.
intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
@@ -7750,14 +7747,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
@Override
- public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
- FileDescriptor err, @NonNull String[] args, ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) {
- (new ShellCmd()).exec(this, in, out, err, args, callback, resultReceiver);
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return new ShellCmd().exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+ err.getFileDescriptor(), args);
}
- private class ShellCmd extends ShellCommand {
-
+ private class ShellCmd extends BasicShellCommandHandler {
@Override
public int onCommand(String cmd) {
if (cmd == null) {
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index b3d4085288dd..95a7a221697a 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -38,7 +38,6 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.telecom.TelecomManager;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
@@ -46,7 +45,6 @@ import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -529,15 +527,9 @@ public class GestureLauncherService extends SystemService {
"userSetupComplete = %s, performing panic gesture.",
userSetupComplete));
}
- // TODO(b/160006048): Not all devices have telephony. Check system feature first.
- TelecomManager telecomManager = (TelecomManager) mContext.getSystemService(
- Context.TELECOM_SERVICE);
- mContext.startActivity(telecomManager.createLaunchEmergencyDialerIntent(null).addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_SINGLE_TOP).putExtra(
- "com.android.phone.EmergencyDialer.extra.ENTRY_TYPE",
- 2)); // 2 maps to power button, forcing into fast emergency dialer experience.
+ StatusBarManagerInternal service = LocalServices.getService(
+ StatusBarManagerInternal.class);
+ service.onEmergencyActionLaunchGestureDetected();
return true;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 1804b7f66fa2..593c406ed47b 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -511,7 +511,7 @@ public class MmsServiceBroker extends SystemService {
contentUri = ContentProvider.maybeAddUserId(contentUri, callingUserId);
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
final UriGrantsManagerInternal ugm = LocalServices
.getService(UriGrantsManagerInternal.class);
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index 6bc1a570b7c0..0f4c94bc8d4f 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -113,6 +113,7 @@ public class MountServiceIdler extends JobService {
JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
builder.setRequiresDeviceIdle(true);
builder.setRequiresBatteryNotLow(true);
+ builder.setRequiresCharging(true);
builder.setMinimumLatency(nextScheduleTime);
tm.schedule(builder.build());
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 8099f8f37bc7..e67b9d8ccd91 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -2,8 +2,7 @@
per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java = codewiz@google.com, ek@google.com, jchalard@google.com, junyulai@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
# Vibrator / Threads
-per-file VibratorService.java, DisplayThread.java = michaelwr@google.com
-per-file VibratorService.java, DisplayThread.java = ogunwale@google.com
+per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com, srnvs@google.com
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 829fca66ec0d..9fc8f0b5a3c3 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -454,10 +454,14 @@ public class RescueParty {
public boolean mayObservePackage(String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
- // A package is a Mainline module if this is non-null
+ // A package is a module if this is non-null
if (pm.getModuleInfo(packageName, 0) != null) {
return true;
}
+ } catch (PackageManager.NameNotFoundException ignore) {
+ }
+
+ try {
ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
} catch (PackageManager.NameNotFoundException e) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4d57fdeafb7c..aa3934293d32 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -18,14 +18,10 @@ package com.android.server;
import static android.Manifest.permission.ACCESS_MTP;
import static android.Manifest.permission.INSTALL_PACKAGES;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
@@ -137,7 +133,6 @@ import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.AppFuseMount;
@@ -153,7 +148,6 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.SystemService.TargetUser;
import com.android.server.pm.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
@@ -1749,7 +1743,7 @@ class StorageManagerService extends IStorageManager.Stub
UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
final int callingUserId = UserHandle.getCallingUserId();
boolean isAdmin;
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
isAdmin = um.getUserInfo(callingUserId).isAdmin();
} finally {
@@ -2524,19 +2518,6 @@ class StorageManagerService extends IStorageManager.Stub
abortIdleMaint(null);
}
- private void remountUidExternalStorage(int uid, int mode) {
- if (uid == Process.SYSTEM_UID) {
- // No need to remount uid for system because it has all access anyways
- return;
- }
-
- try {
- mVold.remountUid(uid, mode);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- }
- }
-
@Override
public void setDebugFlags(int flags, int mask) {
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
@@ -3862,21 +3843,6 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- private IAppOpsCallback.Stub mAppOpsCallback = new IAppOpsCallback.Stub() {
- @Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
- if (!ENABLE_ISOLATED_STORAGE) return;
-
- int mountMode = getMountMode(uid, packageName);
- boolean isUidActive = LocalServices.getService(ActivityManagerInternal.class)
- .getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT;
-
- if (isUidActive) {
- remountUidExternalStorage(uid, mountMode);
- }
- }
- };
-
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
@@ -4251,19 +4217,9 @@ class StorageManagerService extends IStorageManager.Stub
}
// Determine if caller is holding runtime permission
- final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
- uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
- // We're only willing to give out broad access if they also hold
- // runtime permission; this is a firm CDD requirement
- final boolean hasFull = mIPackageManager.checkUidPermission(WRITE_MEDIA_STORAGE,
- uid) == PERMISSION_GRANTED;
- if (hasFull && hasWrite) {
- return Zygote.MOUNT_EXTERNAL_FULL;
- }
-
// We're only willing to give out installer access if they also hold
// runtime permission; this is a firm CDD requirement
final boolean hasInstall = mIPackageManager.checkUidPermission(INSTALL_PACKAGES,
@@ -4283,19 +4239,7 @@ class StorageManagerService extends IStorageManager.Stub
if ((hasInstall || hasInstallOp) && hasWrite) {
return Zygote.MOUNT_EXTERNAL_INSTALLER;
}
-
- // Otherwise we're willing to give out sandboxed or non-sandboxed if
- // they hold the runtime permission
- boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE,
- uid, packageName) == MODE_ALLOWED;
-
- if (hasLegacy && hasWrite) {
- return Zygote.MOUNT_EXTERNAL_WRITE;
- } else if (hasLegacy && hasRead) {
- return Zygote.MOUNT_EXTERNAL_READ;
- } else {
- return Zygote.MOUNT_EXTERNAL_DEFAULT;
- }
+ return Zygote.MOUNT_EXTERNAL_DEFAULT;
} catch (RemoteException e) {
// Should not happen
}
@@ -4585,12 +4529,6 @@ class StorageManagerService extends IStorageManager.Stub
}
@Override
- public void onExternalStoragePolicyChanged(int uid, String packageName) {
- final int mountMode = getExternalStorageMountMode(uid, packageName);
- remountUidExternalStorage(uid, mountMode);
- }
-
- @Override
public int getExternalStorageMountMode(int uid, String packageName) {
if (ENABLE_ISOLATED_STORAGE) {
return getMountMode(uid, packageName);
@@ -4656,6 +4594,11 @@ class StorageManagerService extends IStorageManager.Stub
// make sure the OBB dir for the application is setup correctly, if it exists.
File[] packageObbDirs = userEnv.buildExternalStorageAppObbDirs(packageName);
for (File packageObbDir : packageObbDirs) {
+ if (packageObbDir.getPath().startsWith(
+ Environment.getDataPreloadsMediaDirectory().getPath())) {
+ Slog.i(TAG, "Skipping app data preparation for " + packageObbDir);
+ continue;
+ }
try {
mVold.fixupAppDir(packageObbDir.getCanonicalPath() + "/", uid);
} catch (IOException e) {
@@ -4730,16 +4673,6 @@ class StorageManagerService extends IStorageManager.Stub
updateLegacyStorageApps(packageName, uid, mode == MODE_ALLOWED);
return;
}
-
- if (mode == MODE_ALLOWED && (code == OP_READ_EXTERNAL_STORAGE
- || code == OP_WRITE_EXTERNAL_STORAGE
- || code == OP_REQUEST_INSTALL_PACKAGES)) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- if (userManagerInternal.isUserInitialized(UserHandle.getUserId(uid))) {
- onExternalStoragePolicyChanged(uid, packageName);
- }
- }
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 34e63705d781..b87468419430 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -17,13 +17,13 @@
package com.android.server;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.SystemClock;
import android.os.Trace;
-import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.util.ArrayMap;
import android.util.Slog;
@@ -84,6 +84,13 @@ public final class SystemServiceManager {
@GuardedBy("mTargetUsers")
private final SparseArray<TargetUser> mTargetUsers = new SparseArray<>();
+ /**
+ * Reference to the current user, it's used to set the {@link TargetUser} on
+ * {@link #switchUser(int, int)} as the previous user might have been removed already.
+ */
+ @GuardedBy("mTargetUsers")
+ private @Nullable TargetUser mCurrentUser;
+
SystemServiceManager(Context context) {
mContext = context;
}
@@ -259,18 +266,22 @@ public final class SystemServiceManager {
return targetUser;
}
+ private @NonNull TargetUser newTargetUser(@UserIdInt int userId) {
+ final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
+ Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
+ return new TargetUser(userInfo);
+ }
+
/**
* Starts the given user.
*/
public void startUser(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- // Create cached TargetUser
- final UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
- Preconditions.checkState(userInfo != null, "No UserInfo for " + userId);
+ final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
- mTargetUsers.put(userId, new TargetUser(userInfo));
+ mTargetUsers.put(userId, targetUser);
}
- onUser(t, START, userId);
+ onUser(t, START, /* prevUser= */ null, targetUser);
}
/**
@@ -291,7 +302,26 @@ public final class SystemServiceManager {
* Switches to the given user.
*/
public void switchUser(@UserIdInt int from, @UserIdInt int to) {
- onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, to, from);
+ final TargetUser curUser, prevUser;
+ synchronized (mTargetUsers) {
+ if (mCurrentUser == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "First user switch: from " + from + " to " + to);
+ }
+ prevUser = newTargetUser(from);
+ } else {
+ if (from != mCurrentUser.getUserIdentifier()) {
+ Slog.wtf(TAG, "switchUser(" + from + "," + to + "): mCurrentUser is "
+ + mCurrentUser + ", it should be " + from);
+ }
+ prevUser = mCurrentUser;
+ }
+ curUser = mCurrentUser = getTargetUser(to);
+ if (DEBUG) {
+ Slog.d(TAG, "Set mCurrentUser to " + mCurrentUser);
+ }
+ }
+ onUser(TimingsTraceAndSlog.newAsyncLog(), SWITCH, prevUser, curUser);
}
/**
@@ -314,21 +344,16 @@ public final class SystemServiceManager {
}
private void onUser(@NonNull String onWhat, @UserIdInt int userId) {
- onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, userId);
+ onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
+ getTargetUser(userId));
}
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
- @UserIdInt int userId) {
- onUser(t, onWhat, userId, UserHandle.USER_NULL);
- }
-
- private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
- @UserIdInt int curUserId, @UserIdInt int prevUserId) {
+ @Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
+ final int curUserId = curUser.getUserIdentifier();
t.traceBegin("ssm." + onWhat + "User-" + curUserId);
- Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
- final TargetUser curUser = getTargetUser(curUserId);
- final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
- : getTargetUser(prevUserId);
+ Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId
+ + (prevUser != null ? " (from " + prevUser + ")" : ""));
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
@@ -466,14 +491,16 @@ public final class SystemServiceManager {
.append(service.getClass().getSimpleName())
.append("\n");
}
-
- builder.append("Target users: ");
- final int targetUsersSize = mTargetUsers.size();
- for (int i = 0; i < targetUsersSize; i++) {
- mTargetUsers.valueAt(i).dump(builder);
- if (i != targetUsersSize - 1) builder.append(',');
+ synchronized (mTargetUsers) {
+ builder.append("Current user: ").append(mCurrentUser).append('\n');
+ builder.append("Target users: ");
+ final int targetUsersSize = mTargetUsers.size();
+ for (int i = 0; i < targetUsersSize; i++) {
+ mTargetUsers.valueAt(i).dump(builder);
+ if (i != targetUsersSize - 1) builder.append(',');
+ }
+ builder.append('\n');
}
- builder.append('\n');
Slog.e(TAG, builder.toString());
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 1cb7c4d18326..ebeec39d6ae6 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -23,6 +23,10 @@
"file_patterns": ["NotificationManagerService\\.java"]
},
{
+ "name": "CtsScopedStorageCoreHostTest",
+ "file_patterns": ["StorageManagerService\\.java"]
+ },
+ {
"name": "CtsScopedStorageHostTest",
"file_patterns": ["StorageManagerService\\.java"]
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ba2e147a2acf..8ea71b3202e5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1623,10 +1623,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyDisplayInfoChanged()")) {
return;
}
+ String str = "notifyDisplayInfoChanged: PhoneId=" + phoneId + " subId=" + subId
+ + " telephonyDisplayInfo=" + telephonyDisplayInfo;
if (VDBG) {
- log("notifyDisplayInfoChanged: PhoneId=" + phoneId
- + " subId=" + subId + " telephonyDisplayInfo=" + telephonyDisplayInfo);
+ log(str);
}
+ mLocalLog.log(str);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mTelephonyDisplayInfos[phoneId] = telephonyDisplayInfo;
@@ -2354,10 +2356,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
pw.println("mBarringInfo=" + mBarringInfo.get(i));
+ pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
pw.decreaseIndent();
}
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
-
pw.println("mPhoneCapability=" + mPhoneCapability);
pw.println("mActiveDataSubId=" + mActiveDataSubId);
pw.println("mRadioPowerState=" + mRadioPowerState);
@@ -2418,7 +2420,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.notePhoneState(state.getState());
} catch (RemoteException re) {
@@ -2442,7 +2444,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private void broadcastSignalStrengthChanged(SignalStrength signalStrength, int phoneId,
int subId) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.notePhoneSignalStrength(signalStrength);
} catch (RemoteException e) {
@@ -2487,7 +2489,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
*/
private void broadcastCallStateChanged(int state, String incomingNumber, int phoneId,
int subId) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (state == TelephonyManager.CALL_STATE_IDLE) {
mBatteryStats.notePhoneOff();
@@ -2676,7 +2678,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private boolean validateEventsAndUserLocked(Record r, int events) {
int foregroundUser;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
boolean valid = false;
try {
foregroundUser = ActivityManager.getCurrentUser();
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index bcb7bfacfaf3..0135b9d2f8ab 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -72,7 +72,6 @@ import com.android.internal.app.DisableCarModeActivity;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.DumpUtils;
-import com.android.server.SystemService.TargetUser;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -327,8 +326,16 @@ final class UiModeManagerService extends SystemService {
@Override
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
mCurrentUser = to.getUserIdentifier();
+ if (mNightMode == MODE_NIGHT_AUTO) persistComputedNightMode(from.getUserIdentifier());
getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver);
verifySetupWizardCompleted();
+ synchronized (mLock) {
+ // only update if the value is actually changed
+ if (updateNightModeFromSettingsLocked(getContext(), getContext().getResources(),
+ to.getUserIdentifier())) {
+ updateLocked(0, 0);
+ }
+ }
}
@Override
@@ -356,11 +363,10 @@ final class UiModeManagerService extends SystemService {
new IntentFilter(Intent.ACTION_DOCK_EVENT));
IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
context.registerReceiver(mBatteryReceiver, batteryFilter);
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
context.registerReceiver(mSettingsRestored,
new IntentFilter(Intent.ACTION_SETTING_RESTORED));
- context.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler);
+ context.registerReceiver(mOnShutdown,
+ new IntentFilter(Intent.ACTION_SHUTDOWN));
updateConfigurationLocked();
applyConfigurationExternallyLocked();
}
@@ -407,6 +413,21 @@ final class UiModeManagerService extends SystemService {
publishLocalService(UiModeManagerInternal.class, mLocalService);
}
+ private final BroadcastReceiver mOnShutdown = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mNightMode == MODE_NIGHT_AUTO) {
+ persistComputedNightMode(mCurrentUser);
+ }
+ }
+ };
+
+ private void persistComputedNightMode(int userId) {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.UI_NIGHT_MODE_LAST_COMPUTED, mComputedNightMode ? 1 : 0,
+ userId);
+ }
+
private final BroadcastReceiver mSettingsRestored = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -508,6 +529,10 @@ final class UiModeManagerService extends SystemService {
Secure.getLongForUser(context.getContentResolver(),
Secure.DARK_THEME_CUSTOM_END_TIME,
DEFAULT_CUSTOM_NIGHT_END_TIME.toNanoOfDay() / 1000L, userId) * 1000);
+ if (mNightMode == MODE_NIGHT_AUTO) {
+ mComputedNightMode = Secure.getIntForUser(context.getContentResolver(),
+ Secure.UI_NIGHT_MODE_LAST_COMPUTED, 0, userId) != 0;
+ }
}
return oldNightMode != mNightMode;
@@ -1630,18 +1655,4 @@ final class UiModeManagerService extends SystemService {
}
}
}
-
- private final class UserSwitchedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- final int currentId = intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, USER_SYSTEM);
- // only update if the value is actually changed
- if (updateNightModeFromSettingsLocked(context, context.getResources(), currentId)) {
- updateLocked(0, 0);
- }
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/UpdateLockService.java b/services/core/java/com/android/server/UpdateLockService.java
index 06f73e28829e..38bbbdf742df 100644
--- a/services/core/java/com/android/server/UpdateLockService.java
+++ b/services/core/java/com/android/server/UpdateLockService.java
@@ -74,7 +74,7 @@ public class UpdateLockService extends IUpdateLock.Stub {
void sendLockChangedBroadcast(boolean state) {
// Safe early during boot because this broadcast only goes to registered receivers.
- long oldIdent = Binder.clearCallingIdentity();
+ final long oldIdent = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(UpdateLock.UPDATE_LOCK_CHANGED)
.putExtra(UpdateLock.NOW_IS_CONVENIENT, state)
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
new file mode 100644
index 000000000000..2f35da79420e
--- /dev/null
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -0,0 +1,154 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IVibratorManagerService;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/** System implementation of {@link IVibratorManagerService}. */
+public class VibratorManagerService extends IVibratorManagerService.Stub {
+ private static final String TAG = "VibratorManagerService";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final NativeWrapper mNativeWrapper;
+ private final int[] mVibratorIds;
+
+ static native long nativeInit();
+
+ static native long nativeGetFinalizer();
+
+ static native int[] nativeGetVibratorIds(long nativeServicePtr);
+
+ VibratorManagerService(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ VibratorManagerService(Context context, Injector injector) {
+ mContext = context;
+ mNativeWrapper = injector.getNativeWrapper();
+
+ mNativeWrapper.init();
+
+ int[] vibratorIds = mNativeWrapper.getVibratorIds();
+ mVibratorIds = vibratorIds == null ? new int[0] : vibratorIds;
+ }
+
+ @Override // Binder call
+ public int[] getVibratorIds() {
+ return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback cb, ResultReceiver resultReceiver) {
+ new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
+ }
+
+ /** Point of injection for test dependencies */
+ @VisibleForTesting
+ static class Injector {
+
+ NativeWrapper getNativeWrapper() {
+ return new NativeWrapper();
+ }
+ }
+
+ /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
+ @VisibleForTesting
+ public static class NativeWrapper {
+
+ private long mNativeServicePtr = 0;
+
+ /** Returns native pointer to newly created controller and connects with HAL service. */
+ public void init() {
+ mNativeServicePtr = VibratorManagerService.nativeInit();
+ long finalizerPtr = VibratorManagerService.nativeGetFinalizer();
+
+ if (finalizerPtr != 0) {
+ NativeAllocationRegistry registry =
+ NativeAllocationRegistry.createMalloced(
+ VibratorManagerService.class.getClassLoader(), finalizerPtr);
+ registry.registerNativeAllocation(this, mNativeServicePtr);
+ }
+ }
+
+ /** Returns vibrator ids. */
+ public int[] getVibratorIds() {
+ return VibratorManagerService.nativeGetVibratorIds(mNativeServicePtr);
+ }
+ }
+
+ /** Provides limited functionality from {@link VibratorManagerService} as shell commands. */
+ private final class VibratorManagerShellCommand extends ShellCommand {
+
+ private final IBinder mToken;
+
+ private VibratorManagerShellCommand(IBinder token) {
+ mToken = token;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if ("list".equals(cmd)) {
+ return runListVibrators();
+ }
+ return handleDefaultCommands(cmd);
+ }
+
+ private int runListVibrators() {
+ try (PrintWriter pw = getOutPrintWriter();) {
+ if (mVibratorIds.length == 0) {
+ pw.println("No vibrator found");
+ } else {
+ for (int id : mVibratorIds) {
+ pw.println(id);
+ }
+ }
+ pw.println("");
+ return 0;
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ try (PrintWriter pw = getOutPrintWriter();) {
+ pw.println("Vibrator Manager commands:");
+ pw.println(" help");
+ pw.println(" Prints this help text.");
+ pw.println("");
+ pw.println(" list");
+ pw.println(" Prints the id of device vibrators. This do not include any ");
+ pw.println(" connected input device.");
+ pw.println("");
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 687af107590a..afddd650c46c 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -33,7 +33,6 @@ import android.database.ContentObserver;
import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.V1_0.EffectStrength;
-import android.icu.text.DateFormat;
import android.media.AudioManager;
import android.os.BatteryStats;
import android.os.Binder;
@@ -80,6 +79,7 @@ import libcore.util.NativeAllocationRegistry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -90,6 +90,8 @@ import java.util.concurrent.atomic.AtomicInteger;
public class VibratorService extends IVibratorService.Stub
implements InputManager.InputDeviceListener {
private static final String TAG = "VibratorService";
+ private static final SimpleDateFormat DEBUG_DATE_FORMAT =
+ new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private static final boolean DEBUG = false;
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
@@ -126,7 +128,7 @@ public class VibratorService extends IVibratorService.Stub
private final LinkedList<VibrationInfo> mPreviousRingVibrations;
private final LinkedList<VibrationInfo> mPreviousNotificationVibrations;
private final LinkedList<VibrationInfo> mPreviousAlarmVibrations;
- private final LinkedList<ExternalVibration> mPreviousExternalVibrations;
+ private final LinkedList<VibrationInfo> mPreviousExternalVibrations;
private final LinkedList<VibrationInfo> mPreviousVibrations;
private final int mPreviousVibrationsLimit;
private final boolean mAllowPriorityVibrationsInLowPowerMode;
@@ -151,7 +153,7 @@ public class VibratorService extends IVibratorService.Stub
private SettingsObserver mSettingObserver;
private final NativeWrapper mNativeWrapper;
- private volatile VibrateThread mThread;
+ private volatile VibrateWaveformThread mThread;
// mInputDeviceVibrators lock should be acquired after mLock, if both are
// to be acquired
@@ -162,7 +164,7 @@ public class VibratorService extends IVibratorService.Stub
@GuardedBy("mLock")
private Vibration mCurrentVibration;
private int mCurVibUid = -1;
- private ExternalVibration mCurrentExternalVibration;
+ private ExternalVibrationHolder mCurrentExternalVibration;
private boolean mVibratorUnderExternalControl;
private boolean mLowPowerMode;
@GuardedBy("mLock")
@@ -231,19 +233,12 @@ public class VibratorService extends IVibratorService.Stub
void onComplete(long vibrationId);
}
- /**
- * Holder for a vibration to be played. This class can be shared with native methods for
- * hardware callback support.
- */
+ /** Holder for a {@link VibrationEffect}. */
private final class Vibration implements IBinder.DeathRecipient {
public final IBinder token;
// Start time in CLOCK_BOOTTIME base.
public final long startTime;
- // Start time in unix epoch time. Only to be used for debugging purposes and to correlate
- // with other system events, any duration calculations should be done use startTime so as
- // not to be affected by discontinuities created by RTC adjustments.
- public final long startTimeDebug;
public final VibrationAttributes attrs;
public final long id;
public final int uid;
@@ -255,6 +250,15 @@ public class VibratorService extends IVibratorService.Stub
// The original effect that was requested. Typically these two things differ because
// the effect was scaled based on the users vibration intensity settings.
public VibrationEffect originalEffect;
+ // The scale applied to the original effect.
+ public float scale;
+
+ // Start/end times in unix epoch time. Only to be used for debugging purposes and to
+ // correlate with other system events, any duration calculations should be done use
+ // startTime so as not to be affected by discontinuities created by RTC adjustments.
+ private final long mStartTimeDebug;
+ private long mEndTimeDebug;
+ private VibrationInfo.Status mStatus;
private Vibration(IBinder token, VibrationEffect effect,
VibrationAttributes attrs, int uid, String opPkg, String reason) {
@@ -262,11 +266,12 @@ public class VibratorService extends IVibratorService.Stub
this.effect = effect;
this.id = mNextVibrationId.getAndIncrement();
this.startTime = SystemClock.elapsedRealtime();
- this.startTimeDebug = System.currentTimeMillis();
this.attrs = attrs;
this.uid = uid;
this.opPkg = opPkg;
this.reason = reason;
+ mStartTimeDebug = System.currentTimeMillis();
+ mStatus = VibrationInfo.Status.RUNNING;
}
@Override
@@ -276,11 +281,24 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.d(TAG, "Vibration finished because binder died, cleaning up");
}
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
}
}
}
+ public void end(VibrationInfo.Status status) {
+ if (hasEnded()) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = status;
+ mEndTimeDebug = System.currentTimeMillis();
+ }
+
+ public boolean hasEnded() {
+ return mStatus != VibrationInfo.Status.RUNNING;
+ }
+
public boolean hasTimeoutLongerThan(long millis) {
final long duration = effect.getDuration();
return duration >= 0 && duration > millis;
@@ -308,40 +326,109 @@ public class VibratorService extends IVibratorService.Stub
public VibrationInfo toInfo() {
return new VibrationInfo(
- startTimeDebug, effect, originalEffect, attrs, uid, opPkg, reason);
+ mStartTimeDebug, mEndTimeDebug, effect, originalEffect, scale, attrs,
+ uid, opPkg, reason, mStatus);
}
}
+ /** Holder for a {@link ExternalVibration}. */
+ private final class ExternalVibrationHolder {
+
+ public final ExternalVibration externalVibration;
+ public int scale;
+
+ private final long mStartTimeDebug;
+ private long mEndTimeDebug;
+ private VibrationInfo.Status mStatus;
+
+ private ExternalVibrationHolder(ExternalVibration externalVibration) {
+ this.externalVibration = externalVibration;
+ this.scale = SCALE_NONE;
+ mStartTimeDebug = System.currentTimeMillis();
+ mStatus = VibrationInfo.Status.RUNNING;
+ }
+
+ public void end(VibrationInfo.Status status) {
+ if (mStatus != VibrationInfo.Status.RUNNING) {
+ // Vibration already ended, keep first ending status set and ignore this one.
+ return;
+ }
+ mStatus = status;
+ mEndTimeDebug = System.currentTimeMillis();
+ }
+
+ public VibrationInfo toInfo() {
+ return new VibrationInfo(
+ mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+ scale, externalVibration.getVibrationAttributes(),
+ externalVibration.getUid(), externalVibration.getPackage(),
+ /* reason= */ null, mStatus);
+ }
+ }
+
+ /** Debug information about vibrations. */
private static class VibrationInfo {
+
+ public enum Status {
+ RUNNING,
+ FINISHED,
+ CANCELLED,
+ ERROR_APP_OPS,
+ IGNORED,
+ IGNORED_APP_OPS,
+ IGNORED_BACKGROUND,
+ IGNORED_RINGTONE,
+ IGNORED_UNKNOWN_VIBRATION,
+ IGNORED_UNSUPPORTED,
+ IGNORED_FOR_ALARM,
+ IGNORED_FOR_EXTERNAL,
+ IGNORED_FOR_ONGOING,
+ IGNORED_FOR_POWER,
+ IGNORED_FOR_SETTINGS,
+ }
+
private final long mStartTimeDebug;
+ private final long mEndTimeDebug;
private final VibrationEffect mEffect;
private final VibrationEffect mOriginalEffect;
+ private final float mScale;
private final VibrationAttributes mAttrs;
private final int mUid;
private final String mOpPkg;
private final String mReason;
+ private final VibrationInfo.Status mStatus;
- VibrationInfo(long startTimeDebug, VibrationEffect effect,
- VibrationEffect originalEffect, VibrationAttributes attrs, int uid,
- String opPkg, String reason) {
+ VibrationInfo(long startTimeDebug, long endTimeDebug, VibrationEffect effect,
+ VibrationEffect originalEffect, float scale, VibrationAttributes attrs,
+ int uid, String opPkg, String reason, VibrationInfo.Status status) {
mStartTimeDebug = startTimeDebug;
+ mEndTimeDebug = endTimeDebug;
mEffect = effect;
mOriginalEffect = originalEffect;
+ mScale = scale;
mAttrs = attrs;
mUid = uid;
mOpPkg = opPkg;
mReason = reason;
+ mStatus = status;
}
@Override
public String toString() {
return new StringBuilder()
.append("startTime: ")
- .append(DateFormat.getDateTimeInstance().format(new Date(mStartTimeDebug)))
+ .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+ .append(", endTime: ")
+ .append(mEndTimeDebug == 0 ? null
+ : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+ .append(", status: ")
+ .append(mStatus.name().toLowerCase())
.append(", effect: ")
.append(mEffect)
.append(", originalEffect: ")
.append(mOriginalEffect)
+ .append(", scale: ")
+ .append(String.format("%.2f", mScale))
.append(", attrs: ")
.append(mAttrs)
.append(", uid: ")
@@ -354,11 +441,80 @@ public class VibratorService extends IVibratorService.Stub
}
void dumpProto(ProtoOutputStream proto, long fieldId) {
- synchronized (this) {
- final long token = proto.start(fieldId);
- proto.write(VibrationProto.START_TIME, mStartTimeDebug);
- proto.end(token);
+ final long token = proto.start(fieldId);
+ proto.write(VibrationProto.START_TIME, mStartTimeDebug);
+ proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+ proto.write(VibrationProto.STATUS, mStatus.ordinal());
+
+ final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
+ proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage());
+ proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage());
+ proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags());
+ proto.end(attrsToken);
+
+ if (mEffect != null) {
+ dumpEffect(proto, VibrationProto.EFFECT, mEffect);
}
+ if (mOriginalEffect != null) {
+ dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect);
+ }
+
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffect effect) {
+ final long token = proto.start(fieldId);
+ if (effect instanceof VibrationEffect.OneShot) {
+ dumpEffect(proto, VibrationEffectProto.ONESHOT, (VibrationEffect.OneShot) effect);
+ } else if (effect instanceof VibrationEffect.Waveform) {
+ dumpEffect(proto, VibrationEffectProto.WAVEFORM, (VibrationEffect.Waveform) effect);
+ } else if (effect instanceof VibrationEffect.Prebaked) {
+ dumpEffect(proto, VibrationEffectProto.PREBAKED, (VibrationEffect.Prebaked) effect);
+ } else if (effect instanceof VibrationEffect.Composed) {
+ dumpEffect(proto, VibrationEffectProto.COMPOSED, (VibrationEffect.Composed) effect);
+ }
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId,
+ VibrationEffect.OneShot effect) {
+ final long token = proto.start(fieldId);
+ proto.write(OneShotProto.DURATION, (int) effect.getDuration());
+ proto.write(OneShotProto.AMPLITUDE, effect.getAmplitude());
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId,
+ VibrationEffect.Waveform effect) {
+ final long token = proto.start(fieldId);
+ for (long timing : effect.getTimings()) {
+ proto.write(WaveformProto.TIMINGS, (int) timing);
+ }
+ for (int amplitude : effect.getAmplitudes()) {
+ proto.write(WaveformProto.AMPLITUDES, amplitude);
+ }
+ proto.write(WaveformProto.REPEAT, effect.getRepeatIndex() >= 0);
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId,
+ VibrationEffect.Prebaked effect) {
+ final long token = proto.start(fieldId);
+ proto.write(PrebakedProto.EFFECT_ID, effect.getId());
+ proto.write(PrebakedProto.EFFECT_STRENGTH, effect.getEffectStrength());
+ proto.write(PrebakedProto.FALLBACK, effect.shouldFallback());
+ proto.end(token);
+ }
+
+ private void dumpEffect(ProtoOutputStream proto, long fieldId,
+ VibrationEffect.Composed effect) {
+ final long token = proto.start(fieldId);
+ for (PrimitiveEffect primitive : effect.getPrimitiveEffects()) {
+ proto.write(ComposedProto.EFFECT_IDS, primitive.id);
+ proto.write(ComposedProto.EFFECT_SCALES, primitive.scale);
+ proto.write(ComposedProto.DELAYS, primitive.delay);
+ }
+ proto.end(token);
}
}
@@ -385,15 +541,7 @@ public class VibratorService extends IVibratorService.Stub
mNativeWrapper = injector.getNativeWrapper();
mH = injector.createHandler(Looper.myLooper());
- long nativeServicePtr = mNativeWrapper.vibratorInit(this::onVibrationComplete);
- long finalizerPtr = mNativeWrapper.vibratorGetFinalizer();
-
- if (finalizerPtr != 0) {
- NativeAllocationRegistry registry =
- NativeAllocationRegistry.createMalloced(
- VibratorService.class.getClassLoader(), finalizerPtr);
- registry.registerNativeAllocation(this, nativeServicePtr);
- }
+ mNativeWrapper.vibratorInit(this::onVibrationComplete);
// Reset the hardware to a default state, in case this is a runtime
// restart instead of a fresh boot.
@@ -557,7 +705,7 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.d(TAG, "Vibration finished by callback, cleaning up");
}
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.FINISHED);
}
}
}
@@ -800,6 +948,7 @@ public class VibratorService extends IVibratorService.Stub
}
attrs = fixupVibrationAttributes(attrs);
+ Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
@@ -816,6 +965,7 @@ public class VibratorService extends IVibratorService.Stub
Slog.d(TAG,
"Ignoring incoming vibration in favor of current vibration");
}
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_ONGOING);
return;
}
}
@@ -827,6 +977,7 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
}
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_EXTERNAL);
return;
}
@@ -840,24 +991,29 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
}
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_ALARM);
return;
}
- Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
> ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
&& !vib.isNotification() && !vib.isRingtone() && !vib.isAlarm()) {
Slog.e(TAG, "Ignoring incoming vibration as process with"
+ " uid= " + uid + " is background,"
+ " attrs= " + vib.attrs);
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_BACKGROUND);
return;
}
linkVibration(vib);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
startVibrationLocked(vib);
- addToPreviousVibrationsLocked(vib);
+
+ if (!vib.hasEnded() && mCurrentVibration.id != vib.id) {
+ // Vibration was unexpectedly ignored: add to list for debugging
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED);
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -876,7 +1032,7 @@ public class VibratorService extends IVibratorService.Stub
return effect.getDuration() == Long.MAX_VALUE;
}
- private void addToPreviousVibrationsLocked(Vibration vib) {
+ private void endVibrationLocked(Vibration vib, VibrationInfo.Status status) {
final LinkedList<VibrationInfo> previousVibrations;
if (vib.isRingtone()) {
previousVibrations = mPreviousRingVibrations;
@@ -891,9 +1047,18 @@ public class VibratorService extends IVibratorService.Stub
if (previousVibrations.size() > mPreviousVibrationsLimit) {
previousVibrations.removeFirst();
}
+ vib.end(status);
previousVibrations.addLast(vib.toInfo());
}
+ private void endVibrationLocked(ExternalVibrationHolder vib, VibrationInfo.Status status) {
+ if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
+ mPreviousExternalVibrations.removeFirst();
+ }
+ vib.end(status);
+ mPreviousExternalVibrations.addLast(vib.toInfo());
+ }
+
@Override // Binder call
public void cancelVibrate(IBinder token) {
mContext.enforceCallingOrSelfPermission(
@@ -905,9 +1070,9 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.d(TAG, "Canceling vibration.");
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -916,7 +1081,7 @@ public class VibratorService extends IVibratorService.Stub
}
@GuardedBy("mLock")
- private void doCancelVibrateLocked() {
+ private void doCancelVibrateLocked(VibrationInfo.Status status) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked");
try {
@@ -925,12 +1090,13 @@ public class VibratorService extends IVibratorService.Stub
mThread = null;
}
if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.mute();
+ endVibrationLocked(mCurrentExternalVibration, status);
+ mCurrentExternalVibration.externalVibration.mute();
mCurrentExternalVibration = null;
setVibratorUnderExternalControl(false);
}
doVibratorOff();
- reportFinishVibrationLocked();
+ reportFinishVibrationLocked(status);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -944,7 +1110,7 @@ public class VibratorService extends IVibratorService.Stub
synchronized (mLock) {
// Make sure the vibration is really done. This also reports that the vibration is
// finished.
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.FINISHED);
}
}
@@ -952,7 +1118,7 @@ public class VibratorService extends IVibratorService.Stub
private void startVibrationLocked(final Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- final int intensity = getCurrentIntensityLocked(vib);
+ final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage());
if (!shouldVibrate(vib, intensity)) {
return;
}
@@ -967,32 +1133,34 @@ public class VibratorService extends IVibratorService.Stub
private void startVibrationInnerLocked(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
try {
+ // Set current vibration before starting it, so callback will work.
mCurrentVibration = vib;
if (vib.effect instanceof VibrationEffect.OneShot) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
- VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
- doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib);
+ doVibratorOn(vib);
} else if (vib.effect instanceof VibrationEffect.Waveform) {
// mThread better be null here. doCancelVibrate should always be
// called before startNextVibrationLocked or startVibrationLocked.
- VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
- mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
+ mThread = new VibrateWaveformThread(vib);
mThread.start();
} else if (vib.effect instanceof VibrationEffect.Prebaked) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorPrebakedEffectLocked(vib);
- } else if (vib.effect instanceof VibrationEffect.Composed) {
+ } else if (vib.effect instanceof VibrationEffect.Composed) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
doVibratorComposedEffectLocked(vib);
} else {
Slog.e(TAG, "Unknown vibration type, ignoring");
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNKNOWN_VIBRATION);
+ // The set current vibration is not actually playing, so drop it.
+ mCurrentVibration = null;
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
}
- private boolean isAllowedToVibrateLocked(Vibration vib) {
+ private boolean shouldVibrateForPowerModeLocked(Vibration vib) {
if (!mLowPowerMode) {
return true;
}
@@ -1003,14 +1171,28 @@ public class VibratorService extends IVibratorService.Stub
|| usage == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
}
- private int getCurrentIntensityLocked(Vibration vib) {
- if (vib.isRingtone()) {
+ private int getCurrentIntensityLocked(int usageHint) {
+ if (isRingtone(usageHint)) {
return mRingIntensity;
- } else if (vib.isNotification()) {
+ } else if (isNotification(usageHint)) {
return mNotificationIntensity;
- } else if (vib.isHapticFeedback()) {
+ } else if (isHapticFeedback(usageHint)) {
return mHapticFeedbackIntensity;
- } else if (vib.isAlarm()) {
+ } else if (isAlarm(usageHint)) {
+ return Vibrator.VIBRATION_INTENSITY_HIGH;
+ } else {
+ return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+ }
+ }
+
+ private int getDefaultIntensity(int usageHint) {
+ if (isRingtone(usageHint)) {
+ return mVibrator.getDefaultRingVibrationIntensity();
+ } else if (isNotification(usageHint)) {
+ return mVibrator.getDefaultNotificationVibrationIntensity();
+ } else if (isHapticFeedback(usageHint)) {
+ return mVibrator.getDefaultHapticFeedbackIntensity();
+ } else if (isAlarm(usageHint)) {
return Vibrator.VIBRATION_INTENSITY_HIGH;
} else {
return Vibrator.VIBRATION_INTENSITY_MEDIUM;
@@ -1029,21 +1211,7 @@ public class VibratorService extends IVibratorService.Stub
return;
}
- final int defaultIntensity;
- if (vib.isRingtone()) {
- defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
- } else if (vib.isNotification()) {
- defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
- } else if (vib.isHapticFeedback()) {
- defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
- } else if (vib.isAlarm()) {
- defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
- } else {
- // If we don't know what kind of vibration we're playing then just skip scaling for
- // now.
- return;
- }
-
+ final int defaultIntensity = getDefaultIntensity(vib.attrs.getUsage());
final ScaleLevel scale = mScaleLevels.get(intensity - defaultIntensity);
if (scale == null) {
// We should have scaling levels for all cases, so not being able to scale because of a
@@ -1055,6 +1223,7 @@ public class VibratorService extends IVibratorService.Stub
vib.originalEffect = vib.effect;
vib.effect = vib.effect.resolve(mDefaultVibrationAmplitude).scale(scale.factor);
+ vib.scale = scale.factor;
}
private boolean shouldVibrateForRingtone() {
@@ -1078,7 +1247,7 @@ public class VibratorService extends IVibratorService.Stub
private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
- attrs.getAudioAttributes().getUsage(), uid, packageName);
+ attrs.getAudioUsage(), uid, packageName);
if (mode == AppOpsManager.MODE_ALLOWED) {
mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName);
}
@@ -1094,11 +1263,13 @@ public class VibratorService extends IVibratorService.Stub
}
private boolean shouldVibrate(Vibration vib, int intensity) {
- if (!isAllowedToVibrateLocked(vib)) {
+ if (!shouldVibrateForPowerModeLocked(vib)) {
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_POWER);
return false;
}
if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_FOR_SETTINGS);
return false;
}
@@ -1106,6 +1277,7 @@ public class VibratorService extends IVibratorService.Stub
if (DEBUG) {
Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
}
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_RINGTONE);
return false;
}
@@ -1115,6 +1287,9 @@ public class VibratorService extends IVibratorService.Stub
// We might be getting calls from within system_server, so we don't actually
// want to throw a SecurityException here.
Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+ endVibrationLocked(vib, VibrationInfo.Status.ERROR_APP_OPS);
+ } else {
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_APP_OPS);
}
return false;
}
@@ -1123,10 +1298,11 @@ public class VibratorService extends IVibratorService.Stub
}
@GuardedBy("mLock")
- private void reportFinishVibrationLocked() {
+ private void reportFinishVibrationLocked(VibrationInfo.Status status) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
try {
if (mCurrentVibration != null) {
+ endVibrationLocked(mCurrentVibration, status);
mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
mCurrentVibration.opPkg);
unlinkVibration(mCurrentVibration);
@@ -1163,7 +1339,7 @@ public class VibratorService extends IVibratorService.Stub
if (devicesUpdated || lowPowerModeUpdated) {
// If the state changes out from under us then just reset.
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
}
updateAlwaysOnLocked();
@@ -1234,7 +1410,7 @@ public class VibratorService extends IVibratorService.Stub
}
private void updateAlwaysOnLocked(int id, Vibration vib) {
- final int intensity = getCurrentIntensityLocked(vib);
+ final int intensity = getCurrentIntensityLocked(vib.attrs.getUsage());
if (!shouldVibrate(vib, intensity)) {
mNativeWrapper.vibratorAlwaysOnDisable(id);
} else {
@@ -1279,41 +1455,29 @@ public class VibratorService extends IVibratorService.Stub
return mNativeWrapper.vibratorExists();
}
- /** Vibrates with native callback trigger for {@link #onVibrationComplete(long)}. */
- private void doVibratorOn(long millis, int amplitude, Vibration vib) {
- doVibratorOn(millis, amplitude, vib.uid, vib.attrs, vib.id);
- }
-
- /** Vibrates without native callback. */
- private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
- doVibratorOn(millis, amplitude, uid, attrs, /* vibrationId= */ 0);
- }
-
- private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs,
- long vibrationId) {
+ private void doVibratorOn(Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
- if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
- amplitude = mDefaultVibrationAmplitude;
- }
+ final VibrationEffect.OneShot oneShot =
+ (VibrationEffect.OneShot) vib.effect.resolve(mDefaultVibrationAmplitude);
if (DEBUG) {
- Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
- " with amplitude " + amplitude + ".");
+ Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms"
+ + " with amplitude " + oneShot.getAmplitude() + ".");
}
- noteVibratorOnLocked(uid, millis);
+ noteVibratorOnLocked(vib.uid, oneShot.getDuration());
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
- Vibrator inputDeviceVibrator = mInputDeviceVibrators.get(i);
- inputDeviceVibrator.vibrate(millis, attrs.getAudioAttributes());
+ mInputDeviceVibrators.get(i).vibrate(vib.uid, vib.opPkg, oneShot,
+ vib.reason, vib.attrs);
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
// amplitude when enabled, so we always have to enable first, then set the
// amplitude.
- mNativeWrapper.vibratorOn(millis, vibrationId);
- doVibratorSetAmplitude(amplitude);
+ mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id);
+ doVibratorSetAmplitude(oneShot.getAmplitude());
}
}
} finally {
@@ -1367,6 +1531,10 @@ public class VibratorService extends IVibratorService.Stub
return;
}
}
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
+ // The set current vibration is not actually playing, so drop it.
+ mCurrentVibration = null;
+
if (!prebaked.shouldFallback()) {
return;
}
@@ -1377,11 +1545,12 @@ public class VibratorService extends IVibratorService.Stub
}
Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
vib.opPkg, vib.reason + " (fallback)");
- final int intensity = getCurrentIntensityLocked(fallbackVib);
+ // Set current vibration before starting it, so callback will work.
+ mCurrentVibration = fallbackVib;
+ final int intensity = getCurrentIntensityLocked(fallbackVib.attrs.getUsage());
linkVibration(fallbackVib);
applyVibrationIntensityScalingLocked(fallbackVib, intensity);
startVibrationInnerLocked(fallbackVib);
- return;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1398,11 +1567,10 @@ public class VibratorService extends IVibratorService.Stub
usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
}
// Input devices don't support composed effect, so skip trying it with them.
- if (usingInputDeviceVibrators) {
- return;
- }
-
- if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
+ endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
+ // The set current vibration is not actually playing, so drop it.
+ mCurrentVibration = null;
return;
}
@@ -1511,15 +1679,25 @@ public class VibratorService extends IVibratorService.Stub
} else {
pw.println("null");
}
- pw.print(" mCurrentExternalVibration=" + mCurrentExternalVibration);
+ pw.print(" mCurrentExternalVibration=");
+ if (mCurrentExternalVibration != null) {
+ pw.println(mCurrentExternalVibration.toInfo().toString());
+ } else {
+ pw.println("null");
+ }
pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
pw.println(" mIsVibrating=" + mIsVibrating);
- pw.println(" mVibratorStateListeners Count=" +
- mVibratorStateListeners.getRegisteredCallbackCount());
+ pw.println(" mVibratorStateListeners Count="
+ + mVibratorStateListeners.getRegisteredCallbackCount());
pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
+ pw.println(" mHapticFeedbackDefaultIntensity="
+ + mVibrator.getDefaultHapticFeedbackIntensity());
pw.println(" mNotificationIntensity=" + mNotificationIntensity);
+ pw.println(" mNotificationDefaultIntensity="
+ + mVibrator.getDefaultNotificationVibrationIntensity());
pw.println(" mRingIntensity=" + mRingIntensity);
+ pw.println(" mRingDefaultIntensity=" + mVibrator.getDefaultRingVibrationIntensity());
pw.println(" mSupportedEffects=" + mSupportedEffects);
pw.println(" mSupportedPrimitives=" + mSupportedPrimitives);
pw.println();
@@ -1545,8 +1723,8 @@ public class VibratorService extends IVibratorService.Stub
}
pw.println(" Previous external vibrations:");
- for (ExternalVibration vib : mPreviousExternalVibrations) {
- pw.println(" " + vib);
+ for (VibrationInfo info : mPreviousExternalVibrations) {
+ pw.println(" " + info);
}
}
}
@@ -1557,54 +1735,62 @@ public class VibratorService extends IVibratorService.Stub
synchronized (mLock) {
if (mCurrentVibration != null) {
mCurrentVibration.toInfo().dumpProto(proto,
- VibratorServiceDumpProto.CURRENT_VIBRATION);
+ VibratorServiceDumpProto.CURRENT_VIBRATION);
+ }
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.toInfo().dumpProto(proto,
+ VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
proto.write(VibratorServiceDumpProto.IS_VIBRATING, mIsVibrating);
proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- mVibratorUnderExternalControl);
+ mVibratorUnderExternalControl);
proto.write(VibratorServiceDumpProto.LOW_POWER_MODE, mLowPowerMode);
proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
- mHapticFeedbackIntensity);
- proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
- mNotificationIntensity);
+ mHapticFeedbackIntensity);
+ proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+ mVibrator.getDefaultHapticFeedbackIntensity());
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity);
+ proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+ mVibrator.getDefaultNotificationVibrationIntensity());
proto.write(VibratorServiceDumpProto.RING_INTENSITY, mRingIntensity);
+ proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+ mVibrator.getDefaultRingVibrationIntensity());
for (VibrationInfo info : mPreviousRingVibrations) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
}
for (VibrationInfo info : mPreviousNotificationVibrations) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
}
for (VibrationInfo info : mPreviousAlarmVibrations) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
}
for (VibrationInfo info : mPreviousVibrations) {
- info.dumpProto(proto,
- VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+ }
+
+ for (VibrationInfo info : mPreviousExternalVibrations) {
+ info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
}
}
proto.flush();
}
/** Thread that plays a single {@link VibrationEffect.Waveform}. */
- private class VibrateThread extends Thread {
+ private class VibrateWaveformThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
- private final int mUid;
- private final VibrationAttributes mAttrs;
+ private final Vibration mVibration;
private boolean mForceStop;
- VibrateThread(VibrationEffect.Waveform waveform, int uid, VibrationAttributes attrs) {
- mWaveform = waveform;
- mUid = uid;
- mAttrs = attrs;
- mTmpWorkSource.set(uid);
+ VibrateWaveformThread(Vibration vib) {
+ mWaveform = (VibrationEffect.Waveform) vib.effect;
+ mVibration = new Vibration(vib.token, /* effect= */ null, vib.attrs, vib.uid,
+ vib.opPkg, vib.reason);
+ mTmpWorkSource.set(vib.uid);
mWakeLock.setWorkSource(mTmpWorkSource);
}
@@ -1678,7 +1864,9 @@ public class VibratorService extends IVibratorService.Stub
// appropriate intervals.
onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
repeat);
- doVibratorOn(onDuration, amplitude, mUid, mAttrs);
+ mVibration.effect = VibrationEffect.createOneShot(
+ onDuration, amplitude);
+ doVibratorOn(mVibration);
} else {
doVibratorSetAmplitude(amplitude);
}
@@ -1746,18 +1934,17 @@ public class VibratorService extends IVibratorService.Stub
return VibratorService.vibratorExists(mNativeServicePtr);
}
- /**
- * Returns native pointer to newly created controller and initializes connection to vibrator
- * HAL service.
- */
- public long vibratorInit(OnCompleteListener listener) {
+ /** Initializes connection to vibrator HAL service. */
+ public void vibratorInit(OnCompleteListener listener) {
mNativeServicePtr = VibratorService.vibratorInit(listener);
- return mNativeServicePtr;
- }
+ long finalizerPtr = VibratorService.vibratorGetFinalizer();
- /** Returns pointer to native finalizer function to be called by GC. */
- public long vibratorGetFinalizer() {
- return VibratorService.vibratorGetFinalizer();
+ if (finalizerPtr != 0) {
+ NativeAllocationRegistry registry =
+ NativeAllocationRegistry.createMalloced(
+ VibratorService.class.getClassLoader(), finalizerPtr);
+ registry.registerNativeAllocation(this, mNativeServicePtr);
+ }
}
/** Turns vibrator on for given time. */
@@ -1849,7 +2036,7 @@ public class VibratorService extends IVibratorService.Stub
if (mCurrentVibration != null
&& !(mCurrentVibration.isHapticFeedback()
&& mCurrentVibration.isFromSystem())) {
- doCancelVibrateLocked();
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
}
}
}
@@ -1904,63 +2091,54 @@ public class VibratorService extends IVibratorService.Stub
int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
if (mode != AppOpsManager.MODE_ALLOWED) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = SCALE_MUTE;
if (mode == AppOpsManager.MODE_ERRORED) {
Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
+ endVibrationLocked(vibHolder, VibrationInfo.Status.ERROR_APP_OPS);
+ } else {
+ endVibrationLocked(vibHolder, VibrationInfo.Status.IGNORED_APP_OPS);
}
return SCALE_MUTE;
}
final int scaleLevel;
synchronized (mLock) {
- if (!vib.equals(mCurrentExternalVibration)) {
- if (mCurrentExternalVibration == null) {
- // If we're not under external control right now, then cancel any normal
- // vibration that may be playing and ready the vibrator for external
- // control.
- doCancelVibrateLocked();
- setVibratorUnderExternalControl(true);
- }
- // At this point we either have an externally controlled vibration playing, or
- // no vibration playing. Since the interface defines that only one externally
- // controlled vibration can play at a time, by returning something other than
- // SCALE_MUTE from this function we can be assured that if we are currently
- // playing vibration, it will be muted in favor of the new vibration.
- //
- // Note that this doesn't support multiple concurrent external controls, as we
- // would need to mute the old one still if it came from a different controller.
- mCurrentExternalVibration = vib;
- mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
- mCurrentExternalVibration.linkToDeath(mCurrentExternalDeathRecipient);
- if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) {
- mPreviousExternalVibrations.removeFirst();
- }
- mPreviousExternalVibrations.addLast(vib);
- if (DEBUG) {
- Slog.e(TAG, "Playing external vibration: " + vib);
- }
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ // We are already playing this external vibration, so we can return the same
+ // scale calculated in the previous call to this method.
+ return mCurrentExternalVibration.scale;
}
- final int usage = vib.getVibrationAttributes().getUsage();
- final int defaultIntensity;
- final int currentIntensity;
- if (isRingtone(usage)) {
- defaultIntensity = mVibrator.getDefaultRingVibrationIntensity();
- currentIntensity = mRingIntensity;
- } else if (isNotification(usage)) {
- defaultIntensity = mVibrator.getDefaultNotificationVibrationIntensity();
- currentIntensity = mNotificationIntensity;
- } else if (isHapticFeedback(usage)) {
- defaultIntensity = mVibrator.getDefaultHapticFeedbackIntensity();
- currentIntensity = mHapticFeedbackIntensity;
- } else if (isAlarm(usage)) {
- defaultIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
- currentIntensity = Vibrator.VIBRATION_INTENSITY_HIGH;
+ if (mCurrentExternalVibration == null) {
+ // If we're not under external control right now, then cancel any normal
+ // vibration that may be playing and ready the vibrator for external control.
+ doCancelVibrateLocked(VibrationInfo.Status.CANCELLED);
+ setVibratorUnderExternalControl(true);
} else {
- defaultIntensity = 0;
- currentIntensity = 0;
+ endVibrationLocked(mCurrentExternalVibration, VibrationInfo.Status.CANCELLED);
+ }
+ // At this point we either have an externally controlled vibration playing, or
+ // no vibration playing. Since the interface defines that only one externally
+ // controlled vibration can play at a time, by returning something other than
+ // SCALE_MUTE from this function we can be assured that if we are currently
+ // playing vibration, it will be muted in favor of the new vibration.
+ //
+ // Note that this doesn't support multiple concurrent external controls, as we
+ // would need to mute the old one still if it came from a different controller.
+ mCurrentExternalVibration = new ExternalVibrationHolder(vib);
+ mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
+ vib.linkToDeath(mCurrentExternalDeathRecipient);
+ if (DEBUG) {
+ Slog.e(TAG, "Playing external vibration: " + vib);
}
+ int usage = vib.getVibrationAttributes().getUsage();
+ int defaultIntensity = getDefaultIntensity(usage);
+ int currentIntensity = getCurrentIntensityLocked(usage);
scaleLevel = currentIntensity - defaultIntensity;
}
if (scaleLevel >= SCALE_VERY_LOW && scaleLevel <= SCALE_VERY_HIGH) {
+ mCurrentExternalVibration.scale = scaleLevel;
return scaleLevel;
} else {
// Presumably we want to play this but something about our scaling has gone
@@ -1974,22 +2152,42 @@ public class VibratorService extends IVibratorService.Stub
@Override
public void onExternalVibrationStop(ExternalVibration vib) {
synchronized (mLock) {
- if (vib.equals(mCurrentExternalVibration)) {
- mCurrentExternalVibration.unlinkToDeath(mCurrentExternalDeathRecipient);
- mCurrentExternalDeathRecipient = null;
- mCurrentExternalVibration = null;
- setVibratorUnderExternalControl(false);
+ if (mCurrentExternalVibration != null
+ && mCurrentExternalVibration.externalVibration.equals(vib)) {
if (DEBUG) {
Slog.e(TAG, "Stopping external vibration" + vib);
}
+ doCancelExternalVibrateLocked(VibrationInfo.Status.FINISHED);
}
}
}
+ private void doCancelExternalVibrateLocked(VibrationInfo.Status status) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked");
+ try {
+ if (mCurrentExternalVibration == null) {
+ return;
+ }
+ endVibrationLocked(mCurrentExternalVibration, status);
+ mCurrentExternalVibration.externalVibration.unlinkToDeath(
+ mCurrentExternalDeathRecipient);
+ mCurrentExternalDeathRecipient = null;
+ mCurrentExternalVibration = null;
+ setVibratorUnderExternalControl(false);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+ }
+ }
+
private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
public void binderDied() {
synchronized (mLock) {
- onExternalVibrationStop(mCurrentExternalVibration);
+ if (mCurrentExternalVibration != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "External vibration finished because binder died");
+ }
+ doCancelExternalVibrateLocked(VibrationInfo.Status.CANCELLED);
+ }
}
}
}
@@ -2251,5 +2449,4 @@ public class VibratorService extends IVibratorService.Stub
}
}
}
-
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 523df104ab03..d98e86e81cea 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -42,7 +42,6 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.BroadcastReceiver;
@@ -478,7 +477,7 @@ public class AccountManagerService
* TODO: Only allow accounts that were shared to be added by a limited user.
*/
// fails if the account already exists
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return addAccountInternal(accounts, account, password, extras, callingUid,
@@ -507,7 +506,7 @@ public class AccountManagerService
managedTypes.add(accountType);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return getAccountsAndVisibilityForPackage(packageName, managedTypes, callingUid,
@@ -556,7 +555,7 @@ public class AccountManagerService
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
synchronized (accounts.dbLock) {
@@ -605,7 +604,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
if (AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE.equals(packageName)) {
@@ -665,7 +664,7 @@ public class AccountManagerService
Objects.requireNonNull(packageName, "packageName cannot be null");
int uid = -1;
try {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
} finally {
@@ -737,7 +736,7 @@ public class AccountManagerService
*/
private boolean isPreOApplication(String packageName) {
try {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
ApplicationInfo applicationInfo;
try {
applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
@@ -770,7 +769,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return setAccountVisibility(account, packageName, newVisibility, true /* notify */,
@@ -884,7 +883,7 @@ public class AccountManagerService
mAppOpsManager.checkPackage(callingUid, opPackageName);
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
registerAccountListener(accountTypes, opPackageName, accounts);
@@ -917,7 +916,7 @@ public class AccountManagerService
int callingUid = Binder.getCallingUid();
mAppOpsManager.checkPackage(callingUid, opPackageName);
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
unregisterAccountListener(accountTypes, opPackageName, accounts);
@@ -1034,7 +1033,7 @@ public class AccountManagerService
private boolean packageExistsForUser(String packageName, int userId) {
try {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
mPackageManager.getPackageUidAsUser(packageName, userId);
return true;
@@ -1501,7 +1500,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return readPasswordInternal(accounts, account);
@@ -1536,7 +1535,7 @@ public class AccountManagerService
}
Objects.requireNonNull(account, "account cannot be null");
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return readPreviousNameInternal(accounts, account);
@@ -1587,7 +1586,7 @@ public class AccountManagerService
Log.w(TAG, "User " + userId + " data is locked. callingUid " + callingUid);
return null;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
if (!accountExistsCache(accounts, account)) {
@@ -1682,7 +1681,7 @@ public class AccountManagerService
Slog.d(TAG, "Copying account " + account.toSafeString()
+ " from user " + userFrom + " to user " + userTo);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
new Session(fromAccounts, response, account.type, false,
false /* stripAuthTokenFromResult */, account.name,
@@ -1740,7 +1739,7 @@ public class AccountManagerService
return false;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return updateLastAuthenticatedTime(account);
@@ -1762,7 +1761,7 @@ public class AccountManagerService
final Bundle accountCredentials, final Account account, final UserAccounts targetUser,
final int parentUserId){
Bundle.setDefusable(accountCredentials, true);
- long id = clearCallingIdentity();
+ final long id = clearCallingIdentity();
try {
new Session(targetUser, response, account.type, false,
false /* stripAuthTokenFromResult */, account.name,
@@ -1929,7 +1928,7 @@ public class AccountManagerService
checkReadAccountsPermitted(callingUid, account.type, userId,
opPackageName);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new TestFeaturesSession(accounts, response, account, features).bind();
@@ -2013,7 +2012,7 @@ public class AccountManagerService
accountToRename.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
@@ -2196,7 +2195,7 @@ public class AccountManagerService
}
return;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
UserAccounts accounts = getUserAccounts(userId);
cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
synchronized(accounts.credentialsPermissionNotificationIds) {
@@ -2253,7 +2252,7 @@ public class AccountManagerService
accountId,
accounts,
callingUid);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
return removeAccountInternal(accounts, account, callingUid);
} finally {
@@ -2370,7 +2369,7 @@ public class AccountManagerService
}
}
}
- long id = Binder.clearCallingIdentity();
+ final long id = Binder.clearCallingIdentity();
try {
int parentUserId = accounts.userId;
if (canHaveProfile(parentUserId)) {
@@ -2416,7 +2415,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
List<Pair<Account, String>> deletedTokens;
@@ -2540,7 +2539,7 @@ public class AccountManagerService
+ callingUid);
return null;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return readAuthTokenInternal(accounts, account, authTokenType);
@@ -2568,7 +2567,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
@@ -2595,7 +2594,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
setPasswordInternal(accounts, account, password, callingUid);
@@ -2662,7 +2661,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
setPasswordInternal(accounts, account, null, callingUid);
@@ -2691,7 +2690,7 @@ public class AccountManagerService
account.type);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
if (!accountExistsCache(accounts, account)) {
@@ -2777,7 +2776,7 @@ public class AccountManagerService
throw new SecurityException("can only call from system");
}
int userId = UserHandle.getUserId(callingUid);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new Session(accounts, response, accountType, false /* expectActivityLaunch */,
@@ -2849,7 +2848,7 @@ public class AccountManagerService
return;
}
int userId = UserHandle.getCallingUserId();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
final UserAccounts accounts;
final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
try {
@@ -2871,11 +2870,11 @@ public class AccountManagerService
// Get the calling package. We will use it for the purpose of caching.
final String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME);
List<String> callerOwnedPackageNames;
- ident = Binder.clearCallingIdentity();
+ final long ident2 = Binder.clearCallingIdentity();
try {
callerOwnedPackageNames = Arrays.asList(mPackageManager.getPackagesForUid(callerUid));
} finally {
- Binder.restoreCallingIdentity(ident);
+ Binder.restoreCallingIdentity(ident2);
}
if (callerPkg == null || !callerOwnedPackageNames.contains(callerPkg)) {
String msg = String.format(
@@ -2893,7 +2892,7 @@ public class AccountManagerService
loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
// Distill the caller's package signatures into a single digest.
final byte[] callerPkgSigDigest = calculatePackageSignatureDigest(callerPkg);
@@ -3201,7 +3200,7 @@ public class AccountManagerService
options.putInt(AccountManager.KEY_CALLER_PID, pid);
int usrId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(usrId);
logRecordWithUid(
@@ -3282,7 +3281,7 @@ public class AccountManagerService
options.putInt(AccountManager.KEY_CALLER_UID, uid);
options.putInt(AccountManager.KEY_CALLER_PID, pid);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
logRecordWithUid(
@@ -3365,7 +3364,7 @@ public class AccountManagerService
boolean isPasswordForwardingAllowed = checkPermissionAndNote(
callerPkg, uid, Manifest.permission.GET_PASSWORD);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
logRecordWithUid(accounts, AccountsDb.DEBUG_ACTION_CALLED_START_ACCOUNT_ADD,
@@ -3606,7 +3605,7 @@ public class AccountManagerService
return;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
logRecordWithUid(
@@ -3655,7 +3654,7 @@ public class AccountManagerService
if (intent == null) {
intent = getDefaultCantAddAccountIntent(errorCode);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
mContext.startActivityAsUser(intent, new UserHandle(userId));
} finally {
@@ -3699,7 +3698,7 @@ public class AccountManagerService
}
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new Session(accounts, response, account.type, expectActivityLaunch,
@@ -3736,7 +3735,7 @@ public class AccountManagerService
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new Session(accounts, response, account.type, expectActivityLaunch,
@@ -3790,7 +3789,7 @@ public class AccountManagerService
boolean isPasswordForwardingAllowed = checkPermissionAndNote(
callerPkg, uid, Manifest.permission.GET_PASSWORD);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new StartAccountSession(
@@ -3846,7 +3845,7 @@ public class AccountManagerService
}
int usrId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(usrId);
new Session(accounts, response, account.type, false /* expectActivityLaunch */,
@@ -3932,7 +3931,7 @@ public class AccountManagerService
accountType);
throw new SecurityException(msg);
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
new Session(accounts, response, accountType, expectActivityLaunch,
@@ -4236,7 +4235,7 @@ public class AccountManagerService
if (visibleAccountTypes.isEmpty()) {
return EMPTY_ACCOUNT_ARRAY;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return getAccountsInternal(
@@ -4360,7 +4359,7 @@ public class AccountManagerService
} // else aggregate all the visible accounts (it won't matter if the
// list is empty).
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return getAccountsInternal(
@@ -4566,7 +4565,7 @@ public class AccountManagerService
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts userAccounts = getUserAccounts(userId);
if (ArrayUtils.isEmpty(features)) {
@@ -4644,7 +4643,7 @@ public class AccountManagerService
return;
}
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
UserAccounts userAccounts = getUserAccounts(userId);
if (features == null || features.length == 0) {
@@ -4789,7 +4788,7 @@ public class AccountManagerService
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
- long bid = Binder.clearCallingIdentity();
+ final long bid = Binder.clearCallingIdentity();
try {
PackageManager pm = mContext.getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
@@ -5289,7 +5288,7 @@ public class AccountManagerService
private void doNotification(UserAccounts accounts, Account account, CharSequence message,
Intent intent, String packageName, final int userId) {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "doNotification: " + message + " intent:" + intent);
@@ -5348,7 +5347,7 @@ public class AccountManagerService
}
private void cancelNotification(NotificationId id, String packageName, UserHandle user) {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
INotificationManager service = mInjector.getNotificationManager();
service.cancelNotificationWithTag(
@@ -5417,7 +5416,7 @@ public class AccountManagerService
private boolean isPrivileged(int callingUid) {
String[] packages;
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
try {
packages = mPackageManager.getPackagesForUid(callingUid);
if (packages == null) {
@@ -5509,7 +5508,7 @@ public class AccountManagerService
if (accountType == null) {
return false;
}
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
try {
serviceInfos = mAuthenticatorCache.getAllServices(userId);
@@ -5538,7 +5537,7 @@ public class AccountManagerService
return SIGNATURE_CHECK_MISMATCH;
}
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
try {
serviceInfos = mAuthenticatorCache.getAllServices(userId);
@@ -5584,7 +5583,7 @@ public class AccountManagerService
private List<String> getTypesForCaller(
int callingUid, int userId, boolean isOtherwisePermitted) {
List<String> managedAccountTypes = new ArrayList<>();
- long identityToken = Binder.clearCallingIdentity();
+ final long identityToken = Binder.clearCallingIdentity();
Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos;
try {
serviceInfos = mAuthenticatorCache.getAllServices(userId);
@@ -5667,7 +5666,7 @@ public class AccountManagerService
private boolean isSystemUid(int callingUid) {
String[] packages = null;
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
packages = mPackageManager.getPackagesForUid(callingUid);
if (packages != null) {
@@ -5743,8 +5742,8 @@ public class AccountManagerService
private boolean isProfileOwner(int uid) {
final DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
- return (dpmi != null)
- && dpmi.isActiveAdminWithPolicy(uid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
+ return (dpmi != null) && (dpmi.isActiveProfileOwner(uid) || dpmi.isActiveDeviceOwner(uid));
}
@Override
@@ -6196,7 +6195,7 @@ public class AccountManagerService
final int uid;
try {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
uid = mPackageManager.getPackageUidAsUser(packageName, userId);
} finally {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 84d4b7f50985..f0cc60ca5df0 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -25,6 +25,7 @@ import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICE_BG_LAUNCH;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
@@ -121,6 +122,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -242,6 +244,10 @@ public final class ActiveServices {
// white listed packageName.
ArraySet<String> mWhiteListAllowWhileInUsePermissionInFgs = new ArraySet<>();
+ // TODO: remove this after feature development is done
+ private static final SimpleDateFormat DATE_FORMATTER =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
final Runnable mLastAnrDumpClearer = new Runnable() {
@Override public void run() {
synchronized (mAm) {
@@ -513,17 +519,18 @@ public final class ActiveServices {
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
- int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
- String callingPackage, @Nullable String callingFeatureId, final int userId)
+ int callingPid, int callingUid, boolean fgRequired, String callingPackage,
+ @Nullable String callingFeatureId, final int userId)
throws TransactionTooLargeException {
return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired,
- hideFgNotification, callingPackage, callingFeatureId, userId, false);
+ callingPackage, callingFeatureId, userId, false, null);
}
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
- int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification,
+ int callingPid, int callingUid, boolean fgRequired,
String callingPackage, @Nullable String callingFeatureId, final int userId,
- boolean allowBackgroundActivityStarts) throws TransactionTooLargeException {
+ boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
+ throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -586,10 +593,18 @@ public final class ActiveServices {
}
if (r.mAllowStartForeground == FGS_FEATURE_DENIED
&& mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
- Slog.w(TAG, "startForegroundService() not allowed due to "
- + " mAllowStartForeground false: service "
- + r.shortInstanceName);
- forcedStandby = true;
+ if (mAm.mConstants.mFlagFgsStartTempAllowListEnabled
+ && mAm.isOnDeviceIdleWhitelistLocked(r.appInfo.uid, false)) {
+ // uid is on DeviceIdleController's allowlist.
+ Slog.d(TAG, "startForegroundService() mAllowStartForeground false "
+ + "but allowlist true: service " + r.shortInstanceName);
+ } else {
+ Slog.w(TAG, "startForegroundService() not allowed due to "
+ + "mAllowStartForeground false: service "
+ + r.shortInstanceName);
+ showFgsBgRestrictedNotificationLocked(r);
+ return null;
+ }
}
}
}
@@ -687,7 +702,6 @@ public final class ActiveServices {
r.startRequested = true;
r.delayedStop = false;
r.fgRequired = fgRequired;
- r.hideFgNotification = hideFgNotification;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants, callingUid));
@@ -768,7 +782,7 @@ public final class ActiveServices {
}
}
if (allowBackgroundActivityStarts) {
- r.allowBgActivityStartsOnServiceStart();
+ r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
return cmp;
@@ -1490,12 +1504,20 @@ public final class ActiveServices {
}
if (r.mAllowStartForeground == FGS_FEATURE_DENIED
&& mAm.mConstants.mFlagFgsStartRestrictionEnabled) {
- Slog.w(TAG,
- "Service.startForeground() not allowed due to "
- + "mAllowStartForeground false: service "
- + r.shortInstanceName);
- updateServiceForegroundLocked(r.app, true);
- ignoreForeground = true;
+ if (mAm.mConstants.mFlagFgsStartTempAllowListEnabled
+ && mAm.isOnDeviceIdleWhitelistLocked(r.appInfo.uid, false)) {
+ // uid is on DeviceIdleController's allowlist.
+ Slog.d(TAG, "Service.startForeground() "
+ + "mAllowStartForeground false but allowlist true: service "
+ + r.shortInstanceName);
+ } else {
+ Slog.w(TAG, "Service.startForeground() not allowed due to "
+ + "mAllowStartForeground false: service "
+ + r.shortInstanceName);
+ showFgsBgRestrictedNotificationLocked(r);
+ updateServiceForegroundLocked(r.app, true);
+ ignoreForeground = true;
+ }
}
}
}
@@ -2691,12 +2713,12 @@ public final class ActiveServices {
private int getAllowMode(Intent service, @Nullable String callingPackage) {
if (callingPackage == null || service.getComponent() == null) {
- return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+ return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
}
if (callingPackage.equals(service.getComponent().getPackageName())) {
- return ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL;
+ return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
} else {
- return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+ return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
}
}
@@ -5242,4 +5264,27 @@ public final class ActiveServices {
&& code != FGS_FEATURE_ALLOWED_BY_UID_VISIBLE;
}
+ // TODO: remove this notification after feature development is done
+ private void showFgsBgRestrictedNotificationLocked(ServiceRecord r) {
+ final Context context = mAm.mContext;
+ final String title = "Foreground Service BG-Launch Restricted";
+ final String content = "App restricted: " + r.mRecentCallingPackage;
+ final long now = System.currentTimeMillis();
+ final String bigText = DATE_FORMATTER.format(now) + " " + r.mInfoAllowStartForeground;
+ final String groupKey = "com.android.fgs-bg-restricted";
+ final Notification.Builder n =
+ new Notification.Builder(context,
+ SystemNotificationChannels.ALERTS)
+ .setGroup(groupKey)
+ .setSmallIcon(R.drawable.stat_sys_vitals)
+ .setWhen(0)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setTicker(title)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setStyle(new Notification.BigTextStyle().bigText(bigText));
+ context.getSystemService(NotificationManager.class).notifyAsUser(Long.toString(now),
+ NOTE_FOREGROUND_SERVICE_BG_LAUNCH, n.build(), UserHandle.ALL);
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ef9797f4102a..b181e768b3c5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -164,6 +164,13 @@ final class ActivityManagerConstants extends ContentObserver {
private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
"default_fgs_starts_restriction_enabled";
+ /**
+ * Default value for mFlagFgsStartTempAllowListEnabled if not explicitly set in
+ * Settings.Global.
+ */
+ private static final String KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED =
+ "default_fgs_starts_temp_allowlist_enabled";
+
// Maximum number of cached processes we will allow.
public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
@@ -324,6 +331,10 @@ final class ActivityManagerConstants extends ContentObserver {
// at all.
volatile boolean mFlagFgsStartRestrictionEnabled = false;
+ // When the foreground service background start restriction is enabled, if the app in
+ // DeviceIdleController's Temp AllowList is allowed to bypass the restriction.
+ volatile boolean mFlagFgsStartTempAllowListEnabled = false;
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -484,6 +495,9 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED:
updateFgsStartsRestriction();
break;
+ case KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED:
+ updateFgsStartsTempAllowList();
+ break;
case KEY_OOMADJ_UPDATE_POLICY:
updateOomAdjUpdatePolicy();
break;
@@ -780,6 +794,13 @@ final class ActivityManagerConstants extends ContentObserver {
/*defaultValue*/ false);
}
+ private void updateFgsStartsTempAllowList() {
+ mFlagFgsStartTempAllowListEnabled = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFAULT_FGS_STARTS_TEMP_ALLOWLIST_ENABLED,
+ /*defaultValue*/ false);
+ }
+
private void updateOomAdjUpdatePolicy() {
OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9a65dda3a600..a4bc8b8d9f8c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -153,7 +153,6 @@ import android.app.ContentProviderHolder;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.IApplicationThread;
-import android.app.IAssistDataReceiver;
import android.app.IInstrumentationWatcher;
import android.app.INotificationManager;
import android.app.IProcessObserver;
@@ -286,7 +285,6 @@ import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
import android.view.Display;
import android.view.Gravity;
-import android.view.IRecentsAnimationRunner;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -1199,10 +1197,6 @@ public class ActivityManagerService extends IActivityManager.Stub
final Injector mInjector;
- /** The package verifier app. */
- private String mPackageVerifier;
- private int mPackageVerifierUid = UserHandle.USER_NULL;
-
static final class ProcessChangeItem {
static final int CHANGE_ACTIVITIES = 1<<0;
static final int CHANGE_FOREGROUND_SERVICES = 1<<1;
@@ -1831,18 +1825,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mService.mBatteryStatsService.systemServicesReady();
mService.mServices.systemServicesReady();
- mService.mPackageVerifier = ArrayUtils.firstOrNull(
- LocalServices.getService(PackageManagerInternal.class).getKnownPackageNames(
- PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM));
- if (mService.mPackageVerifier != null) {
- try {
- mService.mPackageVerifierUid =
- getContext().getPackageManager().getPackageUid(
- mService.mPackageVerifier, UserHandle.USER_SYSTEM);
- } catch (NameNotFoundException e) {
- Slog.wtf(TAG, "Package manager couldn't get package verifier uid", e);
- }
- }
} else if (phase == PHASE_ACTIVITY_MANAGER_READY) {
mService.startBroadcastObservers();
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -2436,8 +2418,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void setFocusedStack(int stackId) {
- mActivityTaskManager.setFocusedStack(stackId);
+ public void setFocusedRootTask(int taskId) {
+ mActivityTaskManager.setFocusedRootTask(taskId);
}
/** Sets the task stack listener that gets callbacks when a task stack changes. */
@@ -2920,18 +2902,6 @@ public class ActivityManagerService extends IActivityManager.Stub
return 1;
}
- @Override
- public void startRecentsActivity(Intent intent, IAssistDataReceiver assistDataReceiver,
- IRecentsAnimationRunner recentsAnimationRunner) {
- mActivityTaskManager.startRecentsActivity(
- intent, assistDataReceiver, recentsAnimationRunner);
- }
-
- @Override
- public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
- mActivityTaskManager.cancelRecentsAnimation(restoreHomeStackPosition);
- }
-
/**
* This is the internal entry point for handling Activity.finish().
*
@@ -3457,7 +3427,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final ApplicationInfo appInfo;
final boolean isInstantApp;
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
@@ -3598,7 +3568,7 @@ public class ActivityManagerService extends IActivityManager.Stub
userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
final int[] userIds = mUserController.expandUserId(userId);
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
for (int targetUserId : userIds) {
@@ -3697,7 +3667,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final int callingPid = Binder.getCallingPid();
userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
@@ -5764,11 +5734,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void removeStack(int stackId) {
- mActivityTaskManager.removeStack(stackId);
- }
-
- @Override
public boolean removeTask(int taskId) {
return mActivityTaskManager.removeTask(taskId);
}
@@ -5804,8 +5769,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
- mActivityTaskManager.moveTaskToStack(taskId, stackId, toTop);
+ public void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop) {
+ mActivityTaskManager.moveTaskToRootTask(taskId, rootTaskId, toTop);
}
@Override
@@ -5815,22 +5780,17 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
- * Moves the top activity in the input stackId to the pinned stack.
+ * Moves the top activity in the input rootTaskId to the pinned root task.
*
- * @param stackId Id of stack to move the top activity to pinned stack.
- * @param bounds Bounds to use for pinned stack.
+ * @param rootTaskId Id of root task to move the top activity to pinned root task.
+ * @param bounds Bounds to use for pinned root task.
*
- * @return True if the top activity of the input stack was successfully moved to the pinned
- * stack.
+ * @return True if the top activity of the input root task was successfully moved to the pinned
+ * root task.
*/
@Override
- public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
- return mActivityTaskManager.moveTopActivityToPinnedStack(stackId, bounds);
- }
-
- @Override
- public void positionTaskInStack(int taskId, int stackId, int position) {
- mActivityTaskManager.positionTaskInStack(taskId, stackId, position);
+ public boolean moveTopActivityToPinnedRootTask(int rootTaskId, Rect bounds) {
+ return mActivityTaskManager.moveTopActivityToPinnedRootTask(rootTaskId, bounds);
}
@Override
@@ -6271,7 +6231,7 @@ public class ActivityManagerService extends IActivityManager.Stub
enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
"setDebugApp()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
// Note that this is not really thread safe if there are multiple
// callers into it at the same time, but that's not a situation we
@@ -6372,7 +6332,7 @@ public class ActivityManagerService extends IActivityManager.Stub
enforceCallingPermission(android.Manifest.permission.SET_ALWAYS_FINISH,
"setAlwaysFinish()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
Settings.Global.putInt(
mContext.getContentResolver(),
@@ -7138,7 +7098,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ android.Manifest.permission.FORCE_STOP_PACKAGES);
}
int callerUid = Binder.getCallingUid();
- long iden = Binder.clearCallingIdentity();
+ final long iden = Binder.clearCallingIdentity();
try {
mProcessList.killProcessesWhenImperceptible(pids, reason, callerUid);
} finally {
@@ -7523,7 +7483,7 @@ public class ActivityManagerService extends IActivityManager.Stub
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(Intent.ACTION_USER_STARTED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -8287,7 +8247,7 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid,
String function) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
int uid = Process.INVALID_UID;
try {
uid = mPackageManagerInt.getPackageUid(packageName,
@@ -8295,12 +8255,10 @@ public class ActivityManagerService extends IActivityManager.Stub
} finally {
Binder.restoreCallingIdentity(identity);
}
- if (uid == Process.INVALID_UID) {
- return Process.INVALID_UID;
- }
+ // If the uid is Process.INVALID_UID, the below 'if' check will be always true
if (UserHandle.getAppId(uid) != UserHandle.getAppId(callingUid)) {
// Requires the DUMP permission if the target package doesn't belong
- // to the caller.
+ // to the caller or it doesn't exist.
enforceCallingPermission(android.Manifest.permission.DUMP, function);
}
return uid;
@@ -8350,7 +8308,7 @@ public class ActivityManagerService extends IActivityManager.Stub
private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority,
- int dumpAppId) {
+ int dumpAppId, boolean dumpProxies) {
ActiveServices.ServiceDumper sdumper;
@@ -8409,7 +8367,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
sdumper.dumpWithClient();
}
- if (dumpPackage == null) {
+ if (dumpPackage == null && dumpProxies) {
// Intentionally dropping the lock for this, because dumpBinderProxies() will make many
// outgoing binder calls to retrieve interface descriptors; while that is system code,
// there is nothing preventing an app from overriding this implementation by talking to
@@ -8549,7 +8507,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
if (useProto) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
@@ -8818,13 +8776,14 @@ public class ActivityManagerService extends IActivityManager.Stub
// dumpEverything() will take the lock when needed, and momentarily drop
// it for dumping client state.
dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
- dumpNormalPriority, dumpAppId);
+ dumpNormalPriority, dumpAppId, true /* dumpProxies */);
} else {
// Take the lock here, so we get a consistent state for the entire dump;
- // dumpEverything() will take the lock as well, but that is fine.
+ // dumpEverything() will take the lock as well, which is fine for everything
+ // except dumping proxies, which can take a long time; exclude them.
synchronized(this) {
dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
- dumpNormalPriority, dumpAppId);
+ dumpNormalPriority, dumpAppId, false /* dumpProxies */);
}
}
}
@@ -11314,10 +11273,10 @@ public class ActivityManagerService extends IActivityManager.Stub
}
long kernelUsed = memInfo.getKernelUsedSizeKb();
final long ionHeap = Debug.getIonHeapsSizeKb();
- if (ionHeap > 0) {
+ final long ionPool = Debug.getIonPoolsSizeKb();
+ if (ionHeap >= 0 && ionPool >= 0) {
final long ionMapped = Debug.getIonMappedSizeKb();
final long ionUnmapped = ionHeap - ionMapped;
- final long ionPool = Debug.getIonPoolsSizeKb();
pw.print(" ION: ");
pw.print(stringifyKBSize(ionHeap + ionPool));
pw.print(" (");
@@ -12114,10 +12073,10 @@ public class ActivityManagerService extends IActivityManager.Stub
memInfoBuilder.append("\n");
long kernelUsed = memInfo.getKernelUsedSizeKb();
final long ionHeap = Debug.getIonHeapsSizeKb();
- if (ionHeap > 0) {
+ final long ionPool = Debug.getIonPoolsSizeKb();
+ if (ionHeap >= 0 && ionPool >= 0) {
final long ionMapped = Debug.getIonMappedSizeKb();
final long ionUnmapped = ionHeap - ionMapped;
- final long ionPool = Debug.getIonPoolsSizeKb();
memInfoBuilder.append(" ION: ");
memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
memInfoBuilder.append("\n");
@@ -12244,6 +12203,7 @@ public class ActivityManagerService extends IActivityManager.Stub
app.setHasClientActivities(false);
mServices.killServicesLocked(app, allowRestart);
+ mPhantomProcessList.onAppDied(app.pid);
boolean restart = false;
@@ -12462,8 +12422,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType, boolean requireForeground, boolean hideForegroundNotification,
- String callingPackage, String callingFeatureId, int userId)
+ String resolvedType, boolean requireForeground, String callingPackage,
+ String callingFeatureId, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
@@ -12475,27 +12435,17 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new IllegalArgumentException("callingPackage cannot be null");
}
- final int callingUid = Binder.getCallingUid();
- if (requireForeground && hideForegroundNotification) {
- if (!UserHandle.isSameApp(callingUid, mPackageVerifierUid)
- || !callingPackage.equals(mPackageVerifier)) {
- throw new IllegalArgumentException(
- "Only the package verifier can hide its foreground service notification");
- }
- Slog.i(TAG, "Foreground service notification hiding requested by " + callingPackage);
- }
-
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
- requireForeground, hideForegroundNotification,
- callingPackage, callingFeatureId, userId);
+ requireForeground, callingPackage, callingFeatureId, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -12848,7 +12798,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- long oldIdent = Binder.clearCallingIdentity();
+ final long oldIdent = Binder.clearCallingIdentity();
try {
IBackupManager bm = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
@@ -13812,6 +13762,11 @@ public class ActivityManagerService extends IActivityManager.Stub
case Intent.ACTION_PRE_BOOT_COMPLETED:
timeoutExempt = true;
break;
+ case Intent.ACTION_PACKAGE_UNSTARTABLE:
+ final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ forceStopPackageLocked(packageName, -1, false, true, true,
+ false, false, userId, "package unstartable");
+ break;
}
if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
@@ -15211,7 +15166,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final int callingPid = Binder.getCallingPid();
userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "makePackageIdle", null);
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
synchronized(this) {
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -15940,6 +15895,11 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public int getPendingIntentFlags(IIntentSender target) {
+ return mPendingIntentController.getPendingIntentFlags(target);
+ }
+
+ @Override
public void setPendingIntentAllowBgActivityStarts(IIntentSender target,
IBinder whitelistToken, int flags) {
if (!(target instanceof PendingIntentRecord)) {
@@ -16415,8 +16375,9 @@ public class ActivityManagerService extends IActivityManager.Stub
ComponentName res;
try {
res = mServices.startServiceLocked(null, service,
- resolvedType, -1, uid, fgRequired, false, callingPackage,
- callingFeatureId, userId, allowBackgroundActivityStarts);
+ resolvedType, -1, uid, fgRequired, callingPackage,
+ callingFeatureId, userId, allowBackgroundActivityStarts,
+ backgroundActivityStartsToken);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -16626,7 +16587,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public int getStorageMountMode(int pid, int uid) {
if (uid == SHELL_UID || uid == ROOT_UID) {
- return Zygote.MOUNT_EXTERNAL_FULL;
+ return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
synchronized (mPidsSelfLocked) {
final ProcessRecord pr = mPidsSelfLocked.get(pid);
@@ -16907,7 +16868,7 @@ public class ActivityManagerService extends IActivityManager.Stub
"Cannot kill the dependents of a package without its name.");
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
IPackageManager pm = AppGlobals.getPackageManager();
int pkgUid = -1;
try {
@@ -17468,4 +17429,18 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new SecurityException("Caller uid " + callerUid + " cannot set freezer state ");
}
}
+
+ /**
+ * Holds the AM lock for the specified amount of milliseconds.
+ * Intended for use by the tests that need to imitate lock contention.
+ * Requires permission identity of the shell UID.
+ */
+ @Override
+ public void holdLock(int durationMs) {
+ enforceCallingPermission(Manifest.permission.INJECT_EVENTS, "holdLock");
+
+ synchronized (this) {
+ SystemClock.sleep(durationMs);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 9ce8b11a7351..31e7106979f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -655,7 +655,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println("Starting service: " + intent);
pw.flush();
ComponentName cn = mInterface.startService(null, intent, intent.getType(),
- asForeground, false, SHELL_PACKAGE_NAME, null, mUserId);
+ asForeground, SHELL_PACKAGE_NAME, null, mUserId);
if (cn == null) {
err.println("Error: Not found; no service started.");
return -1;
@@ -2582,16 +2582,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
switch (op) {
case "move-task":
return runStackMoveTask(pw);
- case "positiontask":
- return runStackPositionTask(pw);
case "list":
return runStackList(pw);
case "info":
return runRootTaskInfo(pw);
case "move-top-activity-to-pinned-stack":
- return runMoveTopActivityToPinnedStack(pw);
+ return runMoveTopActivityToPinnedRootTask(pw);
case "remove":
- return runStackRemove(pw);
+ return runRootTaskRemove(pw);
default:
getErrPrintWriter().println("Error: unknown command '" + op + "'");
return -1;
@@ -2628,19 +2626,19 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int runDisplayMoveStack(PrintWriter pw) throws RemoteException {
- String stackIdStr = getNextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
+ String rootTaskIdStr = getNextArgRequired();
+ int rootTaskId = Integer.parseInt(rootTaskIdStr);
String displayIdStr = getNextArgRequired();
int displayId = Integer.parseInt(displayIdStr);
- mTaskInterface.moveStackToDisplay(stackId, displayId);
+ mTaskInterface.moveRootTaskToDisplay(rootTaskId, displayId);
return 0;
}
int runStackMoveTask(PrintWriter pw) throws RemoteException {
String taskIdStr = getNextArgRequired();
int taskId = Integer.parseInt(taskIdStr);
- String stackIdStr = getNextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
+ String rootTaskIdStr = getNextArgRequired();
+ int rootTaskId = Integer.parseInt(rootTaskIdStr);
String toTopStr = getNextArgRequired();
final boolean toTop;
if ("true".equals(toTopStr)) {
@@ -2652,19 +2650,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
return -1;
}
- mTaskInterface.moveTaskToStack(taskId, stackId, toTop);
- return 0;
- }
-
- int runStackPositionTask(PrintWriter pw) throws RemoteException {
- String taskIdStr = getNextArgRequired();
- int taskId = Integer.parseInt(taskIdStr);
- String stackIdStr = getNextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- String positionStr = getNextArgRequired();
- int position = Integer.parseInt(positionStr);
-
- mTaskInterface.positionTaskInStack(taskId, stackId, position);
+ mTaskInterface.moveTaskToRootTask(taskId, rootTaskId, toTop);
return 0;
}
@@ -2684,22 +2670,22 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
- int runStackRemove(PrintWriter pw) throws RemoteException {
- String stackIdStr = getNextArgRequired();
- int stackId = Integer.parseInt(stackIdStr);
- mTaskInterface.removeStack(stackId);
+ int runRootTaskRemove(PrintWriter pw) throws RemoteException {
+ String taskIdStr = getNextArgRequired();
+ int taskId = Integer.parseInt(taskIdStr);
+ mTaskInterface.removeTask(taskId);
return 0;
}
- int runMoveTopActivityToPinnedStack(PrintWriter pw) throws RemoteException {
- int stackId = Integer.parseInt(getNextArgRequired());
+ int runMoveTopActivityToPinnedRootTask(PrintWriter pw) throws RemoteException {
+ int rootTaskId = Integer.parseInt(getNextArgRequired());
final Rect bounds = getBounds();
if (bounds == null) {
getErrPrintWriter().println("Error: invalid input bounds");
return -1;
}
- if (!mTaskInterface.moveTopActivityToPinnedStack(stackId, bounds)) {
+ if (!mTaskInterface.moveTopActivityToPinnedRootTask(rootTaskId, bounds)) {
getErrPrintWriter().println("Didn't move top activity to pinned stack.");
return -1;
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 3f55bffc1f3e..cf900ab9b0e6 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -546,7 +546,7 @@ class AppErrors {
}
}
if (res == AppErrorDialog.FORCE_QUIT) {
- long orig = Binder.clearCallingIdentity();
+ final long orig = Binder.clearCallingIdentity();
try {
// Kill it with fire!
mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
@@ -800,9 +800,7 @@ class AppErrors {
// with a home activity running in the process to prevent a repeatedly crashing app
// from blocking the user to manually clear the list.
final WindowProcessController proc = app.getWindowProcessController();
- final WindowProcessController homeProc = mService.mAtmInternal.getHomeProcess();
- if (proc == homeProc && proc.hasActivities()
- && (((ProcessRecord) homeProc.mOwner).info.flags & FLAG_SYSTEM) == 0) {
+ if (proc.isHomeProcess() && proc.hasActivities() && (app.info.flags & FLAG_SYSTEM) == 0) {
proc.clearPackagePreferredForHomeActivities();
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 374c215fc6d0..20cad185d8d0 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -503,7 +503,7 @@ public final class AppExitInfoTracker {
@VisibleForTesting
void getExitInfo(final String packageName, final int filterUid,
final int filterPid, final int maxNum, final ArrayList<ApplicationExitInfo> results) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
boolean emptyPackageName = TextUtils.isEmpty(packageName);
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 692b3f11a5f8..b6010d917ef8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -33,7 +33,6 @@ import android.telephony.ModemActivityInfo;
import android.telephony.TelephonyManager;
import android.util.IntArray;
import android.util.Slog;
-import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BatteryStatsImpl;
@@ -607,49 +606,11 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
wasReset = true;
} else {
- final long totalActiveTimeMs = txTimeMs + rxTimeMs;
- long maxExpectedIdleTimeMs;
- if (totalActiveTimeMs > timePeriodMs) {
- // Cap the max idle time at zero since the active time consumed the whole time
- maxExpectedIdleTimeMs = 0;
- if (totalActiveTimeMs > timePeriodMs + MAX_WIFI_STATS_SAMPLE_ERROR_MILLIS) {
- StringBuilder sb = new StringBuilder();
- sb.append("Total Active time ");
- TimeUtils.formatDuration(totalActiveTimeMs, sb);
- sb.append(" is longer than sample period ");
- TimeUtils.formatDuration(timePeriodMs, sb);
- sb.append(".\n");
- sb.append("Previous WiFi snapshot: ").append("idle=");
- TimeUtils.formatDuration(lastIdleMs, sb);
- sb.append(" rx=");
- TimeUtils.formatDuration(lastRxMs, sb);
- sb.append(" tx=");
- TimeUtils.formatDuration(lastTxMs, sb);
- sb.append(" e=").append(lastEnergy);
- sb.append("\n");
- sb.append("Current WiFi snapshot: ").append("idle=");
- TimeUtils.formatDuration(latest.getControllerIdleDurationMillis(), sb);
- sb.append(" rx=");
- TimeUtils.formatDuration(latest.getControllerRxDurationMillis(), sb);
- sb.append(" tx=");
- TimeUtils.formatDuration(latest.getControllerTxDurationMillis(), sb);
- sb.append(" e=").append(latest.getControllerEnergyUsedMicroJoules());
- Slog.wtf(TAG, sb.toString());
- }
- } else {
- maxExpectedIdleTimeMs = timePeriodMs - totalActiveTimeMs;
- }
// These times seem to be the most reliable.
deltaControllerTxDurationMillis = txTimeMs;
deltaControllerRxDurationMillis = rxTimeMs;
deltaControllerScanDurationMillis = scanTimeMs;
- // WiFi calculates the idle time as a difference from the on time and the various
- // Rx + Tx times. There seems to be some missing time there because this sometimes
- // becomes negative. Just cap it at 0 and ensure that it is less than the expected idle
- // time from the difference in timestamps.
- // b/21613534
- deltaControllerIdleDurationMillis =
- Math.min(maxExpectedIdleTimeMs, Math.max(0, idleTimeMs));
+ deltaControllerIdleDurationMillis = idleTimeMs;
deltaControllerEnergyUsedMicroJoules =
Math.max(0, latest.getControllerEnergyUsedMicroJoules() - lastEnergy);
wasReset = false;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f42753287e35..3eb4ddebaf06 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -240,14 +240,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void noteBinderCallStats(int workSourceUid, long incrementatCallCount,
- Collection<BinderCallsStats.CallStat> callStats, int[] binderThreadNativeTids) {
+ Collection<BinderCallsStats.CallStat> callStats) {
synchronized (BatteryStatsService.this.mLock) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- mStats::noteBinderCallStats, workSourceUid, incrementatCallCount,
- callStats, binderThreadNativeTids,
+ mStats::noteBinderCallStats, workSourceUid, incrementatCallCount, callStats,
SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()));
}
}
+
+ @Override
+ public void noteBinderThreadNativeIds(int[] binderThreadNativeTids) {
+ synchronized (BatteryStatsService.this.mLock) {
+ mStats.noteBinderThreadNativeIds(binderThreadNativeTids);
+ }
+ }
}
@Override
@@ -276,15 +282,13 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
private void awaitCompletion() {
- synchronized (mLock) {
- final CountDownLatch latch = new CountDownLatch(1);
- mHandler.post(() -> {
- latch.countDown();
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- }
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandler.post(() -> {
+ latch.countDown();
+ });
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
}
}
@@ -2086,7 +2090,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (BatteryStatsHelper.checkWifiOnly(mContext)) {
flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
@@ -2246,7 +2250,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
// Wait for the completion of pending works if there is any
awaitCompletion();
@@ -2273,7 +2277,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
int i=-1;
try {
// Wait for the completion of pending works if there is any
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 93105daa6c5d..57f811215e50 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1686,7 +1686,7 @@ public final class BroadcastQueue {
// that request - we don't want the token to be swept from under our feet...
mHandler.removeCallbacksAndMessages(msgToken);
// ...then add the token
- proc.addAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
+ proc.addOrUpdateAllowBackgroundActivityStartsToken(r, r.mBackgroundActivityStartsToken);
}
final void setBroadcastTimeoutLocked(long timeoutTime) {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 1155569c6b36..950f0a0f56a3 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -682,7 +682,7 @@ public class ContentProviderHelper {
*/
void removeContentProvider(IBinder connection, boolean stable) {
mService.enforceNotIsolatedCaller("removeContentProvider");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mService) {
ContentProviderConnection conn;
@@ -711,7 +711,7 @@ public class ContentProviderHelper {
mService.enforceCallingPermission(
android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
"Do not have permission in call removeContentProviderExternal()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
removeContentProviderExternalUnchecked(name, token, userId);
} finally {
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 757b4e83e120..529c6516398e 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.annotation.NonNull;
import android.app.ActivityThread;
+import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
@@ -86,7 +87,7 @@ final class CoreSettingsObserver extends ContentObserver {
sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_ANGLE_DEBUG_PACKAGE, String.class);
sGlobalSettingToTypeMap.put(
- Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, String.class);
+ Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_ALL_ANGLE, int.class);
sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_ANGLE_GL_DRIVER_SELECTION_PKGS, String.class);
sGlobalSettingToTypeMap.put(
@@ -94,6 +95,8 @@ final class CoreSettingsObserver extends ContentObserver {
sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_ANGLE_ALLOWLIST, String.class);
sGlobalSettingToTypeMap.put(
+ Settings.Global.ANGLE_EGL_FEATURES, String.class);
+ sGlobalSettingToTypeMap.put(
Settings.Global.GLOBAL_SETTINGS_SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
@@ -221,16 +224,17 @@ final class CoreSettingsObserver extends ContentObserver {
@VisibleForTesting
void populateSettings(Bundle snapshot, Map<String, Class<?>> map) {
- Context context = mActivityManagerService.mContext;
+ final Context context = mActivityManagerService.mContext;
+ final ContentResolver cr = context.getContentResolver();
for (Map.Entry<String, Class<?>> entry : map.entrySet()) {
String setting = entry.getKey();
final String value;
if (map == sSecureSettingToTypeMap) {
- value = Settings.Secure.getString(context.getContentResolver(), setting);
+ value = Settings.Secure.getStringForUser(cr, setting, cr.getUserId());
} else if (map == sSystemSettingToTypeMap) {
- value = Settings.System.getString(context.getContentResolver(), setting);
+ value = Settings.System.getStringForUser(cr, setting, cr.getUserId());
} else {
- value = Settings.Global.getString(context.getContentResolver(), setting);
+ value = Settings.Global.getString(cr, setting);
}
if (value == null) {
snapshot.remove(setting);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 4b7c5407c4fe..da01394d8f58 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -771,10 +771,6 @@ public final class OomAdjuster {
uidRec.reset();
}
- if (mService.mAtmInternal != null) {
- mService.mAtmInternal.rankTaskLayersIfNeeded();
- }
-
mAdjSeq++;
if (fullUpdate) {
mNewNumServiceProcs = 0;
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index c62df56a88c5..2ae3d355a7ed 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -311,6 +311,16 @@ public class PendingIntentController {
}
}
+ int getPendingIntentFlags(IIntentSender target) {
+ if (!(target instanceof PendingIntentRecord)) {
+ Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target);
+ return 0;
+ }
+ synchronized (mLock) {
+ return ((PendingIntentRecord) target).key.flags;
+ }
+ }
+
private void makeIntentSenderCanceled(PendingIntentRecord rec) {
rec.canceled = true;
final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index e2fcf08ce82b..5167c5719955 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -27,15 +27,22 @@ import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.os.Handler;
import android.os.Process;
+import android.os.StrictMode;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcStatsUtil;
import com.android.internal.os.ProcessCpuTracker;
import libcore.io.IoUtils;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -78,18 +85,178 @@ public final class PhantomProcessList {
@GuardedBy("mLock")
private final ArrayList<PhantomProcessRecord> mTempPhantomProcesses = new ArrayList<>();
+ /**
+ * The mapping between a phantom process ID to its parent process (an app process)
+ */
+ @GuardedBy("mLock")
+ private final SparseArray<ProcessRecord> mPhantomToAppProcessMap = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final SparseArray<InputStream> mCgroupProcsFds = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final byte[] mDataBuffer = new byte[4096];
+
@GuardedBy("mLock")
private boolean mTrimPhantomProcessScheduled = false;
@GuardedBy("mLock")
int mUpdateSeq;
+ @VisibleForTesting
+ Injector mInjector;
+
private final ActivityManagerService mService;
private final Handler mKillHandler;
PhantomProcessList(final ActivityManagerService service) {
mService = service;
mKillHandler = service.mProcessList.sKillHandler;
+ mInjector = new Injector();
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void lookForPhantomProcessesLocked() {
+ mPhantomToAppProcessMap.clear();
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ synchronized (mService.mPidsSelfLocked) {
+ for (int i = mService.mPidsSelfLocked.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = mService.mPidsSelfLocked.valueAt(i);
+ lookForPhantomProcessesLocked(app);
+ }
+ }
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
+
+ @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
+ private void lookForPhantomProcessesLocked(ProcessRecord app) {
+ if (app.appZygote || app.killed || app.killedByAm) {
+ // process forked from app zygote doesn't have its own acct entry
+ return;
+ }
+ InputStream input = mCgroupProcsFds.get(app.pid);
+ if (input == null) {
+ final String path = getCgroupFilePath(app.info.uid, app.pid);
+ try {
+ input = mInjector.openCgroupProcs(path);
+ } catch (FileNotFoundException | SecurityException e) {
+ if (DEBUG_PROCESSES) {
+ Slog.w(TAG, "Unable to open " + path, e);
+ }
+ return;
+ }
+ // Keep the FD open for better performance
+ mCgroupProcsFds.put(app.pid, input);
+ }
+ final byte[] buf = mDataBuffer;
+ try {
+ int read = 0;
+ int pid = 0;
+ long totalRead = 0;
+ do {
+ read = mInjector.readCgroupProcs(input, buf, 0, buf.length);
+ if (read == -1) {
+ break;
+ }
+ totalRead += read;
+ for (int i = 0; i < read; i++) {
+ final byte b = buf[i];
+ if (b == '\n') {
+ addChildPidLocked(app, pid);
+ pid = 0;
+ } else {
+ pid = pid * 10 + (b - '0');
+ }
+ }
+ if (read < buf.length) {
+ // we may break from here safely as sysfs reading should return the whole page
+ // if the remaining data is larger than a page
+ break;
+ }
+ } while (true);
+ if (pid != 0) {
+ addChildPidLocked(app, pid);
+ }
+ // rewind the fd for the next read
+ input.skip(-totalRead);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error in reading cgroup procs from " + app, e);
+ IoUtils.closeQuietly(input);
+ mCgroupProcsFds.delete(app.pid);
+ }
+ }
+
+ @VisibleForTesting
+ static String getCgroupFilePath(int uid, int pid) {
+ return "/acct/uid_" + uid + "/pid_" + pid + "/cgroup.procs";
+ }
+
+ static String getProcessName(int pid) {
+ String procName = ProcStatsUtil.readTerminatedProcFile(
+ "/proc/" + pid + "/cmdline", (byte) '\0');
+ if (procName == null) {
+ return null;
+ }
+ int l = procName.lastIndexOf('/');
+ if (l > 0 && l < procName.length() - 1) {
+ procName = procName.substring(l + 1);
+ }
+ return procName;
+ }
+
+ @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
+ private void addChildPidLocked(final ProcessRecord app, final int pid) {
+ if (app.pid != pid) {
+ // That's something else...
+ final ProcessRecord r = mService.mPidsSelfLocked.get(pid);
+ if (r != null) {
+ // Is this a process forked via app zygote?
+ if (!r.appZygote) {
+ // Unexpected...
+ if (DEBUG_PROCESSES) {
+ Slog.w(TAG, "Unexpected: " + r + " appears in the cgroup.procs of " + app);
+ }
+ } else {
+ // Just a child process of app zygote, no worries
+ }
+ } else {
+ final int index = mPhantomToAppProcessMap.indexOfKey(pid);
+ if (index >= 0) { // unlikely since we cleared the map at the beginning
+ final ProcessRecord current = mPhantomToAppProcessMap.valueAt(index);
+ if (app == current) {
+ // Okay it's unchanged
+ return;
+ }
+ mPhantomToAppProcessMap.setValueAt(index, app);
+ } else {
+ mPhantomToAppProcessMap.put(pid, app);
+ }
+ // Its UID isn't necessarily to be the same as the app.info.uid, since it could be
+ // forked from child processes of app zygote
+ final int uid = Process.getUidForPid(pid);
+ String procName = mInjector.getProcessName(pid);
+ if (procName == null || uid < 0) {
+ mPhantomToAppProcessMap.delete(pid);
+ return;
+ }
+ getOrCreatePhantomProcessIfNeededLocked(procName, uid, pid, true);
+ }
+ }
+ }
+
+ void onAppDied(final int pid) {
+ synchronized (mLock) {
+ final int index = mCgroupProcsFds.indexOfKey(pid);
+ if (index >= 0) {
+ final InputStream inputStream = mCgroupProcsFds.valueAt(index);
+ mCgroupProcsFds.removeAt(index);
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
}
/**
@@ -99,7 +266,7 @@ public final class PhantomProcessList {
*/
@GuardedBy("mLock")
PhantomProcessRecord getOrCreatePhantomProcessIfNeededLocked(final String processName,
- final int uid, final int pid) {
+ final int uid, final int pid, boolean createIfNeeded) {
// First check if it's actually an app process we know
if (isAppProcess(pid)) {
return null;
@@ -123,56 +290,47 @@ public final class PhantomProcessList {
if (proc.equals(processName, uid, pid)) {
return proc;
}
- // Our zombie process information is outdated, let's remove this one, it shoud
+ // Our zombie process information is outdated, let's remove this one, it should
// have been gone.
mZombiePhantomProcesses.removeAt(idx);
}
}
- int ppid = getParentPid(pid);
-
- // Walk through its parents and see if it could be traced back to an app process.
- while (ppid > 1) {
- if (isAppProcess(ppid)) {
- // It's a phantom process, bookkeep it
- try {
- final PhantomProcessRecord proc = new PhantomProcessRecord(
- processName, uid, pid, ppid, mService,
- this::onPhantomProcessKilledLocked);
- proc.mUpdateSeq = mUpdateSeq;
- mPhantomProcesses.put(pid, proc);
- SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(ppid);
- if (array == null) {
- array = new SparseArray<>();
- mAppPhantomProcessMap.put(ppid, array);
- }
- array.put(pid, proc);
- if (proc.mPidFd != null) {
- mKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
- proc.mPidFd, EVENT_INPUT | EVENT_ERROR,
- this::onPhantomProcessFdEvent);
- mPhantomProcessesPidFds.put(proc.mPidFd.getInt$(), proc);
- }
- scheduleTrimPhantomProcessesLocked();
- return proc;
- } catch (IllegalStateException e) {
- return null;
+ if (!createIfNeeded) {
+ return null;
+ }
+
+ final ProcessRecord r = mPhantomToAppProcessMap.get(pid);
+
+ if (r != null) {
+ // It's a phantom process, bookkeep it
+ try {
+ final PhantomProcessRecord proc = new PhantomProcessRecord(
+ processName, uid, pid, r.pid, mService,
+ this::onPhantomProcessKilledLocked);
+ proc.mUpdateSeq = mUpdateSeq;
+ mPhantomProcesses.put(pid, proc);
+ SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(r.pid);
+ if (array == null) {
+ array = new SparseArray<>();
+ mAppPhantomProcessMap.put(r.pid, array);
}
+ array.put(pid, proc);
+ if (proc.mPidFd != null) {
+ mKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
+ proc.mPidFd, EVENT_INPUT | EVENT_ERROR,
+ this::onPhantomProcessFdEvent);
+ mPhantomProcessesPidFds.put(proc.mPidFd.getInt$(), proc);
+ }
+ scheduleTrimPhantomProcessesLocked();
+ return proc;
+ } catch (IllegalStateException e) {
+ return null;
}
-
- ppid = getParentPid(ppid);
}
return null;
}
- private static int getParentPid(int pid) {
- try {
- return Process.getParentPid(pid);
- } catch (Exception e) {
- }
- return -1;
- }
-
private boolean isAppProcess(int pid) {
synchronized (mService.mPidsSelfLocked) {
return mService.mPidsSelfLocked.get(pid) != null;
@@ -346,10 +504,14 @@ public final class PhantomProcessList {
synchronized (mLock) {
// refresh the phantom process list with the latest cpu stats results.
mUpdateSeq++;
+
+ // Scan app process's accounting procs
+ lookForPhantomProcessesLocked();
+
for (int i = tracker.countStats() - 1; i >= 0; i--) {
final ProcessCpuTracker.Stats st = tracker.getStats(i);
final PhantomProcessRecord r =
- getOrCreatePhantomProcessIfNeededLocked(st.name, st.uid, st.pid);
+ getOrCreatePhantomProcessIfNeededLocked(st.name, st.uid, st.pid, false);
if (r != null) {
r.mUpdateSeq = mUpdateSeq;
r.mCurrentCputime += st.rel_utime + st.rel_stime;
@@ -392,4 +554,19 @@ public final class PhantomProcessList {
proc.dump(pw, prefix + " ");
}
}
+
+ @VisibleForTesting
+ static class Injector {
+ InputStream openCgroupProcs(String path) throws FileNotFoundException, SecurityException {
+ return new FileInputStream(path);
+ }
+
+ int readCgroupProcs(InputStream input, byte[] buf, int offset, int len) throws IOException {
+ return input.read(buf, offset, len);
+ }
+
+ String getProcessName(final int pid) {
+ return PhantomProcessList.getProcessName(pid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2d7c7dec1a2b..f14245e587db 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -92,7 +92,6 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.system.Os;
import android.text.TextUtils;
@@ -1786,14 +1785,10 @@ public final class ProcessList {
final IPackageManager pm = AppGlobals.getPackageManager();
permGids = pm.getPackageGids(app.info.packageName,
MATCH_DIRECT_BOOT_AUTO, app.userId);
- if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) {
- mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
- } else {
- StorageManagerInternal storageManagerInternal = LocalServices.getService(
- StorageManagerInternal.class);
- mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
- app.info.packageName);
- }
+ StorageManagerInternal storageManagerInternal = LocalServices.getService(
+ StorageManagerInternal.class);
+ mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
+ app.info.packageName);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -1924,7 +1919,9 @@ public final class ProcessList {
String instructionSet = null;
if (app.info.primaryCpuAbi != null) {
- instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
+ // If ABI override is specified, use the isa derived from the value of ABI override.
+ // Otherwise, use the isa derived from primary ABI
+ instructionSet = VMRuntime.getInstructionSet(requiredAbi);
}
app.gids = gids;
@@ -1933,7 +1930,7 @@ public final class ProcessList {
// If instructionSet is non-null, this indicates that the system_server is spawning a
// process with an ISA that may be different from its own. System (kernel and hardware)
- // compatililty for these features is checked in the decideTaggingLevel in the
+ // compatibility for these features is checked in the decideTaggingLevel in the
// system_server process (not the child process). As both MTE and TBI are only supported
// in aarch64, we can simply ensure that the new process is also aarch64. This prevents
// the mismatch where a 64-bit system server spawns a 32-bit child that thinks it should
@@ -2556,8 +2553,8 @@ public final class ProcessList {
app.hostingRecord.getName() != null ? app.hostingRecord.getName() : "");
try {
- AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
- app.seInfo, app.info.sourceDir, pid);
+ AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.info.packageName,
+ app.processName, app.uid, app.seInfo, app.info.sourceDir, pid);
} catch (RemoteException ex) {
// Ignore
}
@@ -3577,7 +3574,7 @@ public final class ProcessList {
int clientTargetSdk) {
outInfo.pid = app.pid;
outInfo.uid = app.info.uid;
- if (mService.mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
+ if (app.getWindowProcessController().isHeavyWeightProcess()) {
outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE;
}
if (app.isPersistent()) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index a02317096dbb..6d876b71a573 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -79,6 +79,7 @@ import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -247,7 +248,7 @@ class ProcessRecord implements WindowProcessListener {
long lastTopTime; // The last time the process was in the TOP state or greater.
boolean reportLowMemory; // Set to true when waiting to report low mem
boolean empty; // Is this an empty background process?
- private volatile boolean mCached; // Is this a cached process?
+ private boolean mCached; // Is this a cached process?
String adjType; // Debugging: primary thing impacting oom_adj.
int adjTypeCode; // Debugging: adj code to report to app.
Object adjSource; // Debugging: option dependent object.
@@ -819,10 +820,7 @@ class ProcessRecord implements WindowProcessListener {
}
void setCached(boolean cached) {
- if (mCached != cached) {
- mCached = cached;
- mWindowProcessController.onProcCachedStateChanged(cached);
- }
+ mCached = cached;
}
@Override
@@ -912,7 +910,7 @@ class ProcessRecord implements WindowProcessListener {
Slog.w(TAG, "scheduleCrash: trying to crash system process!");
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
thread.scheduleCrash(message);
} catch (RemoteException e) {
@@ -1372,13 +1370,15 @@ class ProcessRecord implements WindowProcessListener {
* {@param originatingToken} if you have one such originating token, this is useful for tracing
* back the grant in the case of the notification token.
*/
- void addAllowBackgroundActivityStartsToken(Binder entity, @Nullable IBinder originatingToken) {
- if (entity == null) return;
- mWindowProcessController.addAllowBackgroundActivityStartsToken(entity, originatingToken);
+ void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
+ @Nullable IBinder originatingToken) {
+ Objects.requireNonNull(entity);
+ mWindowProcessController.addOrUpdateAllowBackgroundActivityStartsToken(entity,
+ originatingToken);
}
void removeAllowBackgroundActivityStartsToken(Binder entity) {
- if (entity == null) return;
+ Objects.requireNonNull(entity);
mWindowProcessController.removeAllowBackgroundActivityStartsToken(entity);
}
@@ -1879,8 +1879,8 @@ class ProcessRecord implements WindowProcessListener {
boolean getCachedIsHeavyWeight() {
if (mCachedIsHeavyWeight == VALUE_INVALID) {
- mCachedIsHeavyWeight = mService.mAtmInternal.isHeavyWeightProcess(
- getWindowProcessController()) ? VALUE_TRUE : VALUE_FALSE;
+ mCachedIsHeavyWeight = getWindowProcessController().isHeavyWeightProcess()
+ ? VALUE_TRUE : VALUE_FALSE;
}
return mCachedIsHeavyWeight == VALUE_TRUE;
}
@@ -1938,8 +1938,8 @@ class ProcessRecord implements WindowProcessListener {
}
callback.initialize(this, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
processCurTop);
- final int minLayer = getWindowProcessController().computeOomAdjFromActivities(
- ProcessList.VISIBLE_APP_LAYER_MAX, callback);
+ final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
+ getWindowProcessController().computeOomAdjFromActivities(callback));
mCachedAdj = callback.adj;
mCachedForegroundActivities = callback.foregroundActivities;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index d925defdbc73..c10a07862123 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -855,7 +855,7 @@ public final class ProcessStatsService extends IProcessStats.Stub {
if (!com.android.internal.util.DumpUtils.checkDumpAndUsageStatsPermission(mAm.mContext,
TAG, pw)) return;
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
if (args.length > 0) {
if ("--proto".equals(args[0])) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 55e0f2ea5212..4cdf66130cf4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -22,6 +22,7 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -104,7 +105,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT?
boolean delayed; // are we waiting to start this service in the background?
boolean fgRequired; // is the service required to go foreground after starting?
- boolean hideFgNotification; // Hide the fg service notification
boolean fgWaiting; // is a timeout for going foreground already scheduled?
boolean isForeground; // is service currently in foreground mode?
int foregroundId; // Notification ID of last foreground req.
@@ -139,6 +139,12 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// used to clean up the state of mIsAllowedBgActivityStartsByStart after a timeout
private Runnable mCleanUpAllowBgActivityStartsByStartCallback;
private ProcessRecord mAppForAllowingBgActivityStartsByStart;
+ // These are the originating tokens that currently allow bg activity starts by service start.
+ // This is used to trace back the grant when starting activities. We only pass such token to the
+ // ProcessRecord if it's the *only* cause for bg activity starts exemption, otherwise we pass
+ // null.
+ @GuardedBy("ams")
+ private List<IBinder> mBgActivityStartsByStartOriginatingTokens = new ArrayList<>();
// allow while-in-use permissions in foreground service or not.
// while-in-use permissions in FGS started from background might be restricted.
@@ -588,7 +594,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
? _proc : null;
if (mIsAllowedBgActivityStartsByStart
|| mIsAllowedBgActivityStartsByBinding) {
- _proc.addAllowBackgroundActivityStartsToken(this, null);
+ _proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
+ getExclusiveOriginatingToken());
} else {
_proc.removeAllowBackgroundActivityStartsToken(this);
}
@@ -679,10 +686,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
void setAllowedBgActivityStartsByBinding(boolean newValue) {
- if (mIsAllowedBgActivityStartsByBinding != newValue) {
- mIsAllowedBgActivityStartsByBinding = newValue;
- updateParentProcessBgActivityStartsToken();
- }
+ mIsAllowedBgActivityStartsByBinding = newValue;
+ updateParentProcessBgActivityStartsToken();
}
/**
@@ -691,7 +696,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
* timeout. Note that the ability for starting background activities persists for the process
* even if the service is subsequently stopped.
*/
- void allowBgActivityStartsOnServiceStart() {
+ void allowBgActivityStartsOnServiceStart(@Nullable IBinder originatingToken) {
+ mBgActivityStartsByStartOriginatingTokens.add(originatingToken);
setAllowedBgActivityStartsByStart(true);
if (app != null) {
mAppForAllowingBgActivityStartsByStart = app;
@@ -701,32 +707,49 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
if (mCleanUpAllowBgActivityStartsByStartCallback == null) {
mCleanUpAllowBgActivityStartsByStartCallback = () -> {
synchronized (ams) {
- if (app == mAppForAllowingBgActivityStartsByStart) {
- // The process we allowed is still running the service. We remove
- // the ability by start, but it may still be allowed via bound connections.
- setAllowedBgActivityStartsByStart(false);
- } else if (mAppForAllowingBgActivityStartsByStart != null) {
- // The process we allowed is not running the service. It therefore can't be
- // bound so we can unconditionally remove the ability.
- mAppForAllowingBgActivityStartsByStart
- .removeAllowBackgroundActivityStartsToken(ServiceRecord.this);
+ mBgActivityStartsByStartOriginatingTokens.remove(0);
+ if (!mBgActivityStartsByStartOriginatingTokens.isEmpty()) {
+ // There are other callbacks in the queue, let's just update the originating
+ // token
+ if (mIsAllowedBgActivityStartsByStart) {
+ mAppForAllowingBgActivityStartsByStart
+ .addOrUpdateAllowBackgroundActivityStartsToken(
+ this, getExclusiveOriginatingToken());
+ } else {
+ Slog.wtf(TAG,
+ "Service callback to revoke bg activity starts by service "
+ + "start triggered but "
+ + "mIsAllowedBgActivityStartsByStart = false. This "
+ + "should never happen.");
+ }
+ } else {
+ // Last callback on the queue
+ if (app == mAppForAllowingBgActivityStartsByStart) {
+ // The process we allowed is still running the service. We remove
+ // the ability by start, but it may still be allowed via bound
+ // connections.
+ setAllowedBgActivityStartsByStart(false);
+ } else if (mAppForAllowingBgActivityStartsByStart != null) {
+ // The process we allowed is not running the service. It therefore can't
+ // be bound so we can unconditionally remove the ability.
+ mAppForAllowingBgActivityStartsByStart
+ .removeAllowBackgroundActivityStartsToken(ServiceRecord.this);
+ }
+ mAppForAllowingBgActivityStartsByStart = null;
}
- mAppForAllowingBgActivityStartsByStart = null;
}
};
}
- // if there's a request pending from the past, drop it before scheduling a new one
- ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback);
+ // Existing callbacks will only update the originating token, only when the last callback is
+ // executed is the grant revoked.
ams.mHandler.postDelayed(mCleanUpAllowBgActivityStartsByStartCallback,
ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
}
private void setAllowedBgActivityStartsByStart(boolean newValue) {
- if (mIsAllowedBgActivityStartsByStart != newValue) {
- mIsAllowedBgActivityStartsByStart = newValue;
- updateParentProcessBgActivityStartsToken();
- }
+ mIsAllowedBgActivityStartsByStart = newValue;
+ updateParentProcessBgActivityStartsToken();
}
/**
@@ -736,8 +759,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
* {@code mIsAllowedBgActivityStartsByBinding}. If either is true, this ServiceRecord
* should be contributing as a token in parent ProcessRecord.
*
- * @see com.android.server.am.ProcessRecord#addAllowBackgroundActivityStartsToken(Binder,
- * IBinder)
+ * @see com.android.server.am.ProcessRecord#addOrUpdateAllowBackgroundActivityStartsToken(
+ * Binder, IBinder)
* @see com.android.server.am.ProcessRecord#removeAllowBackgroundActivityStartsToken(Binder)
*/
private void updateParentProcessBgActivityStartsToken() {
@@ -747,12 +770,37 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) {
// if the token is already there it's safe to "re-add it" - we're dealing with
// a set of Binder objects
- app.addAllowBackgroundActivityStartsToken(this, null);
+ app.addOrUpdateAllowBackgroundActivityStartsToken(this, getExclusiveOriginatingToken());
} else {
app.removeAllowBackgroundActivityStartsToken(this);
}
}
+ /**
+ * Returns the originating token if that's the only reason background activity starts are
+ * allowed. In order for that to happen the service has to be allowed only due to starts, since
+ * bindings are not associated with originating tokens, and all the start tokens have to be the
+ * same and there can't be any null originating token in the queue.
+ *
+ * Originating tokens are optional, so the caller could provide null when it allows bg activity
+ * starts.
+ */
+ @Nullable
+ private IBinder getExclusiveOriginatingToken() {
+ if (mIsAllowedBgActivityStartsByBinding
+ || mBgActivityStartsByStartOriginatingTokens.isEmpty()) {
+ return null;
+ }
+ IBinder firstToken = mBgActivityStartsByStartOriginatingTokens.get(0);
+ for (int i = 1, n = mBgActivityStartsByStartOriginatingTokens.size(); i < n; i++) {
+ IBinder token = mBgActivityStartsByStartOriginatingTokens.get(i);
+ if (token != firstToken) {
+ return null;
+ }
+ }
+ return firstToken;
+ }
+
@GuardedBy("ams")
void updateKeepWarmLocked() {
mKeepWarming = ams.mConstants.KEEP_WARMING_SERVICES.contains(name)
@@ -837,9 +885,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
public void postNotification() {
- if (hideFgNotification) {
- return;
- }
final int appUid = appInfo.uid;
final int appPid = app.pid;
if (foregroundId != 0 && foregroundNoti != null) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3dfbcc71dd3c..f1bad9819394 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -23,11 +23,10 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE_OR_FULL;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
@@ -713,8 +712,6 @@ class UserController implements Handler.Callback {
Slog.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
// Pre-created user was started right after creation so services could properly
// intialize it; it should be stopped right away as it's not really a "real" user.
- // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
- // callback on SystemService instead.
stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
@@ -1817,7 +1814,7 @@ class UserController implements Handler.Callback {
void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
Intent intent;
if (oldUserId >= 0) {
@@ -1912,12 +1909,11 @@ class UserController implements Handler.Callback {
callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
// If the caller does not have either permission, they are always doomed.
allow = false;
- } else if (allowMode == ALLOW_NON_FULL
- || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
+ } else if (allowMode == ALLOW_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE_OR_FULL
- || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL) {
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+ || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
allow = isSameProfileGroup;
@@ -1944,15 +1940,12 @@ class UserController implements Handler.Callback {
builder.append("; this requires ");
builder.append(INTERACT_ACROSS_USERS_FULL);
if (allowMode != ALLOW_FULL_ONLY) {
- if (allowMode == ALLOW_NON_FULL
- || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL
- || isSameProfileGroup) {
+ if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
}
if (isSameProfileGroup
- && (allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
- || allowMode == ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL)) {
+ && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_PROFILES);
}
@@ -1979,8 +1972,7 @@ class UserController implements Handler.Callback {
private boolean canInteractWithAcrossProfilesPermission(
int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid,
String callingPackage) {
- if (allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_FULL
- && allowMode != ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL) {
+ if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
return false;
}
if (!isSameProfileGroup) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index ffd3c574e9c6..5379f3218a6e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -19,7 +19,6 @@ package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
-import static android.app.ActivityManagerInternal.ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL;
import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
@@ -30,6 +29,7 @@ import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.NoteOpEvent;
@@ -37,7 +37,6 @@ import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
@@ -131,10 +130,10 @@ import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.EventLog;
import android.util.KeyValueListParser;
import android.util.LongSparseArray;
import android.util.Pair;
+import android.util.Pools;
import android.util.Pools.SimplePool;
import android.util.Slog;
import android.util.SparseArray;
@@ -165,7 +164,6 @@ import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
-import com.android.server.am.ActivityManagerService;
import com.android.server.pm.PackageList;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -412,14 +410,26 @@ public class AppOpsService extends IAppOpsService.Stub {
}
InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId,
- @NonNull Runnable onDeath, int uidState) throws RemoteException {
+ @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
+ @OpFlags int flags) throws RemoteException {
+
InProgressStartOpEvent recycled = acquire();
+
+ OpEventProxyInfo proxyInfo = null;
+ if (proxyUid != Process.INVALID_UID) {
+ proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
+ proxyAttributionTag);
+ }
+
if (recycled != null) {
- recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState);
+ recycled.reinit(startTime, elapsedTime, clientId, onDeath, uidState, flags,
+ proxyInfo, mOpEventProxyInfoPool);
return recycled;
}
- return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState);
+ return new InProgressStartOpEvent(startTime, elapsedTime, clientId, onDeath, uidState,
+ proxyInfo, flags);
}
}
@@ -588,29 +598,6 @@ public class AppOpsService extends IAppOpsService.Stub {
// process is not in foreground.
return MODE_IGNORED;
}
- } else if (mode == MODE_ALLOWED) {
- switch (op) {
- case OP_CAMERA:
- if (mActivityManagerInternal != null
- && mActivityManagerInternal.isPendingTopUid(uid)) {
- return MODE_ALLOWED;
- } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
- case OP_RECORD_AUDIO:
- if (mActivityManagerInternal != null
- && mActivityManagerInternal.isPendingTopUid(uid)) {
- return MODE_ALLOWED;
- } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
- return MODE_ALLOWED;
- } else {
- return MODE_IGNORED;
- }
- default:
- return MODE_ALLOWED;
- }
}
return mode;
}
@@ -697,6 +684,12 @@ public class AppOpsService extends IAppOpsService.Stub {
/** uidstate used when calling startOp */
private @AppOpsManager.UidState int mUidState;
+ /** Proxy information of the startOp event */
+ private @Nullable OpEventProxyInfo mProxy;
+
+ /** Proxy flag information */
+ private @OpFlags int mFlags;
+
/** How many times the op was started but not finished yet */
int numUnfinishedStarts;
@@ -708,17 +701,22 @@ public class AppOpsService extends IAppOpsService.Stub {
* @param clientId The client id of the caller of {@link #startOperation}
* @param onDeath The code to execute on client death
* @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param proxy The proxy information, if {@link #startProxyOperation} was called
+ * @param flags The trusted/nontrusted/self flags.
*
* @throws RemoteException If the client is dying
*/
private InProgressStartOpEvent(long startTime, long startElapsedTime,
- @NonNull IBinder clientId, @NonNull Runnable onDeath, int uidState)
- throws RemoteException {
+ @NonNull IBinder clientId, @NonNull Runnable onDeath,
+ @AppOpsManager.UidState int uidState, @Nullable OpEventProxyInfo proxy,
+ @OpFlags int flags) throws RemoteException {
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
mOnDeath = onDeath;
mUidState = uidState;
+ mProxy = proxy;
+ mFlags = flags;
clientId.linkToDeath(this, 0);
}
@@ -741,16 +739,27 @@ public class AppOpsService extends IAppOpsService.Stub {
* @param clientId The client id of the caller of {@link #startOperation}
* @param onDeath The code to execute on client death
* @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param flags The flags relating to the proxy
+ * @param proxy The proxy information, if {@link #startProxyOperation} was called
+ * @param proxyPool The pool to release previous {@link OpEventProxyInfo} to
*
* @throws RemoteException If the client is dying
*/
public void reinit(long startTime, long startElapsedTime, @NonNull IBinder clientId,
- @NonNull Runnable onDeath, int uidState) throws RemoteException {
+ @NonNull Runnable onDeath, @AppOpsManager.UidState int uidState, @OpFlags int flags,
+ @Nullable OpEventProxyInfo proxy, @NonNull Pools.Pool<OpEventProxyInfo> proxyPool
+ ) throws RemoteException {
mStartTime = startTime;
mStartElapsedTime = startElapsedTime;
mClientId = clientId;
mOnDeath = onDeath;
mUidState = uidState;
+ mFlags = flags;
+
+ if (mProxy != null) {
+ proxyPool.release(mProxy);
+ }
+ mProxy = proxy;
clientId.linkToDeath(this, 0);
}
@@ -771,9 +780,19 @@ public class AppOpsService extends IAppOpsService.Stub {
}
/** @return uidstate used when calling startOp */
- public int getUidState() {
+ public @AppOpsManager.UidState int getUidState() {
return mUidState;
}
+
+ /** @return proxy info for the access */
+ public @Nullable OpEventProxyInfo getProxy() {
+ return mProxy;
+ }
+
+ /** @return flags used for the access */
+ public @OpFlags int getFlags() {
+ return mFlags;
+ }
}
private final class AttributedOp {
@@ -903,14 +922,22 @@ public class AppOpsService extends IAppOpsService.Stub {
* Update state when start was called
*
* @param clientId Id of the startOp caller
+ * @param proxyUid The UID of the proxy app
+ * @param proxyPackageName The package name of the proxy app
+ * @param proxyAttributionTag The attribution tag of the proxy app
* @param uidState UID state of the app startOp is called for
+ * @param flags The proxy flags
*/
- public void started(@NonNull IBinder clientId, @AppOpsManager.UidState int uidState)
- throws RemoteException {
- started(clientId, uidState, true);
+ public void started(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @OpFlags int flags) throws RemoteException {
+ started(clientId, proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
+ true);
}
- private void started(@NonNull IBinder clientId, @AppOpsManager.UidState int uidState,
+ private void started(@NonNull IBinder clientId, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag,
+ @AppOpsManager.UidState int uidState, @OpFlags int flags,
boolean triggerCallbackIfNeeded) throws RemoteException {
if (triggerCallbackIfNeeded && !parent.isRunning()) {
scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
@@ -926,7 +953,7 @@ public class AppOpsService extends IAppOpsService.Stub {
event = mInProgressStartOpEventPool.acquire(System.currentTimeMillis(),
SystemClock.elapsedRealtime(), clientId,
PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
- uidState);
+ proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags);
mInProgressEvents.put(clientId, event);
} else {
if (uidState != event.mUidState) {
@@ -936,9 +963,8 @@ public class AppOpsService extends IAppOpsService.Stub {
event.numUnfinishedStarts++;
- // startOp events don't support proxy, hence use flags==SELF
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
- tag, uidState, OP_FLAG_SELF);
+ tag, uidState, flags);
}
/**
@@ -972,14 +998,17 @@ public class AppOpsService extends IAppOpsService.Stub {
mAccessEvents = new LongSparseArray<>(1);
}
- // startOp events don't support proxy, hence use flags==SELF
+ OpEventProxyInfo proxyCopy = event.getProxy() != null
+ ? new OpEventProxyInfo(event.getProxy()) : null;
+
NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(),
- SystemClock.elapsedRealtime() - event.getStartElapsedTime(), null);
- mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent);
+ SystemClock.elapsedRealtime() - event.getStartElapsedTime(), proxyCopy);
+ mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
+ finishedEvent);
mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
parent.packageName, tag, event.getUidState(),
- AppOpsManager.OP_FLAG_SELF, finishedEvent.getDuration());
+ event.getFlags(), finishedEvent.getDuration());
mInProgressStartOpEventPool.release(event);
@@ -1037,9 +1066,17 @@ public class AppOpsService extends IAppOpsService.Stub {
event.numUnfinishedStarts = 1;
finished(event.getClientId(), false);
+ OpEventProxyInfo proxy = event.getProxy();
+
// Call started() to add a new start event object and then add the
// previously removed unfinished start counts back
- started(event.getClientId(), newState, false);
+ if (proxy != null) {
+ started(event.getClientId(), proxy.getUid(), proxy.getPackageName(),
+ proxy.getAttributionTag(), newState, event.getFlags(), false);
+ } else {
+ started(event.getClientId(), Process.INVALID_UID, null, null, newState,
+ OP_FLAG_SELF, false);
+ }
event.numUnfinishedStarts += numPreviousUnfinishedStarts - 1;
} catch (RemoteException e) {
if (DEBUG) Slog.e(TAG, "Cannot switch to new uidState " + newState);
@@ -1143,10 +1180,9 @@ public class AppOpsService extends IAppOpsService.Stub {
for (int i = 0; i < numInProgressEvents; i++) {
InProgressStartOpEvent event = mInProgressEvents.valueAt(i);
- // startOp events don't support proxy
- accessEvents.append(makeKey(event.getUidState(), OP_FLAG_SELF),
+ accessEvents.append(makeKey(event.getUidState(), event.getFlags()),
new NoteOpEvent(event.getStartTime(), now - event.getStartElapsedTime(),
- null));
+ event.getProxy()));
}
}
@@ -1736,24 +1772,13 @@ public class AppOpsService extends IAppOpsService.Stub {
if (Process.isIsolated(uid)) {
return Zygote.MOUNT_EXTERNAL_NONE;
}
- if (noteOperation(AppOpsManager.OP_READ_EXTERNAL_STORAGE, uid,
- packageName, null, true, "External storage policy", true)
- != AppOpsManager.MODE_ALLOWED) {
- return Zygote.MOUNT_EXTERNAL_NONE;
- }
- if (noteOperation(AppOpsManager.OP_WRITE_EXTERNAL_STORAGE, uid,
- packageName, null, true, "External storage policy", true)
- != AppOpsManager.MODE_ALLOWED) {
- return Zygote.MOUNT_EXTERNAL_READ;
- }
- return Zygote.MOUNT_EXTERNAL_WRITE;
+ return Zygote.MOUNT_EXTERNAL_DEFAULT;
}
@Override
public boolean hasExternalStorage(int uid, String packageName) {
final int mountMode = getMountMode(uid, packageName);
- return mountMode == Zygote.MOUNT_EXTERNAL_READ
- || mountMode == Zygote.MOUNT_EXTERNAL_WRITE;
+ return mountMode != Zygote.MOUNT_EXTERNAL_NONE;
}
});
}
@@ -1798,9 +1823,9 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
-
- mHistoricalRegistry.clearHistory(uid, packageName);
}
+
+ mHistoricalRegistry.clearHistory(uid, packageName);
}
public void uidRemoved(int uid) {
@@ -2203,11 +2228,8 @@ public class AppOpsService extends IAppOpsService.Stub {
+ " by uid " + Binder.getCallingUid());
}
- int userId = UserHandle.getUserId(uid);
-
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
verifyIncomingOp(code);
- verifyIncomingUser(userId);
code = AppOpsManager.opToSwitch(code);
if (permissionPolicyCallback == null) {
@@ -2223,14 +2245,14 @@ public class AppOpsService extends IAppOpsService.Stub {
if (mode == defaultMode) {
return;
}
- previousMode = AppOpsManager.MODE_DEFAULT;
+ previousMode = MODE_DEFAULT;
uidState = new UidState(uid);
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
mUidStates.put(uid, uidState);
scheduleWriteLocked();
} else if (uidState.opModes == null) {
- previousMode = AppOpsManager.MODE_DEFAULT;
+ previousMode = MODE_DEFAULT;
if (mode != defaultMode) {
uidState.opModes = new SparseIntArray();
uidState.opModes.put(code, mode);
@@ -2252,11 +2274,6 @@ public class AppOpsService extends IAppOpsService.Stub {
scheduleWriteLocked();
}
uidState.evalForegroundOps(mOpModeWatchers);
-
- if (code == OP_INTERACT_ACROSS_PROFILES) {
- // Invalidate package info cache as the visibility of packages might have changed
- PackageManager.invalidatePackageInfoCache();
- }
}
notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
@@ -2402,7 +2419,7 @@ public class AppOpsService extends IAppOpsService.Stub {
+ permissionInfo.backgroundPermission);
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
@@ -2426,7 +2443,7 @@ public class AppOpsService extends IAppOpsService.Stub {
+ switchCode + ", mode=" + mode + ", permission=" + permissionName);
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
packageManager.updatePermissionFlags(permissionName, packageName,
PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
@@ -2462,12 +2479,8 @@ public class AppOpsService extends IAppOpsService.Stub {
private void setMode(int code, int uid, @NonNull String packageName, int mode,
@Nullable IAppOpsCallback permissionPolicyCallback) {
enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
-
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
ArraySet<ModeCallback> repCbs = null;
code = AppOpsManager.opToSwitch(code);
@@ -2480,7 +2493,7 @@ public class AppOpsService extends IAppOpsService.Stub {
return;
}
- int previousMode = AppOpsManager.MODE_DEFAULT;
+ int previousMode = MODE_DEFAULT;
synchronized (this) {
UidState uidState = getUidStateLocked(uid, false);
Op op = getOpLocked(code, uid, packageName, null, bypass, true);
@@ -2729,9 +2742,6 @@ public class AppOpsService extends IAppOpsService.Stub {
if (changed) {
scheduleFastWriteLocked();
-
- // Invalidate package info cache as the visibility of packages might have changed
- PackageManager.invalidatePackageInfoCache();
}
}
if (callbacks != null) {
@@ -2890,11 +2900,8 @@ public class AppOpsService extends IAppOpsService.Stub {
private int checkOperationImpl(int code, int uid, String packageName,
boolean raw) {
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3013,15 +3020,10 @@ public class AppOpsService extends IAppOpsService.Stub {
String proxiedAttributionTag, int proxyUid, String proxyPackageName,
String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage) {
- int proxiedUserId = UserHandle.getUserId(proxiedUid);
- int proxyUserId = UserHandle.getUserId(proxyUid);
-
verifyIncomingUid(proxyUid);
verifyIncomingOp(code);
- verifyIncomingUser(proxiedUserId);
- verifyIncomingUser(proxyUserId);
- verifyIncomingPackage(proxiedPackageName, proxiedUserId);
- verifyIncomingPackage(proxyPackageName, proxyUserId);
+ verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
+ verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
String resolveProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
if (resolveProxyPackageName == null) {
@@ -3071,12 +3073,9 @@ public class AppOpsService extends IAppOpsService.Stub {
private int noteOperationImpl(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage) {
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingUid(uid);
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3325,7 +3324,7 @@ public class AppOpsService extends IAppOpsService.Stub {
int callingUid = Binder.getCallingUid();
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
synchronized (this) {
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
@@ -3453,12 +3452,9 @@ public class AppOpsService extends IAppOpsService.Stub {
public int startOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp,
String message, boolean shouldCollectMessage) {
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingUid(uid);
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
@@ -3476,7 +3472,75 @@ public class AppOpsService extends IAppOpsService.Stub {
return result;
}
}
+ return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+ Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, false);
+
+ }
+ @Override
+ public int startProxyOperation(IBinder clientId, int code, int proxiedUid,
+ String proxiedPackageName, @Nullable String proxiedAttributionTag, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
+ boolean shouldCollectMessage) {
+ verifyIncomingUid(proxyUid);
+ verifyIncomingOp(code);
+ verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
+ verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
+
+ String resolvedProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
+ if (resolvedProxyPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+
+ final boolean isProxyTrusted = mContext.checkPermission(
+ Manifest.permission.UPDATE_APP_OPS_STATS, -1, proxyUid)
+ == PackageManager.PERMISSION_GRANTED;
+
+ final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
+ : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
+
+ String resolvedProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
+ if (resolvedProxiedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
+ : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
+
+ // Test if the proxied operation will succeed before starting the proxy operation
+ final int testProxiedMode = startOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage, true);
+ if (!shouldStartForMode(testProxiedMode, startIfModeDefault)) {
+ return testProxiedMode;
+ }
+
+ final int proxyMode = startOperationUnchecked(clientId, code, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
+ proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
+ shouldCollectMessage, false);
+ if (!shouldStartForMode(proxyMode, startIfModeDefault)
+ || Binder.getCallingUid() == proxiedUid) {
+ return proxyMode;
+ }
+
+ return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
+ proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, false);
+ }
+
+ private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+ return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+ }
+
+ private int startOperationUnchecked(IBinder clientId, int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
+ boolean shouldCollectMessage, boolean dryRun) {
RestrictionBypass bypass;
try {
bypass = verifyAndGetBypass(uid, packageName, attributionTag);
@@ -3486,19 +3550,25 @@ public class AppOpsService extends IAppOpsService.Stub {
}
synchronized (this) {
- final Ops ops = getOpsLocked(uid, resolvedPackageName, attributionTag, bypass,
- true /* edit */);
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag, bypass, true /* edit */);
if (ops == null) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED);
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName,
+ AppOpsManager.MODE_IGNORED);
+ }
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + resolvedPackageName);
+ + " package " + packageName);
return AppOpsManager.MODE_ERRORED;
}
final Op op = getOpLocked(ops, code, uid, true);
- if (isOpRestrictedLocked(uid, code, resolvedPackageName, bypass)) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_IGNORED);
+ if (isOpRestrictedLocked(uid, code, packageName, bypass)) {
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName,
+ AppOpsManager.MODE_IGNORED);
+ }
return AppOpsManager.MODE_IGNORED;
}
+
final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
final int switchCode = AppOpsManager.opToSwitch(code);
final UidState uidState = ops.uidState;
@@ -3506,13 +3576,16 @@ public class AppOpsService extends IAppOpsService.Stub {
// non-default) it takes over, otherwise use the per package policy.
if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
- if (uidMode != AppOpsManager.MODE_ALLOWED
- && (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
- if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + resolvedPackageName);
- attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, uidMode);
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName);
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.state, flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, uidMode);
+ }
return uidMode;
}
} else {
@@ -3520,26 +3593,31 @@ public class AppOpsService extends IAppOpsService.Stub {
: op;
final int mode = switchOp.evalMode();
if (mode != AppOpsManager.MODE_ALLOWED
- && (!startIfModeDefault || mode != AppOpsManager.MODE_DEFAULT)) {
+ && (!startIfModeDefault || mode != MODE_DEFAULT)) {
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
- + resolvedPackageName);
- attributedOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, mode);
+ + packageName);
+ if (!dryRun) {
+ attributedOp.rejected(uidState.state, flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, mode);
+ }
return mode;
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + resolvedPackageName);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_ALLOWED);
- try {
- attributedOp.started(clientId, uidState.state);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ + " package " + packageName);
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_ALLOWED);
+ try {
+ attributedOp.started(clientId, proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState.state, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
}
- if (shouldCollectAsyncNotedOp) {
+ if (shouldCollectAsyncNotedOp && !dryRun) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
message, shouldCollectMessage);
}
@@ -3550,18 +3628,46 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingUid(uid);
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
return;
}
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ }
+
+ @Override
+ public void finishProxyOperation(IBinder clientId, int code, int proxiedUid,
+ String proxiedPackageName, @Nullable String proxiedAttributionTag, int proxyUid,
+ @Nullable String proxyPackageName, @Nullable String proxyAttributionTag) {
+ verifyIncomingUid(proxyUid);
+ verifyIncomingOp(code);
+ verifyIncomingPackage(proxyPackageName, UserHandle.getUserId(proxyUid));
+ verifyIncomingPackage(proxiedPackageName, UserHandle.getUserId(proxiedUid));
+
+ String resolvedProxyPackageName = resolvePackageName(proxyUid, proxyPackageName);
+ if (resolvedProxyPackageName == null) {
+ return;
+ }
+
+ finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
+ proxyAttributionTag);
+
+ String resolvedProxiedPackageName = resolvePackageName(proxiedUid, proxiedPackageName);
+ if (resolvedProxiedPackageName == null) {
+ return;
+ }
+
+ finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag);
+ }
+
+ private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
RestrictionBypass bypass;
try {
bypass = verifyAndGetBypass(uid, packageName, attributionTag);
@@ -3571,7 +3677,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
synchronized (this) {
- Op op = getOpLocked(code, uid, resolvedPackageName, attributionTag, bypass, true);
+ Op op = getOpLocked(code, uid, packageName, attributionTag, bypass, true);
if (op == null) {
Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+ attributionTag + ") op=" + AppOpsManager.opToName(code));
@@ -3784,33 +3890,6 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
- private void verifyIncomingUser(@UserIdInt int userId) {
- int callingUid = Binder.getCallingUid();
- int callingUserId = UserHandle.getUserId(callingUid);
- int callingPid = Binder.getCallingPid();
-
- if (callingUserId != userId) {
- // Prevent endless loop between when checking appops inside of handleIncomingUser
- if (Binder.getCallingPid() == ActivityManagerService.MY_PID) {
- return;
- }
- long token = Binder.clearCallingIdentity();
- try {
- try {
- LocalServices.getService(ActivityManagerInternal.class).handleIncomingUser(
- callingPid, callingUid, userId, /* allowAll */ false,
- ALLOW_ACROSS_PROFILES_IN_PROFILE_OR_NON_FULL, "appop operation", null);
- } catch (Exception e) {
- EventLog.writeEvent(0x534e4554, "153996875", "appop", userId);
-
- throw e;
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-
private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
@@ -5034,7 +5113,7 @@ public class AppOpsService extends IAppOpsService.Stub {
case "write-settings": {
shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
synchronized (shell.mInternal) {
shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
@@ -5049,7 +5128,7 @@ public class AppOpsService extends IAppOpsService.Stub {
case "read-settings": {
shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
shell.mInternal.readState();
pw.println("Last settings read.");
@@ -5890,11 +5969,8 @@ public class AppOpsService extends IAppOpsService.Stub {
return false;
}
}
- int userId = UserHandle.getUserId(uid);
-
verifyIncomingOp(code);
- verifyIncomingUser(userId);
- verifyIncomingPackage(packageName, userId);
+ verifyIncomingPackage(packageName, UserHandle.getUserId(uid));
final String resolvedPackageName = resolvePackageName(uid, packageName);
if (resolvedPackageName == null) {
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index a3e1b7a7e5c5..84de25c06ebf 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -7,9 +7,6 @@
"name": "CtsAppOps2TestCases"
},
{
- "name": "CtsAppOpHostTestCases"
- },
- {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index e128d993ab63..68cfc23699ce 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -26,6 +26,7 @@ import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNO
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.content.BroadcastReceiver;
@@ -68,6 +69,7 @@ import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.Set;
/**
* An attention service implementation that runs in System Server process.
@@ -82,11 +84,15 @@ public class AttentionManagerService extends SystemService {
private static final long CONNECTION_TTL_MILLIS = 60_000;
/** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */
- private static final String KEY_SERVICE_ENABLED = "service_enabled";
+ @VisibleForTesting
+ static final String KEY_SERVICE_ENABLED = "service_enabled";
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
+ @VisibleForTesting
+ boolean mIsServiceEnabled;
+
/**
* DeviceConfig flag name, describes how much time we consider a result fresh; if the check
* attention called within that period - cached value will be returned.
@@ -98,6 +104,9 @@ public class AttentionManagerService extends SystemService {
@VisibleForTesting
static final long DEFAULT_STALE_AFTER_MILLIS = 1_000;
+ @VisibleForTesting
+ long mStaleAfterMillis;
+
/** The size of the buffer that stores recent attention check results. */
@VisibleForTesting
protected static final int ATTENTION_CACHE_BUFFER_SIZE = 5;
@@ -144,6 +153,11 @@ public class AttentionManagerService extends SystemService {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mContext.registerReceiver(new ScreenStateReceiver(),
new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
+ readValuesFromDeviceConfig();
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ ActivityThread.currentApplication().getMainExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
}
}
@@ -167,15 +181,7 @@ public class AttentionManagerService extends SystemService {
return mComponentName != null;
}
- /**
- * Returns {@code true} if attention service is supported on this device.
- */
- @VisibleForTesting
- protected boolean isAttentionServiceSupported() {
- return isServiceEnabled() && isServiceConfigured(mContext);
- }
-
- private boolean isServiceEnabled() {
+ private boolean getIsServiceEnabled() {
return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_SERVICE_ENABLED,
DEFAULT_SERVICE_ENABLED);
}
@@ -198,6 +204,28 @@ public class AttentionManagerService extends SystemService {
return millis;
}
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ for (String key : keys) {
+ switch (key) {
+ case KEY_SERVICE_ENABLED:
+ case KEY_STALE_AFTER_MILLIS:
+ readValuesFromDeviceConfig();
+ return;
+ default:
+ Slog.i(LOG_TAG, "Ignoring change on " + key);
+ }
+ }
+ }
+
+ private void readValuesFromDeviceConfig() {
+ mIsServiceEnabled = getIsServiceEnabled();
+ mStaleAfterMillis = getStaleAfterMillis();
+
+ Slog.i(LOG_TAG, "readValuesFromDeviceConfig():"
+ + "\nmIsServiceEnabled=" + mIsServiceEnabled
+ + "\nmStaleAfterMillis=" + mStaleAfterMillis);
+ }
+
/**
* Checks whether user attention is at the screen and calls in the provided callback.
*
@@ -211,7 +239,7 @@ public class AttentionManagerService extends SystemService {
boolean checkAttention(long timeout, AttentionCallbackInternal callbackInternal) {
Objects.requireNonNull(callbackInternal);
- if (!isAttentionServiceSupported()) {
+ if (!mIsServiceEnabled) {
Slog.w(LOG_TAG, "Trying to call checkAttention() on an unsupported device.");
return false;
}
@@ -237,7 +265,7 @@ public class AttentionManagerService extends SystemService {
// throttle frequent requests
final AttentionCheckCache cache = mAttentionCheckCacheBuffer == null ? null
: mAttentionCheckCacheBuffer.getLast();
- if (cache != null && now < cache.mLastComputed + getStaleAfterMillis()) {
+ if (cache != null && now < cache.mLastComputed + mStaleAfterMillis) {
callbackInternal.onSuccess(cache.mResult, cache.mTimestamp);
return true;
}
@@ -344,7 +372,8 @@ public class AttentionManagerService extends SystemService {
private void dumpInternal(IndentingPrintWriter ipw) {
ipw.println("Attention Manager Service (dumpsys attention) state:\n");
- ipw.println("isServiceEnabled=" + isServiceEnabled());
+ ipw.println("isServiceEnabled=" + mIsServiceEnabled);
+ ipw.println("mStaleAfterMillis=" + mStaleAfterMillis);
ipw.println("AttentionServicePackageName=" + getServiceConfigPackage(mContext));
ipw.println("Resolved component:");
if (mComponentName != null) {
@@ -368,7 +397,7 @@ public class AttentionManagerService extends SystemService {
private final class LocalService extends AttentionManagerInternal {
@Override
public boolean isAttentionServiceSupported() {
- return AttentionManagerService.this.isAttentionServiceSupported();
+ return AttentionManagerService.this.mIsServiceEnabled;
}
@Override
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 535d1967fbe0..f36603408c4d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -25,6 +25,8 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
@@ -39,6 +41,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -46,8 +49,8 @@ import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -73,10 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** Forced device usage for communications sent to AudioSystem */
private int mForcedUseForComm;
- /**
- * Externally reported force device usage state returned by getters: always consistent
- * with requests by setters */
- private int mForcedUseForCommExt;
// Manages all connected devices, only ever accessed on the message loop
private final AudioDeviceInventory mDeviceInventory;
@@ -136,7 +135,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
setupMessaging(mContext);
mForcedUseForComm = AudioSystem.FORCE_NONE;
- mForcedUseForCommExt = mForcedUseForComm;
}
/*package*/ Context getContext() {
@@ -159,15 +157,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
/*package*/ void onAudioServerDied() {
- // Restore forced usage for communications and record
- synchronized (mDeviceStateLock) {
- AudioSystem.setParameters(
- "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
- onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm,
- false /*fromA2dp*/, "onAudioServerDied");
- onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm,
- false /*fromA2dp*/, "onAudioServerDied");
- }
// restore devices
sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
}
@@ -219,91 +208,156 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Turns speakerphone on/off
* @param on
* @param eventSource for logging purposes
- * @return true if speakerphone state changed
*/
- /*package*/ boolean setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
- synchronized (mDeviceStateLock) {
- if (!addSpeakerphoneClient(cb, pid, on)) {
- return false;
- }
- if (on) {
- // Cancel BT SCO ON request by this same client: speakerphone and BT SCO routes
- // are mutually exclusive.
- // See symmetrical operation for startBluetoothScoForClient_Sync().
- mBtHelper.stopBluetoothScoForPid(pid);
+ /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) {
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
+ }
+
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ AudioDeviceAttributes device = null;
+ if (on) {
+ device = new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+ } else {
+ CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
+ if (client == null || !client.requestsSpeakerphone()) {
+ return;
+ }
+ }
+ setCommunicationRouteForClient(
+ cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource);
}
- final boolean wasOn = isSpeakerphoneOn();
- updateSpeakerphoneOn(eventSource);
- return (wasOn != isSpeakerphoneOn());
}
}
- /**
- * Turns speakerphone off for a given pid and update speakerphone state.
- * @param pid
- */
@GuardedBy("mDeviceStateLock")
- private void setSpeakerphoneOffForPid(int pid) {
- SpeakerphoneClient client = getSpeakerphoneClientForPid(pid);
+ /*package*/ void setCommunicationRouteForClient(
+ IBinder cb, int pid, AudioDeviceAttributes device,
+ int scoAudioMode, String eventSource) {
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
+ }
+
+ final boolean wasBtScoRequested = isBluetoothScoRequested();
+ final boolean wasSpeakerphoneRequested = isSpeakerphoneRequested();
+ CommunicationRouteClient client;
+
+
+ // Save previous client route in case of failure to start BT SCO audio
+ AudioDeviceAttributes prevClientDevice = null;
+ client = getCommunicationRouteClientForPid(pid);
+ if (client != null) {
+ prevClientDevice = client.getDevice();
+ }
+
+ if (device != null) {
+ client = addCommunicationRouteClient(cb, pid, device);
+ if (client == null) {
+ Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: "
+ + pid + " and device: " + device);
+ }
+ } else {
+ client = removeCommunicationRouteClient(cb, true);
+ }
if (client == null) {
return;
}
- client.unregisterDeathRecipient();
- mSpeakerphoneClients.remove(client);
- final String eventSource = new StringBuilder("setSpeakerphoneOffForPid(")
- .append(pid).append(")").toString();
- updateSpeakerphoneOn(eventSource);
- }
- @GuardedBy("mDeviceStateLock")
- private void updateSpeakerphoneOn(String eventSource) {
- if (isSpeakerphoneOnRequested()) {
- if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
- }
- mForcedUseForComm = AudioSystem.FORCE_SPEAKER;
- } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
- if (mBtHelper.isBluetoothScoOn()) {
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- setForceUse_Async(
- AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
- } else {
- mForcedUseForComm = AudioSystem.FORCE_NONE;
+ boolean isBtScoRequested = isBluetoothScoRequested();
+ if (isBtScoRequested && !wasBtScoRequested) {
+ if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
+ Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
+ + pid);
+ // clean up or restore previous client selection
+ if (prevClientDevice != null) {
+ addCommunicationRouteClient(cb, pid, prevClientDevice);
+ } else {
+ removeCommunicationRouteClient(cb, true);
+ }
}
+ } else if (!isBtScoRequested && wasBtScoRequested) {
+ mBtHelper.stopBluetoothSco(eventSource);
}
- mForcedUseForCommExt = mForcedUseForComm;
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In updateSpeakerphoneOn(), mForcedUseForCommExt: " + mForcedUseForCommExt);
+
+ if (wasSpeakerphoneRequested != isSpeakerphoneRequested()) {
+ try {
+ mContext.sendBroadcastAsUser(
+ new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
+ } catch (Exception e) {
+ Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
+ }
}
- setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
+
+ sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
}
/**
- * Returns if speakerphone is requested ON or OFF.
- * If the current audio mode owner is in the speakerphone client list, use this preference.
+ * Returns the device currently requested for communication use case.
+ * If the current audio mode owner is in the communication route client list,
+ * use this preference.
* Otherwise use first client's preference (first client corresponds to latest request).
- * Speakerphone is requested OFF if no client is in the list.
- * @return true if speakerphone is requested ON, false otherwise
+ * null is returned if no client is in the list.
+ * @return AudioDeviceAttributes the requested device for communication.
*/
+
@GuardedBy("mDeviceStateLock")
- private boolean isSpeakerphoneOnRequested() {
- if (mSpeakerphoneClients.isEmpty()) {
- return false;
- }
- for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+ private AudioDeviceAttributes requestedCommunicationDevice() {
+ AudioDeviceAttributes device = null;
+ for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getPid() == mModeOwnerPid) {
- return cl.isOn();
+ device = cl.getDevice();
}
}
- return mSpeakerphoneClients.get(0).isOn();
+ if (!mCommunicationRouteClients.isEmpty() && mModeOwnerPid == 0) {
+ device = mCommunicationRouteClients.get(0).getDevice();
+ }
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "requestedCommunicationDevice, device: "
+ + device + " mode owner pid: " + mModeOwnerPid);
+ }
+ return device;
+ }
+
+ /**
+ * Helper method on top of requestedCommunicationDevice() indicating if
+ * speakerphone ON is currently requested or not.
+ * @return true if speakerphone ON requested, false otherwise.
+ */
+
+ private boolean isSpeakerphoneRequested() {
+ synchronized (mDeviceStateLock) {
+ AudioDeviceAttributes device = requestedCommunicationDevice();
+ return device != null
+ && device.getType()
+ == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ }
}
+ /**
+ * Indicates if active route selection for communication is speakerphone.
+ * @return true if speakerphone is active, false otherwise.
+ */
/*package*/ boolean isSpeakerphoneOn() {
+ return getForcedUseForComm() == AudioSystem.FORCE_SPEAKER;
+ }
+
+ /**
+ * Helper method on top of requestedCommunicationDevice() indicating if
+ * Bluetooth SCO ON is currently requested or not.
+ * @return true if Bluetooth SCO ON is requested, false otherwise.
+ */
+ /*package*/ boolean isBluetoothScoRequested() {
synchronized (mDeviceStateLock) {
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In isSpeakerphoneOn(), mForcedUseForCommExt: " +mForcedUseForCommExt);
- }
- return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
+ AudioDeviceAttributes device = requestedCommunicationDevice();
+ return device != null
+ && device.getType()
+ == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
}
}
@@ -354,7 +408,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
-
/*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int a2dpVolume) {
@@ -446,61 +499,28 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
}
- // never called by system components
- /*package*/ void setBluetoothScoOnByApp(boolean on) {
- synchronized (mDeviceStateLock) {
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In setBluetoothScoOnByApp(), mForcedUseForCommExt: " +
- mForcedUseForCommExt);
- }
- mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
- }
- }
- /*package*/ boolean isBluetoothScoOnForApp() {
- synchronized (mDeviceStateLock) {
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In isBluetoothScoOnForApp(), mForcedUseForCommExt: " +
- mForcedUseForCommExt);
- }
- return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
- }
- }
+ /**
+ * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
+ */
+ private boolean mBluetoothScoOn;
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
}
- //Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
synchronized (mDeviceStateLock) {
- if (on) {
- // do not accept SCO ON if SCO audio is not connected
- if (!mBtHelper.isBluetoothScoOn()) {
- if (mBtHelper.isBluetoothAudioNotConnectedToEarbud()) {
- Log.w(TAG, "setBluetoothScoOn(true) failed because device "+
- "is not in audio connected mode");
- mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
- return;
- }
- }
- mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
- } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
- mForcedUseForComm = isSpeakerphoneOnRequested()
- ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE;
- }
- mForcedUseForCommExt = mForcedUseForComm;
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In setbluetoothScoOn(), mForcedUseForCommExt: " +
- mForcedUseForCommExt);
- }
- AudioSystem.setParameters("BT_SCO=" + (on ? "on" : "off"));
- sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
- sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
- AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource);
+ mBluetoothScoOn = on;
+ sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
}
- // Un-mute ringtone stream volume
- mAudioService.postUpdateRingerModeServiceInt();
+ }
+
+ /**
+ * Indicates if active route selection for communication is Bluetooth SCO.
+ * @return true if Bluetooth SCO is active , false otherwise.
+ */
+ /*package*/ boolean isBluetoothScoOn() {
+ return getForcedUseForComm() == AudioSystem.FORCE_BT_SCO;
}
/*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
@@ -543,22 +563,43 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
}
- @GuardedBy("mSetModeLock")
- /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
+ /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@NonNull String eventSource) {
- synchronized (mDeviceStateLock) {
- // Cancel speakerphone ON request by this same client: speakerphone and BT SCO routes
- // are mutually exclusive.
- // See symmetrical operation for setSpeakerphoneOn(true).
- setSpeakerphoneOffForPid(Binder.getCallingPid());
- mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
+ }
+
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ AudioDeviceAttributes device = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_SCO, "");
+ setCommunicationRouteForClient(cb, pid, device, scoAudioMode, eventSource);
+ if (!isBluetoothScoRequested()) {
+ Log.w(TAG, "startBluetoothScoForClient_Sync: rejected for pid: "
+ + pid + " mode owner pid: " + mModeOwnerPid);
+ postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ }
}
}
- @GuardedBy("mSetModeLock")
- /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
- synchronized (mDeviceStateLock) {
- mBtHelper.stopBluetoothScoForClient(cb, eventSource);
+ /*package*/ void stopBluetoothScoForClient(
+ IBinder cb, int pid, @NonNull String eventSource) {
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
+ }
+
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
+ if (client == null || !client.requestsBluetoothSco()) {
+ return;
+ }
+ setCommunicationRouteForClient(
+ cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource);
+ }
}
}
@@ -730,12 +771,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
hearingAidProfile);
}
- /*package*/ void postScoClientDied(Object obj) {
- sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
- }
-
- /*package*/ void postSpeakerphoneClientDied(Object obj) {
- sendLMsgNoDelay(MSG_L_SPEAKERPHONE_CLIENT_DIED, SENDMSG_QUEUE, obj);
+ /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) {
+ sendLMsgNoDelay(MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED, SENDMSG_QUEUE, client);
}
/*package*/ void postSaveSetPreferredDevicesForStrategy(int strategy,
@@ -855,15 +892,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.dump(pw, prefix);
+ pw.println("\n" + prefix + "Communication route clients:");
+ mCommunicationRouteClients.forEach((cl) -> {
+ pw.println(" " + prefix + "pid: " + cl.getPid() + " device: "
+ + cl.getDevice() + " cb: " + cl.getBinder()); });
+
pw.println("\n" + prefix + "mForcedUseForComm: "
+ AudioSystem.forceUseConfigToString(mForcedUseForComm));
- pw.println(prefix + "mForcedUseForCommExt: "
- + AudioSystem.forceUseConfigToString(mForcedUseForCommExt));
pw.println(prefix + "mModeOwnerPid: " + mModeOwnerPid);
- pw.println(prefix + "Speakerphone clients:");
- mSpeakerphoneClients.forEach((cl) -> {
- pw.println(" " + prefix + "pid: " + cl.getPid() + " on: "
- + cl.isOn() + " cb: " + cl.getBinder()); });
mBtHelper.dump(pw, prefix);
}
@@ -886,6 +922,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
.set(MediaMetrics.Property.FORCE_USE_MODE,
AudioSystem.forceUseConfigToString(config))
.record();
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onSetForceUse(useCase<" + useCase + ">, config<" + config + ">, fromA2dp<"
+ + fromA2dp + ">, eventSource<" + eventSource + ">)");
+ }
AudioSystem.setForceUse(useCase, config);
}
@@ -951,9 +992,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESTORE_DEVICES:
- synchronized (mDeviceStateLock) {
- mDeviceInventory.onRestoreDevices();
- mBtHelper.onAudioServerDiedRestoreA2dp();
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.onRestoreDevices();
+ mBtHelper.onAudioServerDiedRestoreA2dp();
+ onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
+ }
}
break;
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
@@ -1043,25 +1087,24 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (mModeOwnerPid != msg.arg1) {
mModeOwnerPid = msg.arg1;
if (msg.arg2 != AudioSystem.MODE_RINGTONE) {
- updateSpeakerphoneOn("setNewModeOwner");
- }
- if (mModeOwnerPid != 0) {
- mBtHelper.disconnectBluetoothSco(mModeOwnerPid);
+ onUpdateCommunicationRoute("setNewModeOwner");
}
}
}
}
break;
- case MSG_L_SCOCLIENT_DIED:
+ case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- mBtHelper.scoClientDied(msg.obj);
+ onCommunicationRouteClientDied((CommunicationRouteClient) msg.obj);
}
}
break;
- case MSG_L_SPEAKERPHONE_CLIENT_DIED:
- synchronized (mDeviceStateLock) {
- speakerphoneClientDied(msg.obj);
+ case MSG_L_UPDATE_COMMUNICATION_ROUTE:
+ synchronized (mSetModeLock) {
+ synchronized (mDeviceStateLock) {
+ onUpdateCommunicationRoute((String) msg.obj);
+ }
}
break;
case MSG_TOGGLE_HDMI:
@@ -1257,17 +1300,17 @@ import java.util.concurrent.atomic.AtomicBoolean;
// process external command to (dis)connect a hearing aid device
private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
- // a ScoClient died in BtHelper
- private static final int MSG_L_SCOCLIENT_DIED = 32;
- private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 33;
- private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 34;
+ private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY = 32;
+ private static final int MSG_I_SAVE_REMOVE_PREF_DEVICES_FOR_STRATEGY = 33;
+
+ private static final int MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED = 34;
+ private static final int MSG_CHECK_MUTE_MUSIC = 35;
+ private static final int MSG_REPORT_NEW_ROUTES_A2DP = 36;
- private static final int MSG_L_SPEAKERPHONE_CLIENT_DIED = 35;
- private static final int MSG_CHECK_MUTE_MUSIC = 36;
- private static final int MSG_REPORT_NEW_ROUTES_A2DP = 37;
+ private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
+ private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
- private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
- private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 39;
+ private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
// process external command to (dis)connect or change active A2DP device
private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE_EXT = 64;
@@ -1426,14 +1469,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
- private class SpeakerphoneClient implements IBinder.DeathRecipient {
+ // List of applications requesting a specific route for communication.
+ @GuardedBy("mDeviceStateLock")
+ private final @NonNull LinkedList<CommunicationRouteClient> mCommunicationRouteClients =
+ new LinkedList<CommunicationRouteClient>();
+
+ private class CommunicationRouteClient implements IBinder.DeathRecipient {
private final IBinder mCb;
private final int mPid;
- private final boolean mOn;
- SpeakerphoneClient(IBinder cb, int pid, boolean on) {
+ private AudioDeviceAttributes mDevice;
+
+ CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) {
mCb = cb;
mPid = pid;
- mOn = on;
+ mDevice = device;
}
public boolean registerDeathRecipient() {
@@ -1442,7 +1491,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mCb.linkToDeath(this, 0);
status = true;
} catch (RemoteException e) {
- Log.w(TAG, "SpeakerphoneClient could not link to " + mCb + " binder death");
+ Log.w(TAG, "CommunicationRouteClient could not link to " + mCb + " binder death");
}
return status;
}
@@ -1451,13 +1500,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
try {
mCb.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
- Log.w(TAG, "SpeakerphoneClient could not not unregistered to binder");
+ Log.w(TAG, "CommunicationRouteClient could not not unregistered to binder");
}
}
@Override
public void binderDied() {
- postSpeakerphoneClientDied(this);
+ postCommunicationRouteClientDied(this);
}
IBinder getBinder() {
@@ -1468,29 +1517,99 @@ import java.util.concurrent.atomic.AtomicBoolean;
return mPid;
}
- boolean isOn() {
- return mOn;
+ AudioDeviceAttributes getDevice() {
+ return mDevice;
+ }
+
+ boolean requestsBluetoothSco() {
+ return mDevice != null
+ && mDevice.getType()
+ == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
+ }
+
+ boolean requestsSpeakerphone() {
+ return mDevice != null
+ && mDevice.getType()
+ == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
}
}
+ // @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void speakerphoneClientDied(Object obj) {
- if (obj == null) {
+ private void onCommunicationRouteClientDied(CommunicationRouteClient client) {
+ if (client == null) {
return;
}
Log.w(TAG, "Speaker client died");
- if (removeSpeakerphoneClient(((SpeakerphoneClient) obj).getBinder(), false) != null) {
- updateSpeakerphoneOn("speakerphoneClientDied");
+ if (removeCommunicationRouteClient(client.getBinder(), false)
+ != null) {
+ onUpdateCommunicationRoute("onCommunicationRouteClientDied");
+ }
+ }
+
+ /**
+ * Determines which forced usage for communication should be sent to audio policy manager
+ * as a function of current SCO audio activation state and active communication route requests.
+ * SCO audio state has the highest priority as it can result from external activation by
+ * telephony service.
+ * @return selected forced usage for communication.
+ */
+ @GuardedBy("mDeviceStateLock")
+ private int getForcedUseForComm() {
+ boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn();
+
+ if (btSCoOn) {
+ return AudioSystem.FORCE_BT_SCO;
+ }
+ if (isSpeakerphoneRequested()) {
+ return AudioSystem.FORCE_SPEAKER;
+ }
+ return AudioSystem.FORCE_NONE;
+ }
+
+ /**
+ * Configures audio policy manager and audio HAL according to active communication route.
+ * Always called from message Handler.
+ */
+ // @GuardedBy("mSetModeLock")
+ @GuardedBy("mDeviceStateLock")
+ private void onUpdateCommunicationRoute(String eventSource) {
+ mForcedUseForComm = getForcedUseForComm();
+
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "onUpdateCommunicationRoute, mForcedUseForComm: " + mForcedUseForComm
+ + " eventSource: " + eventSource);
+ }
+
+ if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
+ AudioSystem.setParameters("BT_SCO=on");
+ setForceUse_Async(
+ AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_BT_SCO, eventSource);
+ setForceUse_Async(
+ AudioSystem.FOR_RECORD, AudioSystem.FORCE_BT_SCO, eventSource);
+ } else {
+ AudioSystem.setParameters("BT_SCO=off");
+ setForceUse_Async(
+ AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource);
+ if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) {
+ setForceUse_Async(
+ AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_SPEAKER, eventSource);
+ } else {
+ setForceUse_Async(
+ AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_NONE, eventSource);
+ }
}
+ mAudioService.postUpdateRingerModeServiceInt();
}
- private SpeakerphoneClient removeSpeakerphoneClient(IBinder cb, boolean unregister) {
- for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+ private CommunicationRouteClient removeCommunicationRouteClient(
+ IBinder cb, boolean unregister) {
+ for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getBinder() == cb) {
if (unregister) {
cl.unregisterDeathRecipient();
}
- mSpeakerphoneClients.remove(cl);
+ mCommunicationRouteClients.remove(cl);
return cl;
}
}
@@ -1498,30 +1617,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
@GuardedBy("mDeviceStateLock")
- private boolean addSpeakerphoneClient(IBinder cb, int pid, boolean on) {
+ private CommunicationRouteClient addCommunicationRouteClient(
+ IBinder cb, int pid, AudioDeviceAttributes device) {
// always insert new request at first position
- removeSpeakerphoneClient(cb, true);
- SpeakerphoneClient client = new SpeakerphoneClient(cb, pid, on);
+ removeCommunicationRouteClient(cb, true);
+ CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device);
if (client.registerDeathRecipient()) {
- mSpeakerphoneClients.add(0, client);
- return true;
+ mCommunicationRouteClients.add(0, client);
+ return client;
}
- return false;
+ return null;
}
@GuardedBy("mDeviceStateLock")
- private SpeakerphoneClient getSpeakerphoneClientForPid(int pid) {
- for (SpeakerphoneClient cl : mSpeakerphoneClients) {
+ private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) {
+ for (CommunicationRouteClient cl : mCommunicationRouteClients) {
if (cl.getPid() == pid) {
return cl;
}
}
return null;
}
-
- // List of clients requesting speakerPhone ON
- @GuardedBy("mDeviceStateLock")
- private final @NonNull ArrayList<SpeakerphoneClient> mSpeakerphoneClients =
- new ArrayList<SpeakerphoneClient>();
-
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 874a7d36e475..31e78397cf5d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -207,6 +207,9 @@ public class AudioService extends IAudioService.Stub
/** debug calls to devices APIs */
protected static final boolean DEBUG_DEVICES = false;
+ /** Debug communication route */
+ protected static final boolean DEBUG_COMM_RTE = false;
+
/** debug SCO modes */
protected static final boolean DEBUG_SCO = true;
@@ -300,6 +303,7 @@ public class AudioService extends IAudioService.Stub
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
+ private static final int MSG_INIT_STREAMS_VOLUMES = 101;
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -825,7 +829,34 @@ public class AudioService extends IAudioService.Stub
updateStreamVolumeAlias(false /*updateVolumes*/, TAG);
readPersistedSettings();
readUserRestrictions();
- mSettingsObserver = new SettingsObserver();
+
+ mPlaybackMonitor =
+ new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+ mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
+
+ readAndSetLowRamDevice();
+
+ mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported();
+
+ if (mSystemServer.isPrivileged()) {
+ LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
+
+ mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+
+ mRecordMonitor.initMonitor();
+ }
+
+ mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
+
+ // done with service initialization, continue additional work in our Handler thread
+ queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
+ }
+
+ /**
+ * Called by handling of MSG_INIT_STREAMS_VOLUMES
+ */
+ private void onInitStreamsAndVolumes() {
createStreamStates();
// must be called after createStreamStates() as it uses MUSIC volume as default if no
@@ -836,20 +867,42 @@ public class AudioService extends IAudioService.Stub
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
- mPlaybackMonitor =
- new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
-
- mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
-
- readAndSetLowRamDevice();
-
- mIsCallScreeningModeSupported = AudioSystem.isCallScreeningModeSupported();
-
// Call setRingerModeInt() to apply correct mute
// state on streams affected by ringer mode.
mRingerAndZenModeMutedStreams = 0;
setRingerModeInt(getRingerModeInternal(), false);
+ final float[] preScale = new float[3];
+ preScale[0] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
+ 1, 1);
+ preScale[1] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2,
+ 1, 1);
+ preScale[2] = mContext.getResources().getFraction(
+ com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3,
+ 1, 1);
+ for (int i = 0; i < preScale.length; i++) {
+ if (0.0f <= preScale[i] && preScale[i] <= 1.0f) {
+ mPrescaleAbsoluteVolume[i] = preScale[i];
+ }
+ }
+
+ initExternalEventReceivers();
+
+ // check on volume initialization
+ checkVolumeRangeInitialization("AudioService()");
+ }
+
+ /**
+ * Initialize intent receives and settings observers for this service.
+ * Must be called after createStreamStates() as the handling of some events
+ * may affect or need volumes, e.g. BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED
+ * (for intent receiver), or Settings.Global.ZEN_MODE (for settings observer)
+ */
+ private void initExternalEventReceivers() {
+ mSettingsObserver = new SettingsObserver();
+
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
@@ -865,7 +918,6 @@ public class AudioService extends IAudioService.Stub
intentFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
if (mMonitorRotation) {
RotationHelper.init(mContext, mAudioHandler);
}
@@ -873,34 +925,8 @@ public class AudioService extends IAudioService.Stub
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intentFilter.addAction(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
- context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
-
- if (mSystemServer.isPrivileged()) {
- LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
-
- mUserManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
- mRecordMonitor.initMonitor();
- }
-
- final float[] preScale = new float[3];
- preScale[0] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1,
- 1, 1);
- preScale[1] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2,
- 1, 1);
- preScale[2] = mContext.getResources().getFraction(
- com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3,
- 1, 1);
- for (int i = 0; i < preScale.length; i++) {
- if (0.0f <= preScale[i] && preScale[i] <= 1.0f) {
- mPrescaleAbsoluteVolume[i] = preScale[i];
- }
- }
-
- // check on volume initialization
- checkVolumeRangeInitialization("AudioService()");
}
public void systemReady() {
@@ -2601,7 +2627,7 @@ public class AudioService extends IAudioService.Stub
// StreamVolumeCommand contains the information needed to defer the process of
// setStreamVolume() in case the user has to acknowledge the safe volume warning message.
- class StreamVolumeCommand {
+ static class StreamVolumeCommand {
public final int mStreamType;
public final int mIndex;
public final int mFlags;
@@ -2620,7 +2646,7 @@ public class AudioService extends IAudioService.Stub
.append(mIndex).append(",flags=").append(mFlags).append(",device=")
.append(mDevice).append('}').toString();
}
- };
+ }
private int getNewRingerMode(int stream, int index, int flags) {
// setRingerMode does nothing if the device is single volume,so the value would be unchanged
@@ -3330,7 +3356,7 @@ public class AudioService extends IAudioService.Stub
}
private int mRmtSbmxFullVolRefCount = 0;
- private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
+ private final ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
new ArrayList<RmtSbmxFullVolDeathHandler>();
public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) {
@@ -3751,7 +3777,7 @@ public class AudioService extends IAudioService.Stub
final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
|| ringerMode == AudioManager.RINGER_MODE_SILENT;
final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- && isBluetoothScoOn();
+ && mDeviceBroker.isBluetoothScoOn();
// Ask audio policy engine to force use Bluetooth SCO channel if needed
final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
+ "/" + Binder.getCallingPid();
@@ -4395,12 +4421,12 @@ public class AudioService extends IAudioService.Stub
// for logging only
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
+
final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
Log.i(TAG, "In setSpeakerphoneOn(), on: " + on + ", eventSource: " + eventSource);
- final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+ MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
.setUid(uid)
@@ -4408,17 +4434,9 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.STATE, on
? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
.record();
-
- if (stateChanged) {
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendBroadcastAsUser(
- new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
- .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
+ final long ident = Binder.clearCallingIdentity();
+ mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource);
+ Binder.restoreCallingIdentity(ident);
}
/** @see AudioManager#isSpeakerphoneOn() */
@@ -4426,6 +4444,11 @@ public class AudioService extends IAudioService.Stub
return mDeviceBroker.isSpeakerphoneOn();
}
+
+ /** BT SCO audio state seen by apps using the deprecated API setBluetoothScoOn().
+ * @see isBluetoothScoOn() */
+ private boolean mBtScoOnByApp;
+
/** @see AudioManager#setBluetoothScoOn(boolean) */
public void setBluetoothScoOn(boolean on) {
if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
@@ -4436,7 +4459,7 @@ public class AudioService extends IAudioService.Stub
Log.i(TAG, "In setBluetoothScoOn(), on: "+on+". The calling application Uid: "
+ Binder.getCallingUid() + ", is greater than FIRST_APPLICATION_UID"
+ " exiting from setBluetoothScoOn()");
- mDeviceBroker.setBluetoothScoOnByApp(on);
+ mBtScoOnByApp = on;
return;
}
@@ -4463,7 +4486,7 @@ public class AudioService extends IAudioService.Stub
* Note that it doesn't report internal state, but state seen by apps (which may have
* called setBluetoothScoOn() */
public boolean isBluetoothScoOn() {
- return mDeviceBroker.isBluetoothScoOnForApp();
+ return mBtScoOnByApp || mDeviceBroker.isBluetoothScoOn();
}
// TODO investigate internal users due to deprecation of SDK API
@@ -4516,7 +4539,7 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(scoAudioMode))
.record();
- startBluetoothScoInt(cb, scoAudioMode, eventSource);
+ startBluetoothScoInt(cb, pid, scoAudioMode, eventSource);
}
@@ -4536,10 +4559,10 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.SCO_AUDIO_MODE,
BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
.record();
- startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
+ startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
}
- void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
+ void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) {
Log.i(TAG, "In startBluetoothScoInt(), scoAudioMode: " + scoAudioMode);
MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
@@ -4551,9 +4574,9 @@ public class AudioService extends IAudioService.Stub
mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
return;
}
- synchronized (mDeviceBroker.mSetModeLock) {
- mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
- }
+ final long ident = Binder.clearCallingIdentity();
+ mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource);
+ Binder.restoreCallingIdentity(ident);
mmi.record();
}
@@ -4574,9 +4597,9 @@ public class AudioService extends IAudioService.Stub
final String eventSource = new StringBuilder("stopBluetoothSco()")
.append(") from u/pid:").append(uid).append("/")
.append(pid).toString();
- synchronized (mDeviceBroker.mSetModeLock) {
- mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
- }
+ final long ident = Binder.clearCallingIdentity();
+ mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource);
+ Binder.restoreCallingIdentity(ident);
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
.setUid(uid)
.setPid(pid)
@@ -5932,7 +5955,6 @@ public class AudioService extends IAudioService.Stub
private final Intent mStreamDevicesChanged;
private VolumeStreamState(String settingName, int streamType) {
-
mVolumeIndexSettingName = settingName;
mStreamType = streamType;
@@ -6708,6 +6730,11 @@ public class AudioService extends IAudioService.Stub
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_STREAMS_VOLUMES:
+ onInitStreamsAndVolumes();
+ mAudioEventWakeLock.release();
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -6785,14 +6812,17 @@ public class AudioService extends IAudioService.Stub
if (msg.obj == null) {
break;
}
- // If the app corresponding to this mode death handler object is not
- // capturing or playing audio anymore after 3 seconds, remove it
- // from the stack. Otherwise, check again in 3 seconds.
+ // If no other app is currently owning the audio mode and
+ // the app corresponding to this mode death handler object is still in the
+ // mode owner stack but not capturing or playing audio after 3 seconds,
+ // remove it from the stack.
+ // Otherwise, check again in 3 seconds.
SetModeDeathHandler h = (SetModeDeathHandler) msg.obj;
if (mSetModeDeathHandlers.indexOf(h) < 0) {
break;
}
- if (mRecordMonitor.isRecordingActiveForUid(h.getUid())
+ if (getModeOwnerUid() != h.getUid()
+ || mRecordMonitor.isRecordingActiveForUid(h.getUid())
|| mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) {
sendMsg(mAudioHandler,
MSG_CHECK_MODE_FOR_UID,
@@ -7867,6 +7897,7 @@ public class AudioService extends IAudioService.Stub
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
pw.print(" mAvrcpAbsVolSupported="); pw.println(mAvrcpAbsVolSupported);
+ pw.print(" mBtScoOnByApp="); pw.println(mBtScoOnByApp);
pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume);
pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume);
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
@@ -9263,7 +9294,7 @@ public class AudioService extends IAudioService.Stub
}
}
- private HashMap<IBinder, AsdProxy> mAudioServerStateListeners =
+ private final HashMap<IBinder, AsdProxy> mAudioServerStateListeners =
new HashMap<IBinder, AsdProxy>();
private void checkMonitorAudioServerStatePermission() {
@@ -9404,7 +9435,7 @@ public class AudioService extends IAudioService.Stub
if (DEBUG_VOL) {
Log.d(TAG, "Persisting Volume Behavior for DeviceType: " + deviceType);
}
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
System.putIntForUser(mContentResolver,
getSettingsNameForDeviceVolumeBehavior(deviceType),
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 2f387540aa24..46e9c25a09c8 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -30,8 +30,6 @@ import android.content.Intent;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -39,11 +37,9 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Iterator;
-import java.util.NoSuchElementException;
import java.util.Objects;
/**
@@ -60,10 +56,6 @@ public class BtHelper {
mDeviceBroker = broker;
}
- // List of clients having issued a SCO start request
- @GuardedBy("BtHelper.this")
- private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
-
// BluetoothHeadset API to control SCO connection
private @Nullable BluetoothHeadset mBluetoothHeadset;
@@ -401,6 +393,8 @@ public class BtHelper {
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void receiveBtEvent(Intent intent) {
final String action = intent.getAction();
+
+ Log.i(TAG, "receiveBtEvent action: " + action + " mScoAudioState: " + mScoAudioState);
if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
setBtScoActiveDevice(btDevice);
@@ -408,14 +402,7 @@ public class BtHelper {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
int btState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- // broadcast intent if the connection was initated by AudioService
- if (!mScoClients.isEmpty()
- && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL
- || mScoAudioState == SCO_STATE_ACTIVATE_REQ
- || mScoAudioState == SCO_STATE_DEACTIVATE_REQ
- || mScoAudioState == SCO_STATE_DEACTIVATING)) {
- broadcast = true;
- }
+ Log.i(TAG, "receiveBtEvent ACTION_AUDIO_STATE_CHANGED: " + btState);
switch (btState) {
case BluetoothHeadset.STATE_AUDIO_CONNECTED:
if (checkAndUpdatTwsPlusScoState(intent,
@@ -432,6 +419,9 @@ public class BtHelper {
if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
&& mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ } else if (mDeviceBroker.isBluetoothScoRequested()) {
+ // broadcast intent if the connection was initated by AudioService
+ broadcast = true;
}
break;
case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
@@ -447,18 +437,18 @@ public class BtHelper {
// state SCO_STATE_ACTIVE_EXTERNAL and the mScoClients list not empty.
if (mScoAudioState == SCO_STATE_ACTIVATE_REQ
|| (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL
- && !mScoClients.isEmpty())) {
+ && mDeviceBroker.isBluetoothScoRequested())) {
if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
&& connectBluetoothScoAudioHelper(mBluetoothHeadset,
mBluetoothHeadsetDevice, mScoAudioMode)) {
mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcast = false;
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+ broadcast = true;
break;
}
}
- // Tear down SCO if disconnected from external
- if (mScoAudioState == SCO_STATE_DEACTIVATING) {
- clearAllScoClients(0, false);
+ if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+ broadcast = true;
}
mScoAudioState = SCO_STATE_INACTIVE;
Log.i(TAG, "Audio-path brought-down");
@@ -469,11 +459,8 @@ public class BtHelper {
&& mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
}
- broadcast = false;
break;
default:
- // do not broadcast CONNECTING or invalid state
- broadcast = false;
break;
}
if (broadcast) {
@@ -526,83 +513,19 @@ public class BtHelper {
== BluetoothHeadset.STATE_AUDIO_CONNECTED;
}
- /**
- * Disconnect all SCO connections started by {@link AudioManager} except those started by
- * {@param exceptPid}
- *
- * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
- */
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
- if (AudioService.DEBUG_SCO) {
- Log.i(TAG, "In disconnectBluetoothSco(), exceptPid: " + exceptPid);
- }
- checkScoAudioState();
- if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
- return;
- }
- clearAllScoClients(exceptPid, true);
- }
-
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
+ /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
@NonNull String eventSource) {
- ScoClient client = getScoClient(cb, true);
- // The calling identity must be cleared before calling ScoClient.incCount().
- // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- // The caller identity must be cleared after getScoClient() because it is needed if a new
- // client is created.
- final long ident = Binder.clearCallingIdentity();
- try {
- AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
- client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
- } catch (NullPointerException e) {
- Log.e(TAG, "Null ScoClient", e);
- }
- Binder.restoreCallingIdentity(ident);
- }
-
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
- @NonNull String eventSource) {
- ScoClient client = getScoClient(cb, false);
- // The calling identity must be cleared before calling ScoClient.decCount().
- // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs
- // and this must be done on behalf of system server to make sure permissions are granted.
- final long ident = Binder.clearCallingIdentity();
- if (client != null) {
- stopAndRemoveClient(client, eventSource);
- }
- Binder.restoreCallingIdentity(ident);
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
+ return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ synchronized void stopBluetoothScoForPid(int pid) {
- ScoClient client = getScoClientForPid(pid);
- if (client == null) {
- return;
- }
- final String eventSource = new StringBuilder("stopBluetoothScoForPid(")
- .append(pid).append(")").toString();
- stopAndRemoveClient(client, eventSource);
- }
-
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- private void stopAndRemoveClient(ScoClient client, @NonNull String eventSource) {
+ /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
- client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
- SCO_MODE_VIRTUAL_CALL);
- // If a disconnection is pending, the client will be removed when clearAllScoClients()
- // is called form receiveBtEvent()
- if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
- && mScoAudioState != SCO_STATE_DEACTIVATING) {
- client.remove(false /*stop */, true /*unregister*/);
- }
+ return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
}
/*package*/ synchronized void setHearingAidVolume(int index, int streamType) {
@@ -652,7 +575,6 @@ public class BtHelper {
if (AudioService.DEBUG_SCO) {
Log.i(TAG, "In resetBluetoothSco(), calling clearAllScoClients()");
}
- clearAllScoClients(0, false);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
mScoClientDevices.clear();
@@ -906,53 +828,6 @@ public class BtHelper {
};
//----------------------------------------------------------------------
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- /*package*/ synchronized void scoClientDied(Object obj) {
- final ScoClient client = (ScoClient) obj;
- client.remove(true /*stop*/, false /*unregister*/);
- Log.w(TAG, "SCO client died");
- }
-
- private class ScoClient implements IBinder.DeathRecipient {
- private IBinder mCb; // To be notified of client's death
- private int mCreatorPid;
-
- ScoClient(IBinder cb) {
- mCb = cb;
- mCreatorPid = Binder.getCallingPid();
- }
-
- public void registerDeathRecipient() {
- try {
- mCb.linkToDeath(this, 0);
- } catch (RemoteException e) {
- Log.w(TAG, "ScoClient could not link to " + mCb + " binder death");
- }
- }
-
- public void unregisterDeathRecipient() {
- try {
- mCb.unlinkToDeath(this, 0);
- } catch (NoSuchElementException e) {
- Log.w(TAG, "ScoClient could not not unregistered to binder");
- }
- }
-
- @Override
- public void binderDied() {
- // process this from DeviceBroker's message queue to take the right locks since
- // this event can impact SCO mode and requires querying audio mode stack
- mDeviceBroker.postScoClientDied(this);
- }
-
- IBinder getBinder() {
- return mCb;
- }
-
- int getPid() {
- return mCreatorPid;
- }
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
//@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
@@ -963,24 +838,10 @@ public class BtHelper {
+ scoAudioMode);
}
checkScoAudioState();
- if (mScoClients.size() != 1) {
- Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
- + ", num SCO clients=" + mScoClients.size());
- return true;
- }
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
// Make sure that the state transitions to CONNECTING even if we cannot initiate
// the connection.
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client process.
- final int modeOwnerPid = mDeviceBroker.getModeOwnerPid();
- if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
- Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
- + modeOwnerPid + " != creatorPid " + mCreatorPid);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return false;
- }
switch (mScoAudioState) {
case SCO_STATE_INACTIVE:
mScoAudioMode = scoAudioMode;
@@ -994,112 +855,99 @@ public class BtHelper {
SCO_MODE_VIRTUAL_CALL);
if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- }
- }
- }
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " connection, mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return false;
}
- break;
}
- if (mBluetoothHeadsetDevice == null) {
- Log.w(TAG, "requestScoState: no active device while connecting,"
- + " mScoAudioMode=" + mScoAudioMode);
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return false;
- }
- if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ }
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
} else {
- Log.w(TAG, "requestScoState: connect to "
- + getAnonymizedAddress(mBluetoothHeadsetDevice)
- + " failed, mScoAudioMode=" + mScoAudioMode);
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " connection, mScoAudioMode=" + mScoAudioMode);
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
}
break;
- case SCO_STATE_DEACTIVATING:
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ Log.w(TAG, "requestScoState: no active device while connecting,"
+ + " mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
- case SCO_STATE_ACTIVE_INTERNAL:
- Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
- break;
- default:
- Log.w(TAG, "requestScoState: failed to connect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ Log.w(TAG, "requestScoState: connect to "
+ + getAnonymizedAddress(mBluetoothHeadsetDevice)
+ + " failed, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
return false;
- }
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- switch (mScoAudioState) {
- case SCO_STATE_ACTIVE_INTERNAL:
- if (mBluetoothHeadset == null) {
- if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
- } else {
- Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
- + " disconnection, mScoAudioMode=" + mScoAudioMode);
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return false;
- }
- break;
- }
- if (mBluetoothHeadsetDevice == null) {
- mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- break;
- }
- if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_DEACTIVATING;
+ }
+ break;
+ case SCO_STATE_DEACTIVATING:
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ break;
+ case SCO_STATE_ACTIVE_INTERNAL:
+ Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to connect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return false;
+ }
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVE_INTERNAL:
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
} else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " disconnection, mScoAudioMode=" + mScoAudioMode);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return false;
}
break;
- case SCO_STATE_ACTIVATE_REQ:
+ }
+ if (mBluetoothHeadsetDevice == null) {
mScoAudioState = SCO_STATE_INACTIVE;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
break;
- default:
- Log.w(TAG, "requestScoState: failed to disconnect in state "
- + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- return false;
- }
- }
- return true;
- }
-
- @GuardedBy("BtHelper.this")
- void remove(boolean stop, boolean unregister) {
- if (unregister) {
- unregisterDeathRecipient();
- }
- if (stop) {
- requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
- SCO_MODE_VIRTUAL_CALL);
+ }
+ if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_ACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to disconnect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return false;
}
- mScoClients.remove(this);
}
+ return true;
}
//-----------------------------------------------------
@@ -1186,49 +1034,6 @@ public class BtHelper {
}
}
-
- @GuardedBy("BtHelper.this")
- private ScoClient getScoClient(IBinder cb, boolean create) {
- for (ScoClient existingClient : mScoClients) {
- if (existingClient.getBinder() == cb) {
- return existingClient;
- }
- }
- if (create) {
- ScoClient newClient = new ScoClient(cb);
- newClient.registerDeathRecipient();
- mScoClients.add(newClient);
- return newClient;
- }
- return null;
- }
-
- @GuardedBy("BtHelper.this")
- private ScoClient getScoClientForPid(int pid) {
- for (ScoClient cl : mScoClients) {
- if (cl.getPid() == pid) {
- return cl;
- }
- }
- return null;
- }
-
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- @GuardedBy("BtHelper.this")
- private void clearAllScoClients(int exceptPid, boolean stopSco) {
- final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
- for (ScoClient cl : mScoClients) {
- if (cl.getPid() != exceptPid) {
- clients.add(cl);
- }
- }
- for (ScoClient cl : clients) {
- cl.remove(stopSco, true /*unregister*/);
- }
-
- }
-
private boolean getBluetoothHeadset() {
boolean result = false;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1274,10 +1079,6 @@ public class BtHelper {
pw.println(prefix + "mBluetoothHeadsetDevice: " + mBluetoothHeadsetDevice);
pw.println(prefix + "mScoAudioState: " + scoAudioStateToString(mScoAudioState));
pw.println(prefix + "mScoAudioMode: " + scoAudioModeToString(mScoAudioMode));
- pw.println(prefix + "Sco clients:");
- mScoClients.forEach((cl) -> {
- pw.println(" " + prefix + "pid: " + cl.getPid() + " cb: " + cl.getBinder()); });
-
pw.println("\n" + prefix + "mHearingAid: " + mHearingAid);
pw.println(prefix + "mA2dp: " + mA2dp);
pw.println(prefix + "mAvrcpAbsVolSupported: " + mAvrcpAbsVolSupported);
diff --git a/services/core/java/com/android/server/biometrics/HardwareAuthTokenUtils.java b/services/core/java/com/android/server/biometrics/HardwareAuthTokenUtils.java
new file mode 100644
index 000000000000..eff4da3132fe
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/HardwareAuthTokenUtils.java
@@ -0,0 +1,138 @@
+/*
+ * 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.biometrics;
+
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+
+import java.nio.ByteOrder;
+
+/**
+ * Utilities for converting between old and new HardwareAuthToken types. See
+ * {@link HardwareAuthToken}.
+ */
+public class HardwareAuthTokenUtils {
+ public static byte[] toByteArray(HardwareAuthToken hat) {
+ final byte[] array = new byte[69];
+
+ // Version, first byte. Used in hw_auth_token.h but not HardwareAuthToken
+ array[0] = 0;
+
+ // Challenge, 1:8.
+ writeLong(hat.challenge, array, 1 /* offset */);
+
+ // UserId, 9:16.
+ writeLong(hat.userId, array, 9 /* offset */);
+
+ // AuthenticatorId, 17:24.
+ writeLong(hat.authenticatorId, array, 17 /* offset */);
+
+ // AuthenticatorType, 25:28.
+ writeInt(flipIfNativelyLittle(hat.authenticatorType), array, 25 /* offset */);
+
+ // Timestamp, 29:36.
+ writeLong(flipIfNativelyLittle(hat.timestamp.milliSeconds), array, 29 /* offset */);
+
+ // MAC, 37:69. Byte array.
+ System.arraycopy(hat.mac, 0 /* srcPos */, array, 37 /* destPos */, hat.mac.length);
+
+ return array;
+ }
+
+ public static HardwareAuthToken toHardwareAuthToken(byte[] array) {
+ final HardwareAuthToken hardwareAuthToken = new HardwareAuthToken();
+
+ // First byte is version, which doesn't not exist in HardwareAuthToken anymore
+ // Next 8 bytes is the challenge.
+ hardwareAuthToken.challenge = getLong(array, 1 /* offset */);
+
+ // Next 8 bytes is the userId
+ hardwareAuthToken.userId = getLong(array, 9 /* offset */);
+
+ // Next 8 bytes is the authenticatorId.
+ hardwareAuthToken.authenticatorId = getLong(array, 17 /* offset */);
+
+ // Next 4 bytes is the authenticatorType.
+ hardwareAuthToken.authenticatorType = flipIfNativelyLittle(getInt(array, 25 /* offset */));
+
+ // Next 8 bytes is the timestamp.
+ final Timestamp timestamp = new Timestamp();
+ timestamp.milliSeconds = flipIfNativelyLittle(getLong(array, 29 /* offset */));
+ hardwareAuthToken.timestamp = timestamp;
+
+ // Last 32 bytes is the mac, 37:69
+ hardwareAuthToken.mac = new byte[32];
+ System.arraycopy(array, 37 /* srcPos */,
+ hardwareAuthToken.mac,
+ 0 /* destPos */,
+ 32 /* length */);
+
+ return hardwareAuthToken;
+ }
+
+ private static long flipIfNativelyLittle(long l) {
+ if (LITTLE_ENDIAN == ByteOrder.nativeOrder()) {
+ return Long.reverseBytes(l);
+ }
+ return l;
+ }
+
+ private static int flipIfNativelyLittle(int i) {
+ if (LITTLE_ENDIAN == ByteOrder.nativeOrder()) {
+ return Integer.reverseBytes(i);
+ }
+ return i;
+ }
+
+ private static void writeLong(long l, byte[] dest, int offset) {
+ dest[offset + 0] = (byte) l;
+ dest[offset + 1] = (byte) (l >> 8);
+ dest[offset + 2] = (byte) (l >> 16);
+ dest[offset + 3] = (byte) (l >> 24);
+ dest[offset + 4] = (byte) (l >> 32);
+ dest[offset + 5] = (byte) (l >> 40);
+ dest[offset + 6] = (byte) (l >> 48);
+ dest[offset + 7] = (byte) (l >> 56);
+ }
+
+ private static void writeInt(int i, byte[] dest, int offset) {
+ dest[offset + 0] = (byte) i;
+ dest[offset + 1] = (byte) (i >> 8);
+ dest[offset + 2] = (byte) (i >> 16);
+ dest[offset + 3] = (byte) (i >> 24);
+ }
+
+ private static long getLong(byte[] array, int offset) {
+ long result = 0;
+ // Lowest bit is LSB
+ for (int i = 0; i < 8; i++) {
+ result += (long) ((array[i + offset] & 0xffL) << (8 * i));
+ }
+ return result;
+ }
+
+ private static int getInt(byte[] array, int offset) {
+ int result = 0;
+ // Lowest bit is LSB
+ for (int i = 0; i < 4; i++) {
+ result += (int) (((int) array[i + offset] & 0xff) << (8 * i));
+ }
+ return result;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/SensorConfig.java b/services/core/java/com/android/server/biometrics/SensorConfig.java
index 7743f1c91307..a83f67a5e29c 100644
--- a/services/core/java/com/android/server/biometrics/SensorConfig.java
+++ b/services/core/java/com/android/server/biometrics/SensorConfig.java
@@ -16,13 +16,15 @@
package com.android.server.biometrics;
+import android.hardware.biometrics.BiometricManager;
+
/**
* Parsed sensor config. See core/res/res/values/config.xml config_biometric_sensors
*/
public class SensorConfig {
public final int id;
final int modality;
- final int strength;
+ @BiometricManager.Authenticators.Types public final int strength;
public SensorConfig(String config) {
String[] elems = config.split(":");
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 88fd44456ff6..88804e28480b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -48,8 +48,11 @@ import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Binder;
import android.os.Build;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -444,4 +447,22 @@ public class Utils {
}
return false;
}
+
+ /**
+ * Converts from {@link BiometricManager.Authenticators} biometric strength to the internal
+ * {@link SensorPropertiesInternal} strength.
+ */
+ public static @SensorProperties.Strength int authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorProperties.STRENGTH_CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorProperties.STRENGTH_WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorProperties.STRENGTH_STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 4be596de3af1..ff410fcd2f66 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -18,19 +18,26 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.TaskStackListener;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
import android.security.KeyStore;
+import android.util.EventLog;
import android.util.Slog;
+import com.android.server.biometrics.Utils;
+
import java.util.ArrayList;
+import java.util.List;
/**
* A class to keep track of the authentication state for a given client.
@@ -136,7 +143,54 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
pm.incrementAuthForUser(getTargetUserId(), authenticated);
}
+ // Ensure authentication only succeeds if the client activity is on top or is keyguard.
+ boolean isBackgroundAuth = false;
+ if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())) {
+ try {
+ final List<ActivityManager.RunningTaskInfo> tasks =
+ mActivityTaskManager.getTasks(1);
+ if (tasks == null || tasks.isEmpty()) {
+ Slog.e(TAG, "No running tasks reported");
+ isBackgroundAuth = true;
+ } else {
+ final ComponentName topActivity = tasks.get(0).topActivity;
+ if (topActivity == null) {
+ Slog.e(TAG, "Unable to get top activity");
+ isBackgroundAuth = true;
+ } else {
+ final String topPackage = topActivity.getPackageName();
+ if (!topPackage.contentEquals(getOwnerString())) {
+ Slog.e(TAG, "Background authentication detected, top: " + topPackage
+ + ", client: " + this);
+ isBackgroundAuth = true;
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to get running tasks", e);
+ isBackgroundAuth = true;
+ }
+ }
+
+ // Fail authentication if we can't confirm the client activity is on top.
+ if (isBackgroundAuth) {
+ Slog.e(TAG, "Failing possible background authentication");
+ authenticated = false;
+
+ // SafetyNet logging for exploitation attempts of b/159249069.
+ final ApplicationInfo appInfo = getContext().getApplicationInfo();
+ EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,
+ "Attempted background authentication");
+ }
+
if (authenticated) {
+ // SafetyNet logging for b/159249069 if constraint is violated.
+ if (isBackgroundAuth) {
+ final ApplicationInfo appInfo = getContext().getApplicationInfo();
+ EventLog.writeEvent(0x534e4554, "159249069", appInfo != null ? appInfo.uid : -1,
+ "Successful background authentication!");
+ }
+
mAlreadyDone = true;
if (listener != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 588e865112c2..ce2d3405ddc6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -28,6 +28,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.io.PrintWriter;
@@ -37,9 +38,9 @@ import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
+import java.util.Deque;
import java.util.List;
import java.util.Locale;
-import java.util.Queue;
/**
* A scheduler for biometric HAL operations. Maintains a queue of {@link ClientMonitor} operations,
@@ -53,7 +54,8 @@ public class BiometricScheduler {
/**
* Contains all the necessary information for a HAL operation.
*/
- private static final class Operation {
+ @VisibleForTesting
+ static final class Operation {
/**
* The operation is added to the list of pending operations and waiting for its turn.
@@ -176,8 +178,8 @@ public class BiometricScheduler {
@NonNull private final IBiometricService mBiometricService;
@NonNull private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull private final InternalCallback mInternalCallback;
- @NonNull private final Queue<Operation> mPendingOperations;
- @Nullable private Operation mCurrentOperation;
+ @VisibleForTesting @NonNull final Deque<Operation> mPendingOperations;
+ @VisibleForTesting @Nullable Operation mCurrentOperation;
@NonNull private final ArrayDeque<CrashState> mCrashStates;
// Internal callback, notified when an operation is complete. Notifies the requester
@@ -226,6 +228,18 @@ public class BiometricScheduler {
}
}
+ @VisibleForTesting
+ BiometricScheduler(@NonNull String tag,
+ @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull IBiometricService biometricService) {
+ mBiometricTag = tag;
+ mInternalCallback = new InternalCallback();
+ mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
+ mPendingOperations = new ArrayDeque<>();
+ mBiometricService = biometricService;
+ mCrashStates = new ArrayDeque<>();
+ }
+
/**
* Creates a new scheduler.
* @param tag for the specific instance of the scheduler. Should be unique.
@@ -234,13 +248,8 @@ public class BiometricScheduler {
*/
public BiometricScheduler(@NonNull String tag,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- mBiometricTag = tag;
- mInternalCallback = new InternalCallback();
- mGestureAvailabilityDispatcher = gestureAvailabilityDispatcher;
- mPendingOperations = new ArrayDeque<>();
- mBiometricService = IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE));
- mCrashStates = new ArrayDeque<>();
+ this(tag, gestureAvailabilityDispatcher, IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
}
/**
@@ -295,9 +304,50 @@ public class BiometricScheduler {
// to arrive at the head of the queue, before pinging it to start.
final boolean shouldStartNow = currentClient.getCookie() == 0;
if (shouldStartNow) {
- Slog.d(getTag(), "[Starting] " + mCurrentOperation);
- currentClient.start(getInternalCallback());
- mCurrentOperation.state = Operation.STATE_STARTED;
+ if (mCurrentOperation.clientMonitor.getFreshDaemon() == null) {
+ // Note down current length of queue
+ final int pendingOperationsLength = mPendingOperations.size();
+ final Operation lastOperation = mPendingOperations.peekLast();
+ Slog.e(getTag(), "[Unable To Start] " + mCurrentOperation
+ + ". Last pending operation: " + lastOperation);
+
+ // For current operations, 1) unableToStart, which notifies the caller-side, then
+ // 2) notify operation's callback, to notify applicable system service that the
+ // operation failed.
+ mCurrentOperation.clientMonitor.unableToStart();
+ if (mCurrentOperation.mClientCallback != null) {
+ mCurrentOperation.mClientCallback
+ .onClientFinished(mCurrentOperation.clientMonitor, false /* success */);
+ }
+
+ // Then for each operation currently in the pending queue at the time of this
+ // failure, do the same as above. Otherwise, it's possible that something like
+ // setActiveUser fails, but then authenticate (for the wrong user) is invoked.
+ for (int i = 0; i < pendingOperationsLength; i++) {
+ final Operation operation = mPendingOperations.pollFirst();
+ if (operation == null) {
+ Slog.e(getTag(), "Null operation, index: " + i
+ + ", expected length: " + pendingOperationsLength);
+ break;
+ }
+ operation.clientMonitor.unableToStart();
+ if (operation.mClientCallback != null) {
+ operation.mClientCallback.onClientFinished(operation.clientMonitor,
+ false /* success */);
+ }
+ Slog.w(getTag(), "[Aborted Operation] " + operation);
+ }
+
+ // It's possible that during cleanup a new set of operations came in. We can try to
+ // run these. A single request from the manager layer to the service layer may
+ // actually be multiple operations (i.e. updateActiveUser + authenticate).
+ mCurrentOperation = null;
+ startNextOperationIfIdle();
+ } else {
+ Slog.d(getTag(), "[Starting] " + mCurrentOperation);
+ currentClient.start(getInternalCallback());
+ mCurrentOperation.state = Operation.STATE_STARTED;
+ }
} else {
try {
mBiometricService.onReadyForAuthentication(currentClient.getCookie());
@@ -338,9 +388,21 @@ public class BiometricScheduler {
return;
}
- Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
- mCurrentOperation.state = Operation.STATE_STARTED;
- mCurrentOperation.clientMonitor.start(getInternalCallback());
+ if (mCurrentOperation.clientMonitor.getFreshDaemon() == null) {
+ Slog.e(getTag(), "[Unable To Start] Prepared client: " + mCurrentOperation);
+ // This is BiometricPrompt trying to auth but something's wrong with the HAL.
+ mCurrentOperation.clientMonitor.unableToStart();
+ if (mCurrentOperation.mClientCallback != null) {
+ mCurrentOperation.mClientCallback.onClientFinished(mCurrentOperation.clientMonitor,
+ false /* success */);
+ }
+ mCurrentOperation = null;
+ startNextOperationIfIdle();
+ } else {
+ Slog.d(getTag(), "[Starting] Prepared client: " + mCurrentOperation);
+ mCurrentOperation.state = Operation.STATE_STARTED;
+ mCurrentOperation.clientMonitor.start(getInternalCallback());
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
index 3c9dddd0b905..0dee81681fc4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitor.java
@@ -73,7 +73,7 @@ public abstract class ClientMonitor<T> extends LoggableMonitor implements IBinde
T getDaemon();
}
- private final int mSequentialId;
+ protected final int mSequentialId;
@NonNull private final Context mContext;
@NonNull protected final LazyDaemon<T> mLazyDaemon;
private final int mTargetUserId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
index cb7db92ee132..c87f62f94499 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallbackConverter.java
@@ -89,7 +89,8 @@ public final class ClientMonitorCallbackConverter {
}
}
- void onError(int sensorId, int cookie, int error, int vendorCode) throws RemoteException {
+ public void onError(int sensorId, int cookie, int error, int vendorCode)
+ throws RemoteException {
if (mSensorReceiver != null) {
mSensorReceiver.onError(sensorId, cookie, error, vendorCode);
} else if (mFaceServiceReceiver != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
index 92c498c55dbf..bac944fca1de 100644
--- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java
@@ -27,8 +27,6 @@ public abstract class GenerateChallengeClient<T> extends ClientMonitor<T> {
private static final String TAG = "GenerateChallengeClient";
- protected long mChallenge;
-
public GenerateChallengeClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@NonNull String owner, int sensorId) {
@@ -51,12 +49,5 @@ public abstract class GenerateChallengeClient<T> extends ClientMonitor<T> {
super.start(callback);
startHalOperation();
- try {
- getListener().onChallengeGenerated(getSensorId(), mChallenge);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- mCallback.onClientFinished(this, false /* success */);
- }
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java b/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java
new file mode 100644
index 000000000000..153bd4668a4f
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/LockoutConsumer.java
@@ -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.
+ */
+
+package com.android.server.biometrics.sensors;
+
+/**
+ * Interface that clients interested/eligible for lockout events should implement.
+ */
+public interface LockoutConsumer {
+ void onLockoutTimed(long durationMillis);
+ void onLockoutPermanent();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
index 1613d3bb4e8a..c2d4c152cba6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/Face10.java
@@ -26,11 +26,12 @@ import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.Build;
@@ -47,6 +48,7 @@ import android.provider.Settings;
import android.util.Slog;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -60,7 +62,6 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUpdateActiveUserClient;
import org.json.JSONArray;
import org.json.JSONException;
@@ -87,7 +88,7 @@ class Face10 implements IHwBinder.DeathRecipient {
static final String NOTIFICATION_TAG = "FaceService";
static final int NOTIFICATION_ID = 1;
- @NonNull private final FaceSensorProperties mFaceSensorProperties;
+ @NonNull private final FaceSensorPropertiesInternal mFaceSensorProperties;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
@@ -278,14 +279,14 @@ class Face10 implements IHwBinder.DeathRecipient {
}
};
+ @VisibleForTesting
Face10(@NonNull Context context, int sensorId,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- final boolean supportsSelfIllumination = context.getResources()
- .getBoolean(R.bool.config_faceAuthSupportsSelfIllumination);
- final int maxTemplatesAllowed = context.getResources()
- .getInteger(R.integer.config_faceMaxTemplatesPerUser);
- mFaceSensorProperties = new FaceSensorProperties(sensorId, false /* supportsFaceDetect */,
- supportsSelfIllumination, maxTemplatesAllowed);
+ @BiometricManager.Authenticators.Types int strength,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ boolean supportsSelfIllumination, int maxTemplatesAllowed) {
+ mFaceSensorProperties = new FaceSensorPropertiesInternal(sensorId,
+ Utils.authenticatorStrengthToPropertyStrength(strength),
+ maxTemplatesAllowed, false /* supportsFaceDetect */, supportsSelfIllumination);
mContext = context;
mSensorId = sensorId;
mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
@@ -304,6 +305,14 @@ class Face10 implements IHwBinder.DeathRecipient {
}
}
+ Face10(@NonNull Context context, int sensorId,
+ @BiometricManager.Authenticators.Types int strength,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ this(context, sensorId, strength, lockoutResetDispatcher,
+ context.getResources().getBoolean(R.bool.config_faceAuthSupportsSelfIllumination),
+ context.getResources().getInteger(R.integer.config_faceMaxTemplatesPerUser));
+ }
+
@Override
public void serviceDied(long cookie) {
Slog.e(TAG, "HAL died");
@@ -402,7 +411,7 @@ class Face10 implements IHwBinder.DeathRecipient {
}
/**
- * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
+ * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the
* handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
* invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
* this operation on the same lambda/runnable as those operations so that the ordering is
@@ -510,19 +519,17 @@ class Face10 implements IHwBinder.DeathRecipient {
@NonNull String opPackageName) {
mHandler.post(() -> {
if (mCurrentChallengeOwner != null) {
- Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
- + ", interrupted by: " + opPackageName);
final ClientMonitorCallbackConverter listener =
mCurrentChallengeOwner.getListener();
- if (listener == null) {
- Slog.w(TAG, "Null listener, skip sending interruption callback");
- return;
- }
-
- try {
- listener.onChallengeInterrupted(mSensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify challenge interrupted", e);
+ Slog.w(TAG, "Current challenge owner: " + mCurrentChallengeOwner
+ + ", listener: " + listener
+ + ", interrupted by: " + opPackageName);
+ if (listener != null) {
+ try {
+ listener.onChallengeInterrupted(mSensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify challenge interrupted", e);
+ }
}
}
@@ -546,7 +553,8 @@ class Face10 implements IHwBinder.DeathRecipient {
void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String owner) {
mHandler.post(() -> {
- if (!mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
+ if (mCurrentChallengeOwner != null &&
+ !mCurrentChallengeOwner.getOwnerString().contentEquals(owner)) {
Slog.e(TAG, "scheduleRevokeChallenge, package: " + owner
+ " attempting to revoke challenge owned by: "
+ mCurrentChallengeOwner.getOwnerString());
@@ -565,6 +573,13 @@ class Face10 implements IHwBinder.DeathRecipient {
return;
}
+ if (mCurrentChallengeOwner == null) {
+ // Can happen if revoke is incorrectly called, for example without a
+ // preceding generateChallenge
+ Slog.w(TAG, "Current challenge owner is null");
+ return;
+ }
+
final FaceGenerateChallengeClient previousChallengeOwner =
mCurrentChallengeOwner.getInterruptedClient();
mCurrentChallengeOwner = null;
@@ -671,7 +686,8 @@ class Face10 implements IHwBinder.DeathRecipient {
return daemon != null;
}
- @NonNull FaceSensorProperties getFaceSensorProperties() {
+ @NonNull
+ FaceSensorPropertiesInternal getFaceSensorProperties() {
return mFaceSensorProperties;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index bbee6cde4603..3318bcb8d593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -34,7 +34,7 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
public FaceAuthenticator(IFaceService faceService, SensorConfig config)
throws RemoteException {
mFaceService = faceService;
- mFaceService.initializeConfiguration(config.id);
+ mFaceService.initializeConfiguration(config.id, config.strength);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
index ba401f255e43..406a7ccedc33 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceGenerateChallengeClient.java
@@ -59,7 +59,14 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet
@Override
protected void startHalOperation() {
try {
- mChallenge = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+ final long challenge = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
+ try {
+ getListener().onChallengeGenerated(getSensorId(), challenge);
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "generateChallenge failed", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index c6664f4d96ff..83f10c8e658b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -21,11 +21,12 @@ import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.face.Face;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
@@ -66,9 +67,11 @@ public class FaceService extends SystemService {
*/
private final class FaceServiceWrapper extends IFaceService.Stub {
@Override // Binder call
- public List<FaceSensorProperties> getSensorProperties(String opPackageName) {
+ public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
+ String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- final List<FaceSensorProperties> properties = new ArrayList<>();
+
+ final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
if (mFace10 != null) {
properties.add(mFace10.getFaceSensorProperties());
@@ -308,9 +311,10 @@ public class FaceService extends SystemService {
}
@Override // Binder call
- public void initializeConfiguration(int sensorId) {
+ public void initializeConfiguration(int sensorId,
+ @BiometricManager.Authenticators.Types int strength) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mFace10 = new Face10(getContext(), sensorId, mLockoutResetDispatcher);
+ mFace10 = new Face10(getContext(), sensorId, strength, mLockoutResetDispatcher);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 21a46d58a3b3..5219df4a841d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -34,7 +34,7 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub
public FingerprintAuthenticator(IFingerprintService fingerprintService, SensorConfig config)
throws RemoteException {
mFingerprintService = fingerprintService;
- mFingerprintService.initializeConfiguration(config.id);
+ mFingerprintService.initializeConfiguration(config.id, config.strength);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 7c7da118df10..dc22970d617c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
@@ -32,31 +33,44 @@ import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
+import android.hardware.biometrics.ITestSession;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
+import android.os.Handler;
import android.os.IBinder;
import android.os.NativeHandle;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.EventLog;
+import android.util.Pair;
import android.util.Slog;
import android.view.Surface;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
+import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -77,20 +91,82 @@ public class FingerprintService extends SystemService {
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
private final LockPatternUtils mLockPatternUtils;
- private Fingerprint21 mFingerprint21;
+ @NonNull private List<ServiceProvider> mServiceProviders;
+ @NonNull private final ArrayMap<Integer, TestSession> mTestSessions;
+
+ private final class TestSession extends ITestSession.Stub {
+ private final int mSensorId;
+
+ TestSession(int sensorId) {
+ mSensorId = sensorId;
+ }
+
+ @Override
+ public void enableTestHal(boolean enableTestHal) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void startEnroll(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void finishEnroll(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void acceptAuthentication(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void rejectAuthentication(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void notifyAcquired(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void notifyError(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+
+ @Override
+ public void cleanupInternalState(int userId) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+ }
+ }
/**
* Receives the incoming binder calls from FingerprintManager.
*/
private final class FingerprintServiceWrapper extends IFingerprintService.Stub {
+ @Override
+ public ITestSession createTestSession(int sensorId, String opPackageName) {
+ Utils.checkPermission(getContext(), TEST_BIOMETRIC);
+
+ final TestSession session;
+ synchronized (mTestSessions) {
+ if (!mTestSessions.containsKey(sensorId)) {
+ mTestSessions.put(sensorId, new TestSession(sensorId));
+ }
+ session = mTestSessions.get(sensorId);
+ }
+ return session;
+ }
+
@Override // Binder call
- public List<FingerprintSensorProperties> getSensorProperties(String opPackageName) {
+ public List<FingerprintSensorPropertiesInternal> getSensorPropertiesInternal(
+ String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- final List<FingerprintSensorProperties> properties = new ArrayList<>();
- if (mFingerprint21 != null) {
- properties.add(mFingerprint21.getFingerprintSensorProperties());
- }
+ final List<FingerprintSensorPropertiesInternal> properties =
+ FingerprintService.this.getSensorProperties();
Slog.d(TAG, "Retrieved sensor properties for: " + opPackageName
+ ", sensors: " + properties.size());
@@ -102,18 +178,27 @@ public class FingerprintService extends SystemService {
IFingerprintServiceReceiver receiver, String opPackageName) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
- mFingerprint21.scheduleGenerateChallenge(token, receiver, opPackageName);
+ final ServiceProvider provider = getProviderForSensor(sensorId);
+ if (provider == null) {
+ Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
return;
}
- Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId);
+ provider.scheduleGenerateChallenge(sensorId, token, receiver, opPackageName);
}
@Override // Binder call
- public void revokeChallenge(IBinder token, String owner) {
+ public void revokeChallenge(IBinder token, String opPackageName, long challenge) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mFingerprint21.scheduleRevokeChallenge(token, owner);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for revokeChallenge");
+ return;
+ }
+
+ provider.second.scheduleRevokeChallenge(provider.first, token, opPackageName,
+ challenge);
}
@Override // Binder call
@@ -121,14 +206,28 @@ public class FingerprintService extends SystemService {
final IFingerprintServiceReceiver receiver, final String opPackageName,
Surface surface) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mFingerprint21.scheduleEnroll(token, hardwareAuthToken, userId, receiver, opPackageName,
- surface);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for enroll");
+ return;
+ }
+
+ provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
+ receiver, opPackageName, surface);
}
@Override // Binder call
public void cancelEnrollment(final IBinder token) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mFingerprint21.cancelEnrollment(token);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for cancelEnrollment");
+ return;
+ }
+
+ provider.second.cancelEnrollment(provider.first, token);
}
@Override // Binder call
@@ -167,8 +266,15 @@ public class FingerprintService extends SystemService {
!= PackageManager.PERMISSION_GRANTED;
final int statsClient = isKeyguard ? BiometricsProtoEnums.CLIENT_KEYGUARD
: BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER;
- mFingerprint21.scheduleAuthenticate(token, operationId, userId, 0 /* cookie */,
- new ClientMonitorCallbackConverter(receiver), opPackageName,
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for authenticate");
+ return;
+ }
+
+ provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
restricted, statsClient, isKeyguard);
}
@@ -189,7 +295,13 @@ public class FingerprintService extends SystemService {
return;
}
- mFingerprint21.scheduleFingerDetect(token, userId,
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for detectFingerprint");
+ return;
+ }
+
+ provider.second.scheduleFingerDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName, surface,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@@ -201,8 +313,14 @@ public class FingerprintService extends SystemService {
Surface surface) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for prepareForAuthentication");
+ return;
+ }
+
final boolean restricted = true; // BiometricPrompt is always restricted
- mFingerprint21.scheduleAuthenticate(token, operationId, userId, cookie,
+ provider.second.scheduleAuthenticate(provider.first, token, operationId, userId, cookie,
new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, false /* isKeyguard */);
}
@@ -210,7 +328,14 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public void startPreparedClient(int cookie) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
- mFingerprint21.startPreparedClient(cookie);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for startPreparedClient");
+ return;
+ }
+
+ provider.second.startPreparedClient(provider.first, cookie);
}
@@ -226,7 +351,13 @@ public class FingerprintService extends SystemService {
return;
}
- mFingerprint21.cancelAuthentication(token);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for cancelAuthentication");
+ return;
+ }
+
+ provider.second.cancelAuthentication(provider.first, token);
}
@Override // Binder call
@@ -240,21 +371,41 @@ public class FingerprintService extends SystemService {
// For IBiometricsFingerprint2.1, cancelling fingerprint detect is the same as
// cancelling authentication.
- mFingerprint21.cancelAuthentication(token);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for cancelFingerprintDetect");
+ return;
+ }
+
+ provider.second.cancelAuthentication(provider.first, token);
}
@Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
- mFingerprint21.cancelAuthentication(token);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for cancelAuthenticationFromService");
+ return;
+ }
+
+ provider.second.cancelAuthentication(provider.first, token);
}
@Override // Binder call
public void remove(final IBinder token, final int fingerId, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
- mFingerprint21.scheduleRemove(token, receiver, fingerId, userId, opPackageName);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for remove");
+ return;
+ }
+ provider.second.scheduleRemove(provider.first, token, receiver, fingerId, userId,
+ opPackageName);
}
@Override
@@ -272,10 +423,15 @@ public class FingerprintService extends SystemService {
final long ident = Binder.clearCallingIdentity();
try {
- if (args.length > 0 && "--proto".equals(args[0])) {
- mFingerprint21.dumpProto(fd);
- } else {
- mFingerprint21.dumpInternal(pw);
+ for (ServiceProvider provider : mServiceProviders) {
+ for (FingerprintSensorPropertiesInternal props :
+ provider.getSensorProperties()) {
+ if (args.length > 0 && "--proto".equals(args[0])) {
+ provider.dumpProto(props.sensorId, fd);
+ } else {
+ provider.dumpInternal(props.sensorId, pw);
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -292,11 +448,12 @@ public class FingerprintService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
- if (mFingerprint21 == null) {
- Slog.e(TAG, "No HAL, caller: " + opPackageName);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName);
return false;
}
- return mFingerprint21.isHardwareDetected();
+ return provider.second.isHardwareDetected(provider.first);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -309,7 +466,13 @@ public class FingerprintService extends SystemService {
return;
}
- mFingerprint21.rename(fingerId, userId, name);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for rename");
+ return;
+ }
+
+ provider.second.rename(provider.first, fingerId, userId, name);
}
@Override // Binder call
@@ -323,7 +486,8 @@ public class FingerprintService extends SystemService {
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- return mFingerprint21.getEnrolledFingerprints(userId);
+
+ return FingerprintService.this.getEnrolledFingerprints(userId, opPackageName);
}
@Override // Binder call
@@ -337,19 +501,32 @@ public class FingerprintService extends SystemService {
if (userId != UserHandle.getCallingUserId()) {
Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS);
}
- return mFingerprint21.getEnrolledFingerprints(userId).size() > 0;
+ return !FingerprintService.this.getEnrolledFingerprints(userId, opPackageName)
+ .isEmpty();
}
@Override // Binder call
public @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- return mFingerprint21.getLockoutModeForUser(userId);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for getLockoutModeForUser");
+ return LockoutTracker.LOCKOUT_NONE;
+ }
+ return provider.second.getLockoutModeForUser(provider.first, userId);
}
@Override // Binder call
public long getAuthenticatorId(int userId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- return mFingerprint21.getAuthenticatorId(userId);
+
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for getAuthenticatorId");
+ return 0;
+ }
+ return provider.second.getAuthenticatorId(provider.first, userId);
}
@Override // Binder call
@@ -357,12 +534,13 @@ public class FingerprintService extends SystemService {
@Nullable byte [] hardwareAuthToken, String opPackageName) {
Utils.checkPermission(getContext(), RESET_FINGERPRINT_LOCKOUT);
- if (sensorId == mFingerprint21.getFingerprintSensorProperties().sensorId) {
- mFingerprint21.scheduleResetLockout(userId);
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName);
return;
}
- Slog.w(TAG, "No matching sensor for resetLockout, sensorId: " + sensorId);
+ provider.second.scheduleResetLockout(sensorId, userId, hardwareAuthToken);
}
@Override
@@ -384,38 +562,55 @@ public class FingerprintService extends SystemService {
}
@Override // Binder call
- public void initializeConfiguration(int sensorId) {
+ public void initializeConfiguration(int sensorId, int strength) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ final Fingerprint21 fingerprint21;
if ((Build.IS_USERDEBUG || Build.IS_ENG)
&& getContext().getResources().getBoolean(R.bool.allow_test_udfps)
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
- mFingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), sensorId,
+ strength, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
- mFingerprint21 = Fingerprint21.newInstance(getContext(), sensorId,
+ fingerprint21 = Fingerprint21.newInstance(getContext(), sensorId, strength,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
+ mServiceProviders.add(fingerprint21);
}
@Override
- public void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mFingerprint21.onFingerDown(x, y, minor, major);
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
+ if (provider == null) {
+ Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId);
+ return;
+ }
+ provider.onPointerDown(sensorId, x, y, minor, major);
}
@Override
- public void onFingerUp() {
+ public void onPointerUp(int sensorId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mFingerprint21.onFingerUp();
+
+ final ServiceProvider provider = getProviderForSensor(sensorId);
+ if (provider == null) {
+ Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId);
+ return;
+ }
+ provider.onPointerUp(sensorId);
}
@Override
- public void setUdfpsOverlayController(IUdfpsOverlayController controller) {
+ public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
- mFingerprint21.setUdfpsOverlayController(controller);
+
+ for (ServiceProvider provider : mServiceProviders) {
+ provider.setUdfpsOverlayController(controller);
+ }
}
}
@@ -425,6 +620,41 @@ public class FingerprintService extends SystemService {
mGestureAvailabilityDispatcher = new GestureAvailabilityDispatcher();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
+ mServiceProviders = new ArrayList<>();
+ mTestSessions = new ArrayMap<>();
+
+ initializeAidlHals();
+ }
+
+ private void initializeAidlHals() {
+ final String[] instances = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ if (instances == null || instances.length == 0) {
+ return;
+ }
+
+ // If for some reason the HAL is not started before the system service, do not block
+ // the rest of system server. Put this on a background thread.
+ final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ true /* allowIo */);
+ thread.start();
+ final Handler handler = new Handler(thread.getLooper());
+
+ handler.post(() -> {
+ for (String instance : instances) {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
+ final IFingerprint fp = IFingerprint.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(fqName));
+ try {
+ final SensorProps[] props = fp.getSensorProps();
+ final FingerprintProvider provider =
+ new FingerprintProvider(getContext(), props, fqName,
+ mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mServiceProviders.add(provider);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when initializing instance: " + fqName);
+ }
+ }
+ });
}
@Override
@@ -432,6 +662,62 @@ public class FingerprintService extends SystemService {
publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
}
+ @Nullable
+ private ServiceProvider getProviderForSensor(int sensorId) {
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For devices with only a single provider, returns that provider. If no providers, or multiple
+ * providers exist, returns null.
+ */
+ @Nullable
+ private Pair<Integer, ServiceProvider> getSingleProvider() {
+ final List<FingerprintSensorPropertiesInternal> properties = getSensorProperties();
+ if (properties.size() != 1) {
+ Slog.e(TAG, "Multiple sensors found: " + properties.size());
+ return null;
+ }
+
+ // Theoretically we can just return the first provider, but maybe this is easier to
+ // understand.
+ final int sensorId = properties.get(0).sensorId;
+ for (ServiceProvider provider : mServiceProviders) {
+ if (provider.containsSensor(sensorId)) {
+ return new Pair<>(sensorId, provider);
+ }
+ }
+
+ Slog.e(TAG, "Single sensor, but provider not found");
+ return null;
+ }
+
+ @NonNull
+ private List<FingerprintSensorPropertiesInternal> getSensorProperties() {
+ final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
+
+ for (ServiceProvider provider : mServiceProviders) {
+ properties.addAll(provider.getSensorProperties());
+ }
+ return properties;
+ }
+
+ @NonNull
+ private List<Fingerprint> getEnrolledFingerprints(int userId, String opPackageName) {
+ final Pair<Integer, ServiceProvider> provider = getSingleProvider();
+ if (provider == null) {
+ Slog.w(TAG, "Null provider for getEnrolledFingerprints, caller: " + opPackageName);
+ return Collections.emptyList();
+ }
+
+ return provider.second.getEnrolledFingerprints(provider.first, userId);
+ }
+
/**
* Checks for public API invocations to ensure that permissions, etc are granted/correct.
*/
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
new file mode 100644
index 000000000000..35d01882f0c6
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -0,0 +1,116 @@
+/*
+ * 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.biometrics.sensors.fingerprint;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.view.Surface;
+
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Superset of features/functionalities that HALs provide to the rest of the framework. This is
+ * more or less mapped to the public and private APIs that {@link FingerprintManager} provide, and
+ * is used at the system server layer to provide easy mapping between request and provider.
+ *
+ * Note that providers support both single-sensor and multi-sensor HALs. In either case,
+ * {@link FingerprintService} must ensure that providers are only requested to perform operations
+ * on sensors that they own.
+ *
+ * For methods other than {@link #containsSensor(int)}, the caller must ensure that the sensorId
+ * passed in is supported by the provider. For example,
+ * if (serviceProvider.containsSensor(sensorId)) {
+ * serviceProvider.operation(sensorId, ...);
+ * }
+ *
+ * For operations that are supported by some providers but not others, clients are required
+ * to check (e.g. via {@link FingerprintManager#getSensorPropertiesInternal()}) to ensure that the
+ * code path isn't taken. ServiceProviders will provide a no-op for unsupported operations to
+ * fail safely.
+ */
+@SuppressWarnings("deprecation")
+public interface ServiceProvider {
+ /**
+ * Checks if the specified sensor is owned by this provider.
+ */
+ boolean containsSensor(int sensorId);
+
+ @NonNull List<FingerprintSensorPropertiesInternal> getSensorProperties();
+
+ void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken);
+
+ void scheduleGenerateChallenge(int sensorId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, String opPackageName);
+
+ void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
+ @NonNull String opPackageName, long challenge);
+
+ void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId,
+ @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
+ @Nullable Surface surface);
+
+ void cancelEnrollment(int sensorId, @NonNull IBinder token);
+
+ void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+ @Nullable Surface surface, int statsClient);
+
+ void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient, boolean isKeyguard);
+
+ void startPreparedClient(int sensorId, int cookie);
+
+ void cancelAuthentication(int sensorId, @NonNull IBinder token);
+
+ void scheduleRemove(int sensorId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName);
+
+ void scheduleInternalCleanup(int sensorId, int userId);
+
+ boolean isHardwareDetected(int sensorId);
+
+ void rename(int sensorId, int fingerId, int userId, @NonNull String name);
+
+ @NonNull List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId);
+
+ @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId);
+
+ long getAuthenticatorId(int sensorId, int userId);
+
+ void onPointerDown(int sensorId, int x, int y, float minor, float major);
+
+ void onPointerUp(int sensorId);
+
+ void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
+
+ void dumpProto(int sensorId, @NonNull FileDescriptor fd);
+
+ void dumpInternal(int sensorId, @NonNull PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
index e0806ff0fdb0..0bf107a9be8e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java
@@ -22,6 +22,6 @@ package com.android.server.biometrics.sensors.fingerprint;
* finger position (e.g. enroll, authenticate) should implement this.
*/
public interface Udfps {
- void onFingerDown(int x, int y, float minor, float major);
- void onFingerUp();
+ void onPointerDown(int x, int y, float minor, float major);
+ void onPointerUp();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
index 5e521d2fe46c..a2b871ee72c3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -29,7 +29,7 @@ public class UdfpsHelper {
private static final String TAG = "UdfpsHelper";
- static void onFingerDown(IBiometricsFingerprint daemon, int x, int y, float minor,
+ public static void onFingerDown(IBiometricsFingerprint daemon, int x, int y, float minor,
float major) {
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
@@ -46,7 +46,7 @@ public class UdfpsHelper {
}
}
- static void onFingerUp(IBiometricsFingerprint daemon) {
+ public static void onFingerUp(IBiometricsFingerprint daemon) {
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
daemon);
@@ -62,23 +62,25 @@ public class UdfpsHelper {
}
}
- static void showUdfpsOverlay(@Nullable IUdfpsOverlayController udfpsOverlayController) {
+ public static void showUdfpsOverlay(int sensorId,
+ @Nullable IUdfpsOverlayController udfpsOverlayController) {
if (udfpsOverlayController == null) {
return;
}
try {
- udfpsOverlayController.showUdfpsOverlay();
+ udfpsOverlayController.showUdfpsOverlay(sensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
}
}
- static void hideUdfpsOverlay(@Nullable IUdfpsOverlayController udfpsOverlayController) {
+ public static void hideUdfpsOverlay(int sensorId,
+ @Nullable IUdfpsOverlayController udfpsOverlayController) {
if (udfpsOverlayController == null) {
return;
}
try {
- udfpsOverlayController.hideUdfpsOverlay();
+ udfpsOverlayController.hideUdfpsOverlay(sensorId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
new file mode 100644
index 000000000000..e923943bd386
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -0,0 +1,152 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.TaskStackListener;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+
+import java.util.ArrayList;
+
+/**
+ * Fingerprint-specific authentication client supporting the
+ * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
+ */
+public class FingerprintAuthenticationClient extends AuthenticationClient<ISession> implements
+ Udfps, LockoutConsumer {
+ private static final String TAG = "FingerprintAuthenticationClient";
+
+ @NonNull private final LockoutCache mLockoutCache;
+ @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+ @Nullable private ICancellationSignal mCancellationSignal;
+
+ public FingerprintAuthenticationClient(@NonNull Context context,
+ @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
+ boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
+ int sensorId, boolean isStrongBiometric, int statsClient,
+ @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
+ @Nullable IUdfpsOverlayController udfpsOverlayController) {
+ super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
+ cookie, requireConfirmation, sensorId, isStrongBiometric,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
+ lockoutCache);
+ mLockoutCache = lockoutCache;
+ mUdfpsOverlayController = udfpsOverlayController;
+ }
+
+ @Override
+ public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+ boolean authenticated, ArrayList<Byte> token) {
+ super.onAuthenticated(identifier, authenticated, token);
+
+ if (authenticated) {
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mCallback.onClientFinished(this, true /* success */);
+ }
+ }
+
+ @Override
+ protected void startHalOperation() {
+ UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ try {
+ mCancellationSignal = getFreshDaemon().authenticate(mSequentialId, mOperationId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ protected void stopHalOperation() {
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void onPointerDown(int x, int y, float minor, float major) {
+ try {
+ getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onPointerUp() {
+ try {
+ getFreshDaemon().onPointerUp(0 /* pointerId */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
+ // Lockout metrics are logged as an error code.
+ final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
+ logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
+
+ try {
+ getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
+ // Lockout metrics are logged as an error code.
+ final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+ logOnError(getContext(), error, 0 /* vendorCode */, getTargetUserId());
+
+ try {
+ getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
new file mode 100644
index 000000000000..33f5418ee620
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -0,0 +1,112 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricFaceConstants;
+import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+
+public class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
+
+ private static final String TAG = "FingerprintEnrollClient";
+
+ @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+ @Nullable private ICancellationSignal mCancellationSignal;
+ private final int mMaxTemplatesPerUser;
+
+ public FingerprintEnrollClient(@NonNull Context context,
+ @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
+ int statsModality, int sensorId,
+ @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser) {
+ super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
+ 0 /* timeoutSec */, statsModality, sensorId, true /* shouldVibrate */);
+ mUdfpsOverlayController = udfpsOvelayController;
+ mMaxTemplatesPerUser = maxTemplatesPerUser;
+ }
+
+ @Override
+ protected boolean hasReachedEnrollmentLimit() {
+ return FingerprintUtils.getInstance()
+ .getBiometricsForUser(getContext(), getTargetUserId()).size()
+ >= mMaxTemplatesPerUser;
+ }
+
+ @Override
+ protected void stopHalOperation() {
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting cancel", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+ }
+
+ @Override
+ protected void startHalOperation() {
+ UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ try {
+ getFreshDaemon().enroll(mSequentialId,
+ HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when requesting enroll", e);
+ onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void onPointerDown(int x, int y, float minor, float major) {
+ try {
+ getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to send pointer down", e);
+ }
+ }
+
+ @Override
+ public void onPointerUp() {
+ try {
+ getFreshDaemon().onPointerUp(0 /* pointerId */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to send pointer up", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
new file mode 100644
index 000000000000..7db01eed571e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java
@@ -0,0 +1,71 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IGenerateChallengeCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.GenerateChallengeClient;
+
+/**
+ * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.
+ */
+public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<IFingerprint> {
+ private static final String TAG = "FingerprintGenerateChallengeClient";
+ private static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
+
+ private final IGenerateChallengeCallback mGenerateChallengeCallback =
+ new IGenerateChallengeCallback.Stub() {
+ @Override
+ public void onChallengeGenerated(int sensorId, int userId, long challenge) {
+ try {
+ getListener().onChallengeGenerated(sensorId, challenge);
+ mCallback.onClientFinished(FingerprintGenerateChallengeClient.this,
+ true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to send challenge", e);
+ mCallback.onClientFinished(FingerprintGenerateChallengeClient.this,
+ false /* success */);
+ }
+ }
+ };
+
+ public FingerprintGenerateChallengeClient(@NonNull Context context,
+ @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+ @NonNull IBinder token,
+ @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull String owner, int sensorId) {
+ super(context, lazyDaemon, token, listener, owner, sensorId);
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().generateChallenge(getSensorId(), getTargetUserId(),
+ CHALLENGE_TIMEOUT_SEC,
+ mGenerateChallengeCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to generateChallenge", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
new file mode 100644
index 000000000000..5e6c30ecefc8
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -0,0 +1,441 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.TaskStackListener;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provider for a single instance of the {@link IFingerprint} HAL.
+ */
+@SuppressWarnings("deprecation")
+public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvider {
+
+ @NonNull private final Context mContext;
+ @NonNull private final String mHalInstanceName;
+ @NonNull private final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
+ @NonNull private final ClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
+ @NonNull private final Handler mHandler;
+ @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
+ @NonNull private final IActivityTaskManager mActivityTaskManager;
+ @NonNull private final BiometricTaskStackListener mTaskStackListener;
+
+ @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
+
+ private final class BiometricTaskStackListener extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() {
+ mHandler.post(() -> {
+ for (int i = 0; i < mSensors.size(); i++) {
+ final ClientMonitor<?> client = mSensors.get(i).getScheduler()
+ .getCurrentClient();
+ if (!(client instanceof AuthenticationClient)) {
+ Slog.e(getTag(), "Task stack changed for client: " + client);
+ continue;
+ }
+ if (Utils.isKeyguard(mContext, client.getOwnerString())) {
+ continue; // Keyguard is always allowed
+ }
+
+ try {
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage =
+ runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(client.getOwnerString())
+ && !client.isAlreadyDone()) {
+ Slog.e(getTag(), "Stopping background authentication, top: "
+ + topPackage + " currentClient: " + client);
+ mSensors.get(i).getScheduler()
+ .cancelAuthentication(client.getToken());
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Unable to get running tasks", e);
+ }
+ }
+ });
+ }
+ }
+
+ public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
+ @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ mContext = context;
+ mHalInstanceName = halInstanceName;
+ mSensors = new SparseArray<>();
+ mLazyDaemon = this::getHalInstance;
+ mHandler = new Handler(Looper.getMainLooper());
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mActivityTaskManager = ActivityTaskManager.getService();
+ mTaskStackListener = new BiometricTaskStackListener();
+
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+
+ final FingerprintSensorPropertiesInternal internalProp =
+ new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ prop.sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, mContext, mHandler,
+ internalProp, gestureAvailabilityDispatcher);
+
+ mSensors.put(sensorId, sensor);
+ Slog.d(getTag(), "Added: " + internalProp);
+ }
+ }
+
+ private String getTag() {
+ return "FingerprintProvider/" + mHalInstanceName;
+ }
+
+ @Nullable
+ private synchronized IFingerprint getHalInstance() {
+ final IFingerprint daemon = IFingerprint.Stub.asInterface(
+ ServiceManager.waitForDeclaredService(mHalInstanceName));
+ if (daemon == null) {
+ Slog.e(getTag(), "Unable to get daemon");
+ return null;
+ }
+
+ try {
+ daemon.asBinder().linkToDeath(this, 0 /* flags */);
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Unable to linkToDeath", e);
+ }
+
+ for (int i = 0; i < mSensors.size(); i++) {
+ final int sensorId = mSensors.keyAt(i);
+ scheduleLoadAuthenticatorIds(sensorId);
+ scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser());
+ }
+
+ return daemon;
+ }
+
+ private void scheduleForSensor(int sensorId, @NonNull ClientMonitor<?> client) {
+ if (!mSensors.contains(sensorId)) {
+ throw new IllegalStateException("Unable to schedule client: " + client
+ + " for sensor: " + sensorId);
+ }
+ mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ }
+
+ private void scheduleForSensor(int sensorId, @NonNull ClientMonitor<?> client,
+ ClientMonitor.Callback callback) {
+ if (!mSensors.contains(sensorId)) {
+ throw new IllegalStateException("Unable to schedule client: " + client
+ + " for sensor: " + sensorId);
+ }
+ mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
+ }
+
+ private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
+ int userId) throws RemoteException {
+ // Note that per IFingerprint createSession contract, this method will block until all
+ // existing operations are canceled/finished. However, also note that this is fine, since
+ // this method "withoutHandler" means it should only ever be invoked from the worker thread,
+ // so callers will never be blocked.
+ mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
+ }
+
+ private void scheduleLoadAuthenticatorIdsWithoutHandler(int sensorId) {
+
+ }
+
+ private void scheduleLoadAuthenticatorIds(int sensorId) {
+
+ }
+
+ @Override
+ public boolean containsSensor(int sensorId) {
+ return mSensors.contains(sensorId);
+ }
+
+ @NonNull
+ @Override
+ public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
+ final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ for (int i = 0; i < mSensors.size(); i++) {
+ props.add(mSensors.valueAt(i).getSensorProperties());
+ }
+ return props;
+ }
+
+ @Override
+ public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
+ mHandler.post(() -> {
+ final IFingerprint daemon = getHalInstance();
+ if (daemon == null) {
+ Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
+ return;
+ }
+
+ try {
+ if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
+ createNewSessionWithoutHandler(daemon, sensorId, userId);
+ }
+
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
+ scheduleForSensor(sensorId, client);
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
+ }
+ });
+ }
+
+ @Override
+ public void scheduleGenerateChallenge(int sensorId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, String opPackageName) {
+ mHandler.post(() -> {
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
+ scheduleForSensor(sensorId, client);
+ });
+ }
+
+ @Override
+ public void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
+ @NonNull String opPackageName, long challenge) {
+ mHandler.post(() -> {
+ final FingerprintRevokeChallengeClient client =
+ new FingerprintRevokeChallengeClient(mContext, mLazyDaemon, token,
+ opPackageName, sensorId, challenge);
+ scheduleForSensor(sensorId, client);
+ });
+ }
+
+ @Override
+ public void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken,
+ int userId, @NonNull IFingerprintServiceReceiver receiver,
+ @NonNull String opPackageName, @Nullable Surface surface) {
+ mHandler.post(() -> {
+ final IFingerprint daemon = getHalInstance();
+ if (daemon == null) {
+ Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
+ // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
+ // this operation. We should not send the callback yet, since the scheduler may
+ // be processing something else.
+ return;
+ }
+
+ try {
+ if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
+ createNewSessionWithoutHandler(daemon, sensorId, userId);
+ }
+
+ final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+ .maxEnrollmentsPerUser;
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FingerprintUtils.getInstance(),
+ BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
+ mUdfpsOverlayController, maxTemplatesPerUser);
+ scheduleForSensor(sensorId, client, new ClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull ClientMonitor<?> clientMonitor,
+ boolean success) {
+ if (success) {
+ scheduleLoadAuthenticatorIdsWithoutHandler(sensorId);
+ }
+ }
+ });
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Remote exception when scheduling enroll", e);
+ }
+ });
+ }
+
+ @Override
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
+ mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelEnrollment(token));
+ }
+
+ @Override
+ public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ @NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
+ @Nullable Surface surface, int statsClient) {
+
+ }
+
+ @Override
+ public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean isKeyguard) {
+ mHandler.post(() -> {
+ final IFingerprint daemon = getHalInstance();
+ if (daemon == null) {
+ Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
+ // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
+ // this operation. We should not send the callback yet, since the scheduler may
+ // be processing something else.
+ return;
+ }
+
+ try {
+ if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
+ createNewSessionWithoutHandler(daemon, sensorId, userId);
+ }
+
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ operationId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+ mUdfpsOverlayController);
+ mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client);
+ } catch (RemoteException e) {
+ Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
+ }
+ });
+ }
+
+ @Override
+ public void startPreparedClient(int sensorId, int cookie) {
+ mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ mHandler.post(() -> mSensors.get(sensorId).getScheduler().cancelAuthentication(token));
+ }
+
+ @Override
+ public void scheduleRemove(int sensorId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
+
+ }
+
+ @Override
+ public void scheduleInternalCleanup(int userId, int sensorId) {
+
+ }
+
+ @Override
+ public boolean isHardwareDetected(int sensorId) {
+ return false;
+ }
+
+ @Override
+ public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
+
+ }
+
+ @NonNull
+ @Override
+ public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public int getLockoutModeForUser(int sensorId, int userId) {
+ return mSensors.get(sensorId).getLockoutCache().getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public long getAuthenticatorId(int sensorId, int userId) {
+ return 0;
+ }
+
+ @Override
+ public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
+ final ClientMonitor<?> client = mSensors.get(sensorId).getScheduler().getCurrentClient();
+ if (!(client instanceof Udfps)) {
+ Slog.e(getTag(), "onPointerDown received during client: " + client);
+ return;
+ }
+ final Udfps udfps = (Udfps) client;
+ udfps.onPointerDown(x, y, minor, major);
+ }
+
+ @Override
+ public void onPointerUp(int sensorId) {
+ final ClientMonitor<?> client = mSensors.get(sensorId).getScheduler().getCurrentClient();
+ if (!(client instanceof Udfps)) {
+ Slog.e(getTag(), "onPointerUp received during client: " + client);
+ return;
+ }
+ final Udfps udfps = (Udfps) client;
+ udfps.onPointerUp();
+ }
+
+ @Override
+ public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
+ mUdfpsOverlayController = controller;
+ }
+
+ @Override
+ public void dumpProto(int sensorId, @NonNull FileDescriptor fd) {
+
+ }
+
+ @Override
+ public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
+
+ }
+
+ @Override
+ public void binderDied() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
new file mode 100644
index 000000000000..bc35ad4b2807
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -0,0 +1,78 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+/**
+ * Fingerprint-specific resetLockout client for the {@link IFingerprint} AIDL HAL interface.
+ * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is
+ * cleared.
+ */
+public class FingerprintResetLockoutClient extends ClientMonitor<ISession> {
+
+ private static final String TAG = "FingerprintResetLockoutClient";
+
+ private final HardwareAuthToken mHardwareAuthToken;
+ private final LockoutCache mLockoutCache;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+
+ public FingerprintResetLockoutClient(@NonNull Context context,
+ @NonNull LazyDaemon<ISession> lazyDaemon, int userId, String owner, int sensorId,
+ @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
+ 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+ mLockoutCache = lockoutTracker;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ }
+
+ @Override
+ public void unableToStart() {
+ // Nothing to do here
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().resetLockout(mSequentialId, mHardwareAuthToken);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to reset lockout", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ }
+
+ void onLockoutCleared() {
+ mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_NONE);
+ mLockoutResetDispatcher.notifyLockoutResetCallbacks(getSensorId());
+ mCallback.onClientFinished(this, true /* success */);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
new file mode 100644
index 000000000000..e97dbe795f74
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java
@@ -0,0 +1,65 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.IRevokeChallengeCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.RevokeChallengeClient;
+
+/**
+ * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.
+ */
+public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<IFingerprint> {
+
+ private static final String TAG = "FingerpirntRevokeChallengeClient";
+
+ private final long mChallenge;
+
+ private final IRevokeChallengeCallback mRevokeChallengeCallback =
+ new IRevokeChallengeCallback.Stub() {
+ @Override
+ public void onChallengeRevoked(int sensorId, int userId, long challenge) {
+ final boolean success = challenge == mChallenge;
+ mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);
+ }
+ };
+
+ public FingerprintRevokeChallengeClient(
+ @NonNull Context context,
+ @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+ @NonNull IBinder token,
+ @NonNull String owner, int sensorId, long challenge) {
+ super(context, lazyDaemon, token, owner, sensorId);
+ mChallenge = challenge;
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().revokeChallenge(getSensorId(), getTargetUserId(), mChallenge,
+ mRevokeChallengeCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to revokeChallenge", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java
new file mode 100644
index 000000000000..2abbcb0c7493
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/LockoutCache.java
@@ -0,0 +1,47 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.util.SparseIntArray;
+
+import com.android.server.biometrics.sensors.LockoutTracker;
+
+/**
+ * For a single sensor, caches lockout states for all users.
+ */
+public class LockoutCache implements LockoutTracker {
+
+ // Map of userId to LockoutMode
+ private final SparseIntArray mUserLockoutStates;
+
+ LockoutCache() {
+ mUserLockoutStates = new SparseIntArray();
+ }
+
+ public void setLockoutModeForUser(int userId, @LockoutMode int mode) {
+ synchronized (this) {
+ mUserLockoutStates.put(userId, mode);
+ }
+ }
+
+ @Override
+ public int getLockoutModeForUser(int userId) {
+ synchronized (this) {
+ return mUserLockoutStates.get(userId, LOCKOUT_NONE);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
new file mode 100644
index 000000000000..dc90be8ea886
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -0,0 +1,294 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.Error;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.AcquisitionClient;
+import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitor;
+import com.android.server.biometrics.sensors.Interruptable;
+import com.android.server.biometrics.sensors.LockoutConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Maintains the state of a single sensor within an instance of the
+ * {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL.
+ */
+class Sensor {
+ @NonNull private final String mTag;
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
+ @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final LockoutCache mLockoutCache;
+
+ @Nullable private Session mCurrentSession; // TODO: Death recipient
+ @NonNull private final ClientMonitor.LazyDaemon<ISession> mLazySession;
+
+ private static class Session {
+ @NonNull private final String mTag;
+ @NonNull private final ISession mSession;
+ private final int mUserId;
+ private final ISessionCallback mSessionCallback;
+
+ Session(@NonNull String tag, @NonNull ISession session, int userId,
+ @NonNull ISessionCallback sessionCallback) {
+ mTag = tag;
+ mSession = session;
+ mUserId = userId;
+ mSessionCallback = sessionCallback;
+ Slog.d(mTag, "New session created for user: " + userId);
+ }
+ }
+
+ Sensor(@NonNull String tag, @NonNull Context context, @NonNull Handler handler,
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ mTag = tag;
+ mContext = context;
+ mHandler = handler;
+ mSensorProperties = sensorProperties;
+ mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+ mLockoutCache = new LockoutCache();
+ mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
+ }
+
+ @NonNull ClientMonitor.LazyDaemon<ISession> getLazySession() {
+ return mLazySession;
+ }
+
+ @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+ return mSensorProperties;
+ }
+
+ boolean hasSessionForUser(int userId) {
+ return mCurrentSession != null && mCurrentSession.mUserId == userId;
+ }
+
+ void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
+ throws RemoteException {
+ final ISessionCallback callback = new ISessionCallback.Stub() {
+ @Override
+ public void onStateChanged(int cookie, byte state) {
+
+ }
+
+ @Override
+ public void onAcquired(byte info, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AcquisitionClient)) {
+ Slog.e(mTag, "onAcquired for non-acquisition client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
+ acquisitionClient.onAcquired(info, vendorCode);
+ });
+ }
+
+ @Override
+ public void onError(byte error, int vendorCode) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ Slog.d(mTag, "onError"
+ + ", client: " + Utils.getClientName(client)
+ + ", error: " + error
+ + ", vendorCode: " + vendorCode);
+ if (!(client instanceof Interruptable)) {
+ Slog.e(mTag, "onError for non-error consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final Interruptable interruptable = (Interruptable) client;
+ interruptable.onError(error, vendorCode);
+
+ if (error == Error.HW_UNAVAILABLE) {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ }
+ });
+ }
+
+ @Override
+ public void onEnrollmentProgress(int enrollmentId, int remaining) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintEnrollClient)) {
+ Slog.e(mTag, "onEnrollmentProgress for non-enroll client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final int currentUserId = client.getTargetUserId();
+ final CharSequence name = FingerprintUtils.getInstance()
+ .getUniqueName(mContext, currentUserId);
+ final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, sensorId);
+
+ final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
+ enrollClient.onEnrollResult(fingerprint, remaining);
+ });
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", enrollmentId, sensorId);
+ final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat);
+ final ArrayList<Byte> byteList = new ArrayList<>();
+ for (byte b : byteArray) {
+ byteList.add(b);
+ }
+
+ authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList);
+ });
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof AuthenticationConsumer)) {
+ Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final AuthenticationConsumer authenticationConsumer =
+ (AuthenticationConsumer) client;
+ final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, sensorId);
+ authenticationConsumer
+ .onAuthenticated(fp, false /* authenticated */, null /* hat */);
+ });
+ }
+
+ @Override
+ public void onLockoutTimed(long durationMillis) {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutTimed for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutTimed(durationMillis);
+ });
+ }
+
+ @Override
+ public void onLockoutPermanent() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof LockoutConsumer)) {
+ Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final LockoutConsumer lockoutConsumer = (LockoutConsumer) client;
+ lockoutConsumer.onLockoutPermanent();
+ });
+ }
+
+ @Override
+ public void onLockoutCleared() {
+ mHandler.post(() -> {
+ final ClientMonitor<?> client = mScheduler.getCurrentClient();
+ if (!(client instanceof FingerprintResetLockoutClient)) {
+ Slog.e(mTag, "onLockoutCleared for non-resetLockout client: "
+ + Utils.getClientName(client));
+ return;
+ }
+
+ final FingerprintResetLockoutClient resetLockoutClient =
+ (FingerprintResetLockoutClient) client;
+ resetLockoutClient.onLockoutCleared();
+ });
+ }
+
+ @Override
+ public void onInteractionDetected() {
+
+ }
+
+ @Override
+ public void onEnrollmentsEnumerated(int[] enrollmentIds) {
+
+ }
+
+ @Override
+ public void onEnrollmentsRemoved(int[] enrollmentIds) {
+
+ }
+
+ @Override
+ public void onAuthenticatorIdRetrieved(long authenticatorId) {
+
+ }
+
+ @Override
+ public void onAuthenticatorIdInvalidated() {
+
+ }
+ };
+
+ final ISession newSession = daemon.createSession(sensorId, userId, callback);
+ mCurrentSession = new Session(mTag, newSession, userId, callback);
+ }
+
+ @NonNull BiometricScheduler getScheduler() {
+ return mScheduler;
+ }
+
+ @NonNull LockoutCache getLockoutCache() {
+ return mLockoutCache;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 7e62329a168a..da68bc8b79da 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -27,11 +27,13 @@ import android.app.UserSwitchObserver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
@@ -63,6 +65,10 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
import org.json.JSONArray;
import org.json.JSONException;
@@ -80,14 +86,14 @@ import java.util.Map;
* Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
* its extended minor versions.
*/
-class Fingerprint21 implements IHwBinder.DeathRecipient {
+public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider {
private static final String TAG = "Fingerprint21";
private static final int ENROLL_TIMEOUT_SEC = 60;
final Context mContext;
private final IActivityTaskManager mActivityTaskManager;
- private final FingerprintSensorProperties mSensorProperties;
+ @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
private final BiometricScheduler mScheduler;
private final Handler mHandler;
private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -240,7 +246,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
mHandler.post(() -> {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
Slog.d(TAG, "handleError"
- + ", client: " + (client != null ? client.getOwnerString() : null)
+ + ", client: " + Utils.getClientName(client)
+ ", error: " + error
+ ", vendorCode: " + vendorCode);
if (!(client instanceof Interruptable)) {
@@ -296,6 +302,7 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
Fingerprint21(@NonNull Context context, @NonNull BiometricScheduler scheduler,
@NonNull Handler handler, int sensorId,
+ @BiometricManager.Authenticators.Types int strength,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
@@ -335,25 +342,27 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
}
final @FingerprintSensorProperties.SensorType int sensorType =
- isUdfps ? FingerprintSensorProperties.TYPE_UDFPS
+ isUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
: FingerprintSensorProperties.TYPE_REAR;
// resetLockout is controlled by the framework, so hardwareAuthToken is not required
final boolean resetLockoutRequiresHardwareAuthToken = false;
- final int maxTemplatesAllowed = mContext.getResources()
+ final int maxEnrollmentsPerUser = mContext.getResources()
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
- mSensorProperties = new FingerprintSensorProperties(sensorId, sensorType,
- resetLockoutRequiresHardwareAuthToken, maxTemplatesAllowed);
+
+ mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId,
+ Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
+ sensorType, resetLockoutRequiresHardwareAuthToken);
}
- static Fingerprint21 newInstance(@NonNull Context context, int sensorId,
+ public static Fingerprint21 newInstance(@NonNull Context context, int sensorId, int strength,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
final BiometricScheduler scheduler =
new BiometricScheduler(TAG, gestureAvailabilityDispatcher);
final HalResultController controller = new HalResultController(context, handler, scheduler);
- return new Fingerprint21(context, scheduler, handler, sensorId, lockoutResetDispatcher,
- controller);
+ return new Fingerprint21(context, scheduler, handler, sensorId, strength,
+ lockoutResetDispatcher, controller);
}
@Override
@@ -429,9 +438,6 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
@Nullable IUdfpsOverlayController getUdfpsOverlayController() {
return mUdfpsOverlayController;
}
- @LockoutTracker.LockoutMode int getLockoutModeForUser(int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
private void scheduleLoadAuthenticatorIds() {
// Note that this can be performed on the scheduler (as opposed to being done immediately
@@ -460,7 +466,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
* correct.
*/
private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
- final boolean hasEnrolled = !getEnrolledFingerprints(targetUserId).isEmpty();
+ final boolean hasEnrolled =
+ !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
final FingerprintUpdateActiveUserClient client =
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId, mCurrentUserId,
@@ -475,7 +482,21 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void scheduleResetLockout(int userId) {
+ @Override
+ public boolean containsSensor(int sensorId) {
+ return mSensorProperties.sensorId == sensorId;
+ }
+
+ @Override
+ @NonNull
+ public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
+ final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
+ properties.add(mSensorProperties);
+ return properties;
+ }
+
+ @Override
+ public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
// Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
// thread.
mHandler.post(() -> {
@@ -483,7 +504,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void scheduleGenerateChallenge(@NonNull IBinder token,
+ @Override
+ public void scheduleGenerateChallenge(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
final FingerprintGenerateChallengeClient client =
@@ -494,7 +516,9 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void scheduleRevokeChallenge(@NonNull IBinder token, @NonNull String opPackageName) {
+ @Override
+ public void scheduleRevokeChallenge(int sensorId, @NonNull IBinder token,
+ @NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
mContext, mLazyDaemon, token, opPackageName, mSensorProperties.sensorId);
@@ -502,7 +526,9 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void scheduleEnroll(@NonNull IBinder token, @NonNull byte[] hardwareAuthToken, int userId,
+ @Override
+ public void scheduleEnroll(int sensorId, @NonNull IBinder token,
+ @NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
@Nullable Surface surface) {
mHandler.post(() -> {
@@ -525,13 +551,15 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void cancelEnrollment(@NonNull IBinder token) {
+ @Override
+ public void cancelEnrollment(int sensorId, @NonNull IBinder token) {
mHandler.post(() -> {
mScheduler.cancelEnrollment(token);
});
}
- void scheduleFingerDetect(@NonNull IBinder token, int userId,
+ @Override
+ public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
@Nullable Surface surface, int statsClient) {
mHandler.post(() -> {
@@ -546,9 +574,11 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void scheduleAuthenticate(@NonNull IBinder token, long operationId, int userId, int cookie,
- @NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
- boolean restricted, int statsClient, boolean isKeyguard) {
+ @Override
+ public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean isKeyguard) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -562,20 +592,20 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- void startPreparedClient(int cookie) {
- mHandler.post(() -> {
- mScheduler.startPreparedClient(cookie);
- });
+ @Override
+ public void startPreparedClient(int sensorId, int cookie) {
+ mHandler.post(() -> mScheduler.startPreparedClient(cookie));
}
- void cancelAuthentication(@NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelAuthentication(token);
- });
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ mHandler.post(() -> mScheduler.cancelAuthentication(token));
}
- void scheduleRemove(@NonNull IBinder token, @NonNull IFingerprintServiceReceiver receiver,
- int fingerId, int userId, @NonNull String opPackageName) {
+ @Override
+ public void scheduleRemove(int sensorId, @NonNull IBinder token,
+ @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
+ @NonNull String opPackageName) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -591,7 +621,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
- final List<Fingerprint> enrolledList = getEnrolledFingerprints(userId);
+ final List<Fingerprint> enrolledList = getEnrolledFingerprints(
+ mSensorProperties.sensorId, userId);
final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
mSensorProperties.sensorId, enrolledList, FingerprintUtils.getInstance(),
@@ -600,54 +631,69 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
});
}
- boolean isHardwareDetected() {
- final IBiometricsFingerprint daemon = getDaemon();
- return daemon != null;
+ @Override
+ public void scheduleInternalCleanup(int userId, int sensorId) {
+ scheduleInternalCleanup(userId);
}
- @NonNull FingerprintSensorProperties getFingerprintSensorProperties() {
- return mSensorProperties;
+ @Override
+ public boolean isHardwareDetected(int sensorId) {
+ final IBiometricsFingerprint daemon = getDaemon();
+ return daemon != null;
}
- void rename(int fingerId, int userId, String name) {
+ @Override
+ public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
mHandler.post(() -> {
FingerprintUtils.getInstance().renameBiometricForUser(mContext, userId, fingerId, name);
});
}
- List<Fingerprint> getEnrolledFingerprints(int userId) {
+ @Override
+ @NonNull
+ public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
return FingerprintUtils.getInstance().getBiometricsForUser(mContext, userId);
}
- long getAuthenticatorId(int userId) {
+ @Override
+ @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public long getAuthenticatorId(int sensorId, int userId) {
return mAuthenticatorIds.get(userId);
}
- void onFingerDown(int x, int y, float minor, float major) {
+ @Override
+ public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof Udfps)) {
Slog.w(TAG, "onFingerDown received during client: " + client);
return;
}
final Udfps udfps = (Udfps) client;
- udfps.onFingerDown(x, y, minor, major);
+ udfps.onPointerDown(x, y, minor, major);
}
- void onFingerUp() {
+ @Override
+ public void onPointerUp(int sensorId) {
final ClientMonitor<?> client = mScheduler.getCurrentClient();
if (!(client instanceof Udfps)) {
Slog.w(TAG, "onFingerDown received during client: " + client);
return;
}
final Udfps udfps = (Udfps) client;
- udfps.onFingerUp();
+ udfps.onPointerUp();
}
- void setUdfpsOverlayController(IUdfpsOverlayController controller) {
+ @Override
+ public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
mUdfpsOverlayController = controller;
}
- void dumpProto(FileDescriptor fd) {
+ @Override
+ public void dumpProto(int sensorId, FileDescriptor fd) {
PerformanceTracker tracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
@@ -687,7 +733,8 @@ class Fingerprint21 implements IHwBinder.DeathRecipient {
tracker.clear();
}
- void dumpInternal(@NonNull PrintWriter pw) {
+ @Override
+ public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
PerformanceTracker performanceTracker =
PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 7cab2b889eaa..e4933e40ccd5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -36,13 +38,16 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import com.android.internal.R;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
+import java.util.List;
import java.util.Random;
/**
@@ -120,7 +125,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
@NonNull private final TestableBiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
- @NonNull private final FingerprintSensorProperties mSensorProperties;
+ @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
@NonNull private final MockHalResultController mMockHalResultController;
@NonNull private final TrustManager mTrustManager;
@NonNull private final SparseBooleanArray mUserHasTrust;
@@ -265,7 +270,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
}
}
- static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
+ public static Fingerprint21UdfpsMock newInstance(@NonNull Context context, int sensorId,
+ @BiometricManager.Authenticators.Types int strength,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
Slog.d(TAG, "Creating Fingerprint23Mock!");
@@ -275,7 +281,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, scheduler, handler, sensorId,
+ return new Fingerprint21UdfpsMock(context, scheduler, handler, sensorId, strength,
lockoutResetDispatcher, controller);
}
@@ -393,17 +399,19 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
// Schedule this only after we invoke onClientFinished for the previous client, so that
// internal preemption logic is not run.
- mFingerprint21.scheduleAuthenticate(token, operationId, user, cookie,
- listener, opPackageName, restricted, statsClient, isKeyguard);
+ mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
+ operationId, user, cookie, listener, opPackageName, restricted, statsClient,
+ isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler, int sensorId,
+ @BiometricManager.Authenticators.Types int strength,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
- super(context, scheduler, handler, sensorId, lockoutResetDispatcher, controller);
+ super(context, scheduler, handler, sensorId, strength, lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
@@ -411,9 +419,10 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
final boolean resetLockoutRequiresHardwareAuthToken = false;
final int maxTemplatesAllowed = mContext.getResources()
.getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
- mSensorProperties = new FingerprintSensorProperties(sensorId,
- FingerprintSensorProperties.TYPE_UDFPS, resetLockoutRequiresHardwareAuthToken,
- maxTemplatesAllowed);
+ mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId,
+ Utils.authenticatorStrengthToPropertyStrength(strength), maxTemplatesAllowed,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ resetLockoutRequiresHardwareAuthToken);
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
mTrustManager = context.getSystemService(TrustManager.class);
@@ -445,12 +454,14 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
@Override
@NonNull
- FingerprintSensorProperties getFingerprintSensorProperties() {
- return mSensorProperties;
+ public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
+ final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
+ properties.add(mSensorProperties);
+ return properties;
}
@Override
- void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int sensorId, int x, int y, float minor, float major) {
mHandler.post(() -> {
Slog.d(TAG, "onFingerDown");
final AuthenticationConsumer lastAuthenticatedConsumer =
@@ -497,7 +508,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
}
@Override
- void onFingerUp() {
+ public void onPointerUp(int sensorId) {
mHandler.post(() -> {
Slog.d(TAG, "onFingerUp");
@@ -552,7 +563,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
// Things can happen before SysUI loads and sets the controller.
if (controller != null) {
Slog.d(TAG, "setDebugMessage: " + message);
- controller.setDebugMessage(message);
+ controller.setDebugMessage(mSensorProperties.sensorId, message);
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when sending message: " + message, e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 99d348a780f3..46605d1a2d2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,6 +33,8 @@ import android.util.Slog;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.ArrayList;
@@ -79,7 +81,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
if (authenticated) {
resetFailedAttempts(getTargetUserId());
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
mCallback.onClientFinished(this, true /* success */);
} else {
final @LockoutTracker.LockoutMode int lockoutMode =
@@ -92,7 +94,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
// Send the error, but do not invoke the FinishCallback yet. Since lockout is not
// controlled by the HAL, the framework must stop the sensor before finishing the
// client.
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
cancel();
}
@@ -111,7 +113,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().authenticate(mOperationId, getTargetUserId());
@@ -119,14 +121,14 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -138,12 +140,12 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
}
@Override
- public void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int x, int y, float minor, float major) {
UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
}
@Override
- public void onFingerUp() {
+ public void onPointerUp() {
UdfpsHelper.onFingerUp(getFreshDaemon());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8652ee403089..4747488e5a70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,6 +32,8 @@ import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.ArrayList;
@@ -61,7 +63,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -80,25 +82,25 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
- public void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int x, int y, float minor, float major) {
UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
}
@Override
- public void onFingerUp() {
+ public void onPointerUp() {
UdfpsHelper.onFingerUp(getFreshDaemon());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index d5db6e411b95..975ac3d6f2bf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +30,8 @@ import android.util.Slog;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
/**
* Fingerprint-specific enroll client supporting the
@@ -70,7 +72,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.showUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);
@@ -78,14 +80,14 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(mUdfpsOverlayController);
+ UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -97,12 +99,12 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
}
@Override
- public void onFingerDown(int x, int y, float minor, float major) {
+ public void onPointerDown(int x, int y, float minor, float major) {
UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
}
@Override
- public void onFingerUp() {
+ public void onPointerUp() {
UdfpsHelper.onFingerUp(getFreshDaemon());
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
index 8fb8c992268d..5169c7de8cdd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintGenerateChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
@@ -45,7 +45,14 @@ public class FingerprintGenerateChallengeClient
@Override
protected void startHalOperation() {
try {
- mChallenge = getFreshDaemon().preEnroll();
+ final long challenge = getFreshDaemon().preEnroll();
+ try {
+ getListener().onChallengeGenerated(getSensorId(), challenge);
+ mCallback.onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ mCallback.onClientFinished(this, false /* success */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "preEnroll failed", e);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
index 571d2b80fd3e..e0611120c69a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
index 834bf42eba3f..5fd1d1e812ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintInternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
index 9f5456300884..4bbb7efa2166 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
index 882660e7a618..8f58cae51575 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintRevokeChallengeClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index c1c3593db564..00e2413c2f38 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import android.annotation.NonNull;
import android.content.Context;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index e7b2ae76f083..4fc1545c4334 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.biometrics.sensors.fingerprint;
+package com.android.server.biometrics.sensors.fingerprint.hidl;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 5d2f51230c12..507600783aa4 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -836,7 +836,7 @@ public class ClipboardService extends SystemService {
return;
}
if (Settings.Global.getInt(getContext().getContentResolver(),
- "clipboard_access_toast_enabled", 0) == 0) {
+ "clipboard_access_toast_enabled", 1) == 0) {
return;
}
// Don't notify if the app accessing the clipboard is the same as the current owner.
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 3e619200d414..0304cdc47515 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -23,7 +23,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -70,8 +69,6 @@ public class DataConnectionStats extends BroadcastReceiver {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
mContext.registerReceiver(this, filter, null /* broadcastPermission */, mListenerHandler);
}
@@ -81,10 +78,7 @@ public class DataConnectionStats extends BroadcastReceiver {
if (action.equals(Intent.ACTION_SIM_STATE_CHANGED)) {
updateSimState(intent);
notePhoneDataConnectionState();
- } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
- action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
- notePhoneDataConnectionState();
- }
+ }
}
private void notePhoneDataConnectionState() {
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 855578d9fbe4..b0316e2fd620 100755
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -47,7 +47,6 @@ import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.SocketKeepalive.InvalidSocketException;
import android.net.TcpKeepalivePacketData;
-import android.net.util.IpUtils;
import android.net.util.KeepaliveUtils;
import android.os.Binder;
import android.os.Handler;
@@ -63,6 +62,7 @@ import android.util.Pair;
import com.android.internal.R;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.IpUtils;
import java.io.FileDescriptor;
import java.net.InetAddress;
@@ -371,6 +371,13 @@ public class KeepaliveTracker {
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
}
}
+ // Ignore the case when the network disconnects immediately after stop() has been
+ // called and the keepalive code is waiting for the response from the modem. This
+ // might happen when the caller listens for a lower-layer network disconnect
+ // callback and stop the keepalive at that time. But the stop() races with the
+ // stop() generated in ConnectivityService network disconnection code path.
+ if (mStartedState == STOPPING && reason == ERROR_INVALID_NETWORK) return;
+
// Store the reason of stopping, and report it after the keepalive is fully stopped.
if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + mStopReason);
@@ -384,9 +391,6 @@ public class KeepaliveTracker {
// e.g. invalid parameter.
cleanupStoppedKeepalive(mNai, mSlot);
break;
- case STOPPING:
- // Keepalive is already in stopping state, ignore.
- return;
default:
mStartedState = STOPPING;
switch (mType) {
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
index de302fc01f2d..198de78ecfa6 100644
--- a/services/core/java/com/android/server/connectivity/PacManager.java
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -154,7 +154,7 @@ public class PacManager {
mNetThreadHandler = new Handler(netThread.getLooper());
mPacRefreshIntent = PendingIntent.getBroadcast(
- context, 0, new Intent(ACTION_PAC_REFRESH), 0);
+ context, 0, new Intent(ACTION_PAC_REFRESH), PendingIntent.FLAG_IMMUTABLE);
context.registerReceiver(new PacRefreshIntentReceiver(),
new IntentFilter(ACTION_PAC_REFRESH));
mConnectivityHandler = handler;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 99dc58e20209..1ed6b35341b8 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -77,6 +77,7 @@ import android.net.ipsec.ike.ChildSessionParams;
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionParams;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -142,6 +143,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -378,8 +380,8 @@ public class Vpn {
}
}
- public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
- return vpn.jniCheck(iface) == 0;
+ public boolean isInterfacePresent(final Vpn vpn, final String iface) {
+ return vpn.jniCheck(iface) != 0;
}
}
@@ -964,7 +966,7 @@ public class Vpn {
/** Prepare the VPN for the given package. Does not perform permission checks. */
@GuardedBy("this")
private void prepareInternal(String newPackage) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
// Reset the interface.
if (mInterface != null) {
@@ -1128,7 +1130,13 @@ public class Vpn {
return mNetworkInfo;
}
- public int getNetId() {
+ /**
+ * Return netId of current running VPN network.
+ *
+ * @return a netId if there is a running VPN network or NETID_UNSET if there is no running VPN
+ * network or network is null.
+ */
+ public synchronized int getNetId() {
final NetworkAgent agent = mNetworkAgent;
if (null == agent) return NETID_UNSET;
final Network network = agent.getNetwork();
@@ -1243,7 +1251,7 @@ public class Vpn {
mNetworkCapabilities.setAdministratorUids(new int[] {mOwnerUID});
mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
mNetworkInfo, mNetworkCapabilities, lp,
@@ -1262,7 +1270,7 @@ public class Vpn {
}
private boolean canHaveRestrictedProfile(int userId) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
return UserManager.get(mContext).canHaveRestrictedProfile(userId);
} finally {
@@ -1309,7 +1317,7 @@ public class Vpn {
// Check if the service is properly declared.
Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
intent.setClassName(mPackage, config.user);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
// Restricted users are not allowed to create VPNs, they are tied to Owner
enforceNotRestrictedUser();
@@ -1600,7 +1608,7 @@ public class Vpn {
*/
public synchronized void onUserStopped() {
// Switch off networking lockdown (if it was enabled)
- setLockdown(false);
+ setVpnForcedLocked(false);
mAlwaysOn = false;
// Quit any active connections
@@ -1706,7 +1714,7 @@ public class Vpn {
/**
* Return the configuration of the currently running VPN.
*/
- public VpnConfig getVpnConfig() {
+ public synchronized VpnConfig getVpnConfig() {
enforceControlPermission();
return mConfig;
}
@@ -2037,7 +2045,7 @@ public class Vpn {
*/
public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
enforceControlPermission();
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
startLegacyVpnPrivileged(profile, keyStore, egress);
} finally {
@@ -2144,7 +2152,11 @@ public class Vpn {
break;
}
- // Prepare arguments for mtpd.
+ // Prepare arguments for mtpd. MTU/MRU calculated conservatively. Only IPv4 supported
+ // because LegacyVpn.
+ // 1500 - 60 (Carrier-internal IPv6 + UDP + GTP) - 10 (PPP) - 16 (L2TP) - 8 (UDP)
+ // - 77 (IPsec w/ SHA-2 512, 256b trunc-len, AES-CBC) - 8 (UDP encap) - 20 (IPv4)
+ // - 28 (464xlat)
String[] mtpd = null;
switch (profile.type) {
case VpnProfile.TYPE_PPTP:
@@ -2152,7 +2164,7 @@ public class Vpn {
iface, "pptp", profile.server, "1723",
"name", profile.username, "password", profile.password,
"linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
(profile.mppe ? "+mppe" : "nomppe"),
};
break;
@@ -2162,7 +2174,7 @@ public class Vpn {
iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
"name", profile.username, "password", profile.password,
"linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+ "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
};
break;
}
@@ -2301,7 +2313,7 @@ public class Vpn {
void onChildTransformCreated(
@NonNull Network network, @NonNull IpSecTransform transform, int direction);
- void onSessionLost(@NonNull Network network);
+ void onSessionLost(@NonNull Network network, @Nullable Exception exception);
}
/**
@@ -2458,7 +2470,7 @@ public class Vpn {
networkAgent.sendLinkProperties(lp);
} catch (Exception e) {
Log.d(TAG, "Error in ChildOpened for network " + network, e);
- onSessionLost(network);
+ onSessionLost(network, e);
}
}
@@ -2488,7 +2500,7 @@ public class Vpn {
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
} catch (IOException e) {
Log.d(TAG, "Transform application failed for network " + network, e);
- onSessionLost(network);
+ onSessionLost(network, e);
}
}
@@ -2546,11 +2558,20 @@ public class Vpn {
Log.d(TAG, "Ike Session started for network " + network);
} catch (Exception e) {
Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
- onSessionLost(network);
+ onSessionLost(network, e);
}
});
}
+ /** Marks the state as FAILED, and disconnects. */
+ private void markFailedAndDisconnect(Exception exception) {
+ synchronized (Vpn.this) {
+ updateState(DetailedState.FAILED, exception.getMessage());
+ }
+
+ disconnectVpnRunner();
+ }
+
/**
* Handles loss of a session
*
@@ -2560,7 +2581,7 @@ public class Vpn {
* <p>This method MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*/
- public void onSessionLost(@NonNull Network network) {
+ public void onSessionLost(@NonNull Network network, @Nullable Exception exception) {
if (!isActiveNetwork(network)) {
Log.d(TAG, "onSessionLost() called for obsolete network " + network);
@@ -2572,6 +2593,27 @@ public class Vpn {
return;
}
+ if (exception instanceof IkeProtocolException) {
+ final IkeProtocolException ikeException = (IkeProtocolException) exception;
+
+ switch (ikeException.getErrorType()) {
+ case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
+ case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
+ // All the above failures are configuration errors, and are terminal
+ markFailedAndDisconnect(exception);
+ return;
+ // All other cases possibly recoverable.
+ }
+ } else if (exception instanceof IllegalArgumentException) {
+ // Failed to build IKE/ChildSessionParams; fatal profile configuration error
+ markFailedAndDisconnect(exception);
+ return;
+ }
+
mActiveNetwork = null;
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
@@ -2621,12 +2663,18 @@ public class Vpn {
}
/**
- * Cleans up all Ikev2VpnRunner internal state
+ * Disconnects and shuts down this VPN.
+ *
+ * <p>This method resets all internal Ikev2VpnRunner state, but unless called via
+ * VpnRunner#exit(), this Ikev2VpnRunner will still be listed as the active VPN of record
+ * until the next VPN is started, or the Ikev2VpnRunner is explicitly exited. This is
+ * necessary to ensure that the detailed state is shown in the Settings VPN menus; if the
+ * active VPN is cleared, Settings VPNs will not show the resultant state or errors.
*
* <p>This method MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*/
- private void shutdownVpnRunner() {
+ private void disconnectVpnRunner() {
mActiveNetwork = null;
mIsRunning = false;
@@ -2640,9 +2688,13 @@ public class Vpn {
@Override
public void exitVpnRunner() {
- mExecutor.execute(() -> {
- shutdownVpnRunner();
- });
+ try {
+ mExecutor.execute(() -> {
+ disconnectVpnRunner();
+ });
+ } catch (RejectedExecutionException ignored) {
+ // The Ikev2VpnRunner has already shut down.
+ }
}
}
@@ -2713,7 +2765,10 @@ public class Vpn {
final LinkProperties lp = cm.getLinkProperties(network);
if (lp != null && lp.getAllInterfaceNames().contains(mOuterInterface)) {
final NetworkInfo networkInfo = cm.getNetworkInfo(network);
- if (networkInfo != null) mOuterConnection.set(networkInfo.getType());
+ if (networkInfo != null) {
+ mOuterConnection.set(networkInfo.getType());
+ break;
+ }
}
}
}
@@ -2944,7 +2999,7 @@ public class Vpn {
checkInterruptAndDelay(false);
// Check if the interface is gone while we are waiting.
- if (mDeps.checkInterfacePresent(Vpn.this, mConfig.interfaze)) {
+ if (!mDeps.isInterfacePresent(Vpn.this, mConfig.interfaze)) {
throw new IllegalStateException(mConfig.interfaze + " is gone");
}
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
index 103f659cc258..626303001ba0 100644
--- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -270,13 +270,13 @@ public class VpnIkev2Utils {
@Override
public void onClosed() {
Log.d(mTag, "IkeClosed for network " + mNetwork);
- mCallback.onSessionLost(mNetwork); // Server requested session closure. Retry?
+ mCallback.onSessionLost(mNetwork, null); // Server requested session closure. Retry?
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
- mCallback.onSessionLost(mNetwork);
+ mCallback.onSessionLost(mNetwork, exception);
}
@Override
@@ -306,13 +306,13 @@ public class VpnIkev2Utils {
@Override
public void onClosed() {
Log.d(mTag, "ChildClosed for network " + mNetwork);
- mCallback.onSessionLost(mNetwork);
+ mCallback.onSessionLost(mNetwork, null);
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
- mCallback.onSessionLost(mNetwork);
+ mCallback.onSessionLost(mNetwork, exception);
}
@Override
@@ -349,7 +349,7 @@ public class VpnIkev2Utils {
@Override
public void onLost(@NonNull Network network) {
Log.d(mTag, "Tearing down; lost network: " + network);
- mCallback.onSessionLost(network);
+ mCallback.onSessionLost(network, null);
}
}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 1294e9030f62..a2c427b8036a 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -596,7 +596,7 @@ public final class ContentService extends IContentService.Stub {
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -650,7 +650,7 @@ public final class ContentService extends IContentService.Stub {
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager == null) {
@@ -718,7 +718,7 @@ public final class ContentService extends IContentService.Stub {
"no permission to modify the sync settings for user " + userId);
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
if (cname != null) {
Slog.e(TAG, "cname not null.");
return;
@@ -751,7 +751,7 @@ public final class ContentService extends IContentService.Stub {
Bundle extras = new Bundle(request.getBundle());
validateExtras(callingUid, extras);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncStorageEngine.EndPoint info;
@@ -834,7 +834,7 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -866,7 +866,7 @@ public final class ContentService extends IContentService.Stub {
final int callingPid = Binder.getCallingPid();
final int syncExemptionFlag = getSyncExemptionForCaller(callingUid);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -899,7 +899,7 @@ public final class ContentService extends IContentService.Stub {
pollFrequency = clampPeriod(pollFrequency);
long defaultFlex = SyncStorageEngine.calculateDefaultFlexTime(pollFrequency);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncStorageEngine.EndPoint info =
new SyncStorageEngine.EndPoint(account, authority, userId);
@@ -927,7 +927,7 @@ public final class ContentService extends IContentService.Stub {
final int callingUid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
getSyncManager()
.removePeriodicSync(
@@ -951,7 +951,7 @@ public final class ContentService extends IContentService.Stub {
"no permission to read the sync settings");
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
return getSyncManager().getPeriodicSyncs(
new SyncStorageEngine.EndPoint(account, providerName, userId));
@@ -976,7 +976,7 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -1012,7 +1012,7 @@ public final class ContentService extends IContentService.Stub {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -1040,7 +1040,7 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -1067,7 +1067,7 @@ public final class ContentService extends IContentService.Stub {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
@@ -1084,7 +1084,7 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
int userId = UserHandle.getCallingUserId();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager == null) {
@@ -1116,7 +1116,7 @@ public final class ContentService extends IContentService.Stub {
final boolean canAccessAccounts =
mContext.checkCallingOrSelfPermission(Manifest.permission.GET_ACCOUNTS)
== PackageManager.PERMISSION_GRANTED;
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
return getSyncManager().getSyncStorageEngine()
.getCurrentSyncsCopy(userId, canAccessAccounts);
@@ -1146,7 +1146,7 @@ public final class ContentService extends IContentService.Stub {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager == null) {
@@ -1176,7 +1176,7 @@ public final class ContentService extends IContentService.Stub {
"no permission to read the sync stats");
enforceCrossUserPermission(userId,
"no permission to retrieve the sync settings for user " + userId);
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
SyncManager syncManager = getSyncManager();
if (syncManager == null) return false;
@@ -1196,7 +1196,7 @@ public final class ContentService extends IContentService.Stub {
@Override
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
final int callingUid = Binder.getCallingUid();
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null && callback != null) {
@@ -1210,7 +1210,7 @@ public final class ContentService extends IContentService.Stub {
@Override
public void removeStatusChangeListener(ISyncStatusObserver callback) {
- long identityToken = clearCallingIdentity();
+ final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null && callback != null) {
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 91fa15a913a3..c686ba4c4b5c 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2773,7 +2773,7 @@ public class SyncManager {
}
private void onReady() {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mOnReadyCallback.onReady();
} finally {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
new file mode 100644
index 000000000000..7e3c1ab50ad5
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -0,0 +1,276 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.devicestate.IDeviceStateManager;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+import com.android.server.policy.DeviceStatePolicyImpl;
+
+import java.util.Arrays;
+
+/**
+ * A system service that manages the state of a device with user-configurable hardware like a
+ * foldable phone.
+ * <p>
+ * Device state is an abstract concept that allows mapping the current state of the device to the
+ * state of the system. For example, system services (like
+ * {@link com.android.server.display.DisplayManagerService display manager} and
+ * {@link com.android.server.wm.WindowManagerService window manager}) and system UI may have
+ * different behaviors depending on the physical state of the device. This is useful for
+ * variable-state devices, like foldable or rollable devices, that can be configured by users into
+ * differing hardware states, which each may have a different expected use case.
+ * </p>
+ * <p>
+ * The {@link DeviceStateManagerService} is responsible for receiving state change requests from
+ * the {@link DeviceStateProvider} to modify the current device state and communicating with the
+ * {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state.
+ * </p>
+ *
+ * @see DeviceStatePolicy
+ */
+public final class DeviceStateManagerService extends SystemService {
+ private static final String TAG = "DeviceStateManagerService";
+ private static final boolean DEBUG = false;
+
+ private final Object mLock = new Object();
+ @NonNull
+ private final DeviceStatePolicy mDeviceStatePolicy;
+
+ @GuardedBy("mLock")
+ private IntArray mSupportedDeviceStates;
+
+ // The current committed device state.
+ @GuardedBy("mLock")
+ private int mCommittedState = INVALID_DEVICE_STATE;
+ // The device state that is currently pending callback from the policy to be committed.
+ @GuardedBy("mLock")
+ private int mPendingState = INVALID_DEVICE_STATE;
+ // Whether or not the policy is currently waiting to be notified of the current pending state.
+ @GuardedBy("mLock")
+ private boolean mIsPolicyWaitingForState = false;
+ // The device state that is currently requested and is next to be configured and committed.
+ @GuardedBy("mLock")
+ private int mRequestedState = INVALID_DEVICE_STATE;
+
+ public DeviceStateManagerService(@NonNull Context context) {
+ this(context, new DeviceStatePolicyImpl());
+ }
+
+ @VisibleForTesting
+ DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
+ super(context);
+ mDeviceStatePolicy = policy;
+ }
+
+ @Override
+ public void onStart() {
+ mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+ publishBinderService(Context.DEVICE_STATE_SERVICE, new BinderService());
+ }
+
+ /**
+ * Returns the current state the system is in. Note that the system may be in the process of
+ * configuring a different state.
+ *
+ * @see #getPendingState()
+ */
+ @VisibleForTesting
+ int getCommittedState() {
+ synchronized (mLock) {
+ return mCommittedState;
+ }
+ }
+
+ /**
+ * Returns the state the system is currently configuring, or {@link #INVALID_DEVICE_STATE} if
+ * the system is not in the process of configuring a state.
+ */
+ @VisibleForTesting
+ int getPendingState() {
+ synchronized (mLock) {
+ return mPendingState;
+ }
+ }
+
+ /**
+ * Returns the requested state. The service will configure the device to match the requested
+ * state when possible.
+ */
+ @VisibleForTesting
+ int getRequestedState() {
+ synchronized (mLock) {
+ return mRequestedState;
+ }
+ }
+
+ private void updateSupportedStates(int[] supportedDeviceStates) {
+ // Must ensure sorted as isSupportedStateLocked() impl uses binary search.
+ Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length);
+ synchronized (mLock) {
+ mSupportedDeviceStates = IntArray.wrap(supportedDeviceStates);
+
+ if (mRequestedState != INVALID_DEVICE_STATE
+ && !isSupportedStateLocked(mRequestedState)) {
+ // The current requested state is no longer valid. We'll clear it here, though
+ // we won't actually update the current state with a call to
+ // updatePendingStateLocked() as doing so will not have any effect.
+ mRequestedState = INVALID_DEVICE_STATE;
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if the provided state is supported. Requires that
+ * {@link #mSupportedDeviceStates} is sorted prior to calling.
+ */
+ private boolean isSupportedStateLocked(int state) {
+ return mSupportedDeviceStates.binarySearch(state) >= 0;
+ }
+
+ /**
+ * Requests that the system enter the provided {@code state}. The request may not be honored
+ * under certain conditions, for example if the provided state is not supported.
+ *
+ * @see #isSupportedStateLocked(int)
+ */
+ private void requestState(int state) {
+ synchronized (mLock) {
+ if (isSupportedStateLocked(state)) {
+ mRequestedState = state;
+ }
+ updatePendingStateLocked();
+ }
+
+ notifyPolicyIfNeeded();
+ }
+
+ /**
+ * Tries to update the current configuring state with the current requested state. Must call
+ * {@link #notifyPolicyIfNeeded()} to actually notify the policy that the state is being
+ * changed.
+ */
+ private void updatePendingStateLocked() {
+ if (mRequestedState == INVALID_DEVICE_STATE) {
+ // No currently requested state.
+ return;
+ }
+
+ if (mPendingState != INVALID_DEVICE_STATE) {
+ // Have pending state, can not configure a new state until the state is committed.
+ return;
+ }
+
+ if (mRequestedState == mCommittedState) {
+ // No need to notify the policy as the committed state matches the requested state.
+ return;
+ }
+
+ mPendingState = mRequestedState;
+ mIsPolicyWaitingForState = true;
+ }
+
+ /**
+ * Notifies the policy to configure the supplied state. Should not be called with {@link #mLock}
+ * held.
+ */
+ private void notifyPolicyIfNeeded() {
+ if (Thread.holdsLock(mLock)) {
+ Throwable error = new Throwable("Attempting to notify DeviceStatePolicy with service"
+ + " lock held");
+ error.fillInStackTrace();
+ Slog.w(TAG, error);
+ }
+ int state;
+ synchronized (mLock) {
+ if (!mIsPolicyWaitingForState) {
+ return;
+ }
+ mIsPolicyWaitingForState = false;
+ state = mPendingState;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Notifying policy to configure state: " + state);
+ }
+ mDeviceStatePolicy.configureDeviceForState(state, this::commitPendingState);
+ }
+
+ /**
+ * Commits the current pending state after a callback from the {@link DeviceStatePolicy}.
+ *
+ * <pre>
+ * ------------- ----------- -------------
+ * Provider -> | Requested | -> | Pending | -> Policy -> | Committed |
+ * ------------- ----------- -------------
+ * </pre>
+ * <p>
+ * When a new state is requested it immediately enters the requested state. Once the policy is
+ * available to accept a new state, which could also be immediately if there is no current
+ * pending state at the point of request, the policy is notified and a callback is provided to
+ * trigger the state to be committed.
+ * </p>
+ */
+ private void commitPendingState() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Committing state: " + mPendingState);
+ }
+ mCommittedState = mPendingState;
+ mPendingState = INVALID_DEVICE_STATE;
+ updatePendingStateLocked();
+ }
+
+ notifyPolicyIfNeeded();
+ }
+
+ private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
+ @Override
+ public void onSupportedDeviceStatesChanged(int[] newDeviceStates) {
+ for (int i = 0; i < newDeviceStates.length; i++) {
+ if (newDeviceStates[i] < 0) {
+ throw new IllegalArgumentException("Supported device states includes invalid"
+ + " value: " + newDeviceStates[i]);
+ }
+ }
+
+ updateSupportedStates(newDeviceStates);
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ if (state < 0) {
+ throw new IllegalArgumentException("Invalid state: " + state);
+ }
+
+ requestState(state);
+ }
+ }
+
+ /** Implementation of {@link IDeviceStateManager} published as a binder service. */
+ private final class BinderService extends IDeviceStateManager.Stub {
+
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
new file mode 100644
index 000000000000..274b8e562098
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStatePolicy.java
@@ -0,0 +1,40 @@
+/*
+ * 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.devicestate;
+
+import android.annotation.NonNull;
+
+/**
+ * Interface for the component responsible for supplying the current device state as well as
+ * configuring the state of the system in response to device state changes.
+ *
+ * @see DeviceStateManagerService
+ */
+public interface DeviceStatePolicy {
+ /** Returns the provider of device states. */
+ DeviceStateProvider getDeviceStateProvider();
+
+ /**
+ * Configures the system into the provided state. Guaranteed not to be called again until the
+ * {@code onComplete} callback has been executed.
+ *
+ * @param state the state the system should be configured for.
+ * @param onComplete a callback that must be triggered once the system has been properly
+ * configured to match the supplied state.
+ */
+ void configureDeviceForState(int state, @NonNull Runnable onComplete);
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
new file mode 100644
index 000000000000..0e8bf9bda4aa
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.devicestate;
+
+/**
+ * Responsible for providing the set of currently supported device states and well as the current
+ * device state.
+ *
+ * @see DeviceStatePolicy
+ */
+public interface DeviceStateProvider {
+ /**
+ * Registers a listener for changes in provider state.
+ * <p>
+ * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(int[])} be called
+ * followed by {@link Listener#onStateChanged(int)} with the initial values on successful
+ * registration of the listener.
+ */
+ void setListener(Listener listener);
+
+ /** Callback for changes in {@link DeviceStateProvider} state. */
+ interface Listener {
+ /**
+ * Called to notify the listener of a change in supported device states. Required to be
+ * called once on successful registration of the listener and then once on every
+ * subsequent change in supported device states.
+ * <p>
+ * The set of device states can change based on the current hardware state of the device.
+ * For example, if a device state depends on a particular peripheral device (display, etc)
+ * it would only be reported as supported when the device is plugged. Otherwise, it should
+ * not be included in the set of supported states.
+ * <p>
+ * All values provided must be greater than or equal to zero and there must always be at
+ * least one supported device state.
+ *
+ * @param newDeviceStates array of supported device states.
+ *
+ * @throws IllegalArgumentException if the list of device states is empty or if one of the
+ * provided states is less than 0.
+ */
+ void onSupportedDeviceStatesChanged(int[] newDeviceStates);
+
+ /**
+ * Called to notify the listener of a change in current device state. Required to be called
+ * once on successful registration of the listener and then once on every subsequent change
+ * in device state. Value must have been included in the set of supported device states
+ * provided in the most recent call to {@link #onSupportedDeviceStatesChanged(int[])}.
+ *
+ * @param state the new device state.
+ *
+ * @throws IllegalArgumentException if the state is less than 0.
+ */
+ void onStateChanged(int state);
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
new file mode 100644
index 000000000000..ae79fc0e2c2d
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -0,0 +1,3 @@
+ogunwale@google.com
+akulian@google.com
+darryljohnson@google.com
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index eb2c7e6105ae..93cada7adca3 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -208,7 +208,6 @@ class AutomaticBrightnessController {
private PackageManager mPackageManager;
private Context mContext;
- private DisplayDeviceConfig mDisplayDeviceConfig;
private final Injector mInjector;
AutomaticBrightnessController(Callbacks callbacks, Looper looper,
@@ -217,14 +216,13 @@ class AutomaticBrightnessController {
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig
- displayDeviceConfig) {
+ HysteresisLevels screenBrightnessThresholds, Context context) {
this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
- ambientBrightnessThresholds, screenBrightnessThresholds, context,
- displayDeviceConfig);
+ ambientBrightnessThresholds, screenBrightnessThresholds, context
+ );
}
@VisibleForTesting
@@ -234,8 +232,7 @@ class AutomaticBrightnessController {
float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
- HysteresisLevels screenBrightnessThresholds, Context context, DisplayDeviceConfig
- displayDeviceConfig) {
+ HysteresisLevels screenBrightnessThresholds, Context context) {
mInjector = injector;
mContext = context;
mCallbacks = callbacks;
@@ -257,7 +254,6 @@ class AutomaticBrightnessController {
mScreenBrightnessThresholds = screenBrightnessThresholds;
mShortTermModelValid = true;
mShortTermModelAnchor = -1;
- mDisplayDeviceConfig = displayDeviceConfig;
mHandler = new AutomaticBrightnessHandler(looper);
mAmbientLightRingBuffer =
new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
diff --git a/services/core/java/com/android/server/display/DisplayBlanker.java b/services/core/java/com/android/server/display/DisplayBlanker.java
index d294898da8c4..e2129ba13626 100644
--- a/services/core/java/com/android/server/display/DisplayBlanker.java
+++ b/services/core/java/com/android/server/display/DisplayBlanker.java
@@ -20,5 +20,5 @@ package com.android.server.display;
* Interface used to update the actual display state.
*/
public interface DisplayBlanker {
- void requestDisplayState(int state, float brightness);
+ void requestDisplayState(int displayId, int state, float brightness);
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 0d824334d873..23e5a43a359f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import android.os.Environment;
import android.util.Slog;
+import android.view.DisplayAddress;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.NitsMap;
@@ -31,6 +32,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.List;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -45,7 +47,11 @@ public class DisplayDeviceConfig {
private static final String ETC_DIR = "etc";
private static final String DISPLAY_CONFIG_DIR = "displayconfig";
- private static final String CONFIG_FILE_FORMAT = "display_%d.xml";
+ private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
+ private static final String PORT_SUFFIX_FORMAT = "port_%d";
+ private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
+ private static final String NO_SUFFIX_FORMAT = "%d";
+ private static final long STABLE_FLAG = 1L << 62;
private float[] mNits;
private float[] mBrightness;
@@ -55,17 +61,43 @@ public class DisplayDeviceConfig {
/**
* Creates an instance for the specified display.
- *
+ * Tries to find a file with identifier in the following priority order:
+ * <ol>
+ * <li>physicalDisplayId</li>
+ * <li>physicalDisplayId without a stable flag (old system)</li>
+ * <li>portId</li>
+ * </ol>
* @param physicalDisplayId The display ID for which to load the configuration.
* @return A configuration instance for the specified display.
*/
public static DisplayDeviceConfig create(long physicalDisplayId) {
- final DisplayDeviceConfig config = new DisplayDeviceConfig();
- final String filename = String.format(CONFIG_FILE_FORMAT, physicalDisplayId);
+ DisplayDeviceConfig config;
+
+ // Create config using filename from physical ID (including "stable" bit).
+ config = getConfigFromSuffix(STABLE_ID_SUFFIX_FORMAT, physicalDisplayId);
+ if (config != null) {
+ return config;
+ }
+
+ // Create config using filename from physical ID (excluding "stable" bit).
+ final long withoutStableFlag = physicalDisplayId & ~STABLE_FLAG;
+ config = getConfigFromSuffix(NO_SUFFIX_FORMAT, withoutStableFlag);
+ if (config != null) {
+ return config;
+ }
+
+ // Create config using filename from port ID.
+ final DisplayAddress.Physical physicalAddress =
+ DisplayAddress.fromPhysicalDisplayId(physicalDisplayId);
+ int port = physicalAddress.getPort();
+ config = getConfigFromSuffix(PORT_SUFFIX_FORMAT, port);
+ if (config != null) {
+ return config;
+ }
+
+ // None of these files exist.
+ return null;
- config.initFromFile(Environment.buildPath(
- Environment.getProductDirectory(), ETC_DIR, DISPLAY_CONFIG_DIR, filename));
- return config;
}
/**
@@ -86,6 +118,30 @@ public class DisplayDeviceConfig {
return mBrightness;
}
+ @Override
+ public String toString() {
+ String str = "DisplayDeviceConfig{"
+ + "mBrightness=" + Arrays.toString(mBrightness)
+ + ", mNits=" + Arrays.toString(mNits)
+ + "}";
+ return str;
+ }
+
+ private static DisplayDeviceConfig getConfigFromSuffix(String suffixFormat, long idNumber) {
+
+ final String suffix = String.format(suffixFormat, idNumber);
+ final String filename = String.format(CONFIG_FILE_FORMAT, suffix);
+ final File filePath = Environment.buildPath(
+ Environment.getProductDirectory(), ETC_DIR, DISPLAY_CONFIG_DIR, filename);
+
+ if (filePath.exists()) {
+ final DisplayDeviceConfig config = new DisplayDeviceConfig();
+ config.initFromFile(filePath);
+ return config;
+ }
+ return null;
+ }
+
private void initFromFile(File configFile) {
if (!configFile.exists()) {
// Display configuration files aren't required to exist.
@@ -121,13 +177,13 @@ public class DisplayDeviceConfig {
if (i > 0) {
if (nits[i] < nits[i - 1]) {
Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest "
- + " of configuration. Nits: " + nits[i] + " < " + nits[i - 1]);
+ + " of configuration. Nits: " + nits[i] + " < " + nits[i - 1]);
return;
}
if (backlight[i] < backlight[i - 1]) {
Slog.e(TAG, "screenBrightnessMap must be non-decreasing, ignoring rest "
- + " of configuration. Value: " + backlight[i] + " < "
+ + " of configuration. Value: " + backlight[i] + " < "
+ backlight[i - 1]);
return;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
new file mode 100644
index 000000000000..aa4db29e0d49
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -0,0 +1,191 @@
+/*
+ * 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.display;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.display.DisplayManagerService.SyncRoot;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Container for all the display devices present in the system. If an object wants to get events
+ * about all the DisplayDevices without needing to listen to all of the DisplayAdapters, they can
+ * listen and interact with the instance of this class.
+ * <p>
+ * The collection of {@link DisplayDevice}s and their usage is protected by the provided
+ * {@link DisplayManagerService.SyncRoot} lock object.
+ */
+class DisplayDeviceRepository implements DisplayAdapter.Listener {
+ private static final String TAG = "DisplayDeviceRepository";
+
+ public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
+ public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
+ public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
+
+ /**
+ * List of all currently connected display devices. Indexed by the displayId.
+ * TODO: multi-display - break the notion that this is indexed by displayId.
+ */
+ @GuardedBy("mSyncRoot")
+ private final List<DisplayDevice> mDisplayDevices = new ArrayList<>();
+
+ /** Listeners for {link DisplayDevice} events. */
+ @GuardedBy("mSyncRoot")
+ private final List<Listener> mListeners = new ArrayList<>();
+
+ /** Global lock object from {@link DisplayManagerService}. */
+ private final SyncRoot mSyncRoot;
+
+ private final PersistentDataStore mPersistentDataStore;
+
+ /**
+ * @param syncRoot The global lock for DisplayManager related objects.
+ * @param persistentDataStore Settings data store from {@link DisplayManagerService}.
+ */
+ DisplayDeviceRepository(@NonNull SyncRoot syncRoot,
+ @NonNull PersistentDataStore persistentDataStore) {
+ mSyncRoot = syncRoot;
+ mPersistentDataStore = persistentDataStore;
+ }
+
+ public void addListener(@NonNull Listener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+ switch (event) {
+ case DISPLAY_DEVICE_EVENT_ADDED:
+ handleDisplayDeviceAdded(device);
+ break;
+
+ case DISPLAY_DEVICE_EVENT_CHANGED:
+ handleDisplayDeviceChanged(device);
+ break;
+
+ case DISPLAY_DEVICE_EVENT_REMOVED:
+ handleDisplayDeviceRemoved(device);
+ break;
+ }
+ }
+
+ @Override
+ public void onTraversalRequested() {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).onTraversalRequested();
+ }
+ }
+
+ public boolean containsLocked(DisplayDevice d) {
+ return mDisplayDevices.contains(d);
+ }
+
+ public int sizeLocked() {
+ return mDisplayDevices.size();
+ }
+
+ public void forEachLocked(Consumer<DisplayDevice> consumer) {
+ final int count = mDisplayDevices.size();
+ for (int i = 0; i < count; i++) {
+ consumer.accept(mDisplayDevices.get(i));
+ }
+ }
+
+ private void handleDisplayDeviceAdded(DisplayDevice device) {
+ synchronized (mSyncRoot) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if (mDisplayDevices.contains(device)) {
+ Slog.w(TAG, "Attempted to add already added display device: " + info);
+ return;
+ }
+ Slog.i(TAG, "Display device added: " + info);
+ device.mDebugLastLoggedDeviceInfo = info;
+
+ mDisplayDevices.add(device);
+ sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
+ }
+ }
+
+ private void handleDisplayDeviceChanged(DisplayDevice device) {
+ synchronized (mSyncRoot) {
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if (!mDisplayDevices.contains(device)) {
+ Slog.w(TAG, "Attempted to change non-existent display device: " + info);
+ return;
+ }
+
+ int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
+ if (diff == DisplayDeviceInfo.DIFF_STATE) {
+ Slog.i(TAG, "Display device changed state: \"" + info.name
+ + "\", " + Display.stateToString(info.state));
+ } else if (diff != 0) {
+ Slog.i(TAG, "Display device changed: " + info);
+ }
+
+ if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
+ try {
+ mPersistentDataStore.setColorMode(device, info.colorMode);
+ } finally {
+ mPersistentDataStore.saveIfNeeded();
+ }
+ }
+ device.mDebugLastLoggedDeviceInfo = info;
+
+ device.applyPendingDisplayDeviceInfoChangesLocked();
+ sendEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
+ }
+ }
+
+ private void handleDisplayDeviceRemoved(DisplayDevice device) {
+ synchronized (mSyncRoot) {
+ DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ if (!mDisplayDevices.remove(device)) {
+ Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
+ return;
+ }
+
+ Slog.i(TAG, "Display device removed: " + info);
+ device.mDebugLastLoggedDeviceInfo = info;
+ sendEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+ }
+ }
+
+ private void sendEventLocked(DisplayDevice device, int event) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).onDisplayDeviceEventLocked(device, event);
+ }
+ }
+
+ /**
+ * Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}.
+ */
+ public interface Listener {
+ void onDisplayDeviceEventLocked(DisplayDevice device, int event);
+
+ // TODO: multi-display - Try to remove the need for requestTraversal...it feels like
+ // a shoe-horned method for a shoe-horned feature.
+ void onTraversalRequested();
+ };
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cd160a2cc731..f8715e373744 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -110,10 +110,10 @@ import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
/**
* Manages attached displays.
@@ -132,7 +132,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
* </p><p>
* Display adapters are only weakly coupled to the display manager service.
* Display adapters communicate changes in display device state to the display manager
- * service asynchronously via a {@link DisplayAdapter.Listener} registered
+ * service asynchronously via a {@link DisplayAdapter.Listener}, and through
+ * the {@link DisplayDeviceRepository.Listener}, which is ultimately registered
* by the display manager service. This separation of concerns is important for
* two main reasons. First, it neatly encapsulates the responsibilities of these
* two classes: display adapters handle individual display devices whereas
@@ -181,7 +182,6 @@ public final class DisplayManagerService extends SystemService {
private final Context mContext;
private final DisplayManagerHandler mHandler;
private final Handler mUiHandler;
- private final DisplayAdapterListener mDisplayAdapterListener;
private final DisplayModeDirector mDisplayModeDirector;
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
@@ -202,13 +202,6 @@ public final class DisplayManagerService extends SystemService {
// services should be started. This option may disable certain display adapters.
public boolean mOnlyCore;
- // True if the display manager service should pretend there is only one display
- // and only tell applications about the existence of the default logical display.
- // The display manager can still mirror content to secondary displays but applications
- // cannot present unique content on those displays.
- // Used for demonstration purposes only.
- private final boolean mSingleDisplayDemoMode;
-
// All callback records indexed by calling process id.
public final SparseArray<CallbackRecord> mCallbacks =
new SparseArray<CallbackRecord>();
@@ -216,15 +209,17 @@ public final class DisplayManagerService extends SystemService {
// List of all currently registered display adapters.
private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
- // List of all currently connected display devices.
- private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>();
+ /**
+ * Repository of all active {@link DisplayDevice}s.
+ */
+ private final DisplayDeviceRepository mDisplayDeviceRepo;
- // List of all logical displays indexed by logical display id.
- // Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
- private final SparseArray<LogicalDisplay> mLogicalDisplays =
- new SparseArray<LogicalDisplay>();
- private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
- private int mNextBuiltInDisplayId = 4096;
+ /**
+ * Contains all the {@link LogicalDisplay} instances and is responsible for mapping
+ * {@link DisplayDevice}s to {@link LogicalDisplay}s. DisplayManagerService listens to display
+ * event on this object.
+ */
+ private final LogicalDisplayMapper mLogicalDisplayMapper;
// List of all display transaction listeners.
private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
@@ -284,10 +279,6 @@ public final class DisplayManagerService extends SystemService {
// May be used outside of the lock but only on the handler thread.
private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
- // Temporary display info, used for comparing display configurations.
- private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
- private final DisplayInfo mTempNonOverrideDisplayInfo = new DisplayInfo();
-
// Temporary viewports, used when sending new viewport information to the
// input system. May be used outside of the lock but only on the handler thread.
private final ArrayList<DisplayViewport> mTempViewports = new ArrayList<>();
@@ -340,9 +331,10 @@ public final class DisplayManagerService extends SystemService {
mContext = context;
mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
mUiHandler = UiThread.getHandler();
- mDisplayAdapterListener = new DisplayAdapterListener();
+ mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
+ mLogicalDisplayMapper = new LogicalDisplayMapper(mDisplayDeviceRepo,
+ new LogicalDisplayListener(), mPersistentDataStore);
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
- mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
Resources resources = mContext.getResources();
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
@@ -404,13 +396,13 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
long timeout = SystemClock.uptimeMillis()
+ mInjector.getDefaultDisplayDelayTimeout();
- while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null ||
- mVirtualDisplayAdapter == null) {
+ while (mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY) == null
+ || mVirtualDisplayAdapter == null) {
long delay = timeout - SystemClock.uptimeMillis();
if (delay <= 0) {
throw new RuntimeException("Timeout waiting for default display "
+ "to be initialized. DefaultDisplay="
- + mLogicalDisplays.get(Display.DEFAULT_DISPLAY)
+ + mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY)
+ ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter);
}
if (DEBUG) {
@@ -460,7 +452,7 @@ public final class DisplayManagerService extends SystemService {
mSystemReady = true;
// Just in case the top inset changed before the system was ready. At this point, any
// relevant configuration should be in place.
- recordTopInsetLocked(mLogicalDisplays.get(Display.DEFAULT_DISPLAY));
+ recordTopInsetLocked(mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY));
updateSettingsLocked();
}
@@ -479,6 +471,11 @@ public final class DisplayManagerService extends SystemService {
return mHandler;
}
+ @VisibleForTesting
+ DisplayDeviceRepository getDisplayDeviceRepository() {
+ return mDisplayDeviceRepo;
+ }
+
private void loadStableDisplayValuesLocked() {
final Point size = mPersistentDataStore.getStableDisplaySize();
if (size.x > 0 && size.y > 0) {
@@ -520,13 +517,12 @@ public final class DisplayManagerService extends SystemService {
}
@VisibleForTesting
- void setDisplayInfoOverrideFromWindowManagerInternal(
- int displayId, DisplayInfo info) {
+ void setDisplayInfoOverrideFromWindowManagerInternal(int displayId, DisplayInfo info) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
- handleLogicalDisplayChanged(displayId, display);
+ handleLogicalDisplayChangedLocked(display);
scheduleTraversalLocked(false);
}
}
@@ -538,7 +534,7 @@ public final class DisplayManagerService extends SystemService {
*/
private void getNonOverrideDisplayInfoInternal(int displayId, DisplayInfo outInfo) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplays.get(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
display.getNonOverrideDisplayInfoLocked(outInfo);
}
@@ -637,7 +633,7 @@ public final class DisplayManagerService extends SystemService {
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
DisplayInfo info = display.getDisplayInfoLocked();
if (info.hasAccess(callingUid)
@@ -649,25 +645,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- private int[] getDisplayIdsInternal(int callingUid) {
- synchronized (mSyncRoot) {
- final int count = mLogicalDisplays.size();
- int[] displayIds = new int[count];
- int n = 0;
- for (int i = 0; i < count; i++) {
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
- DisplayInfo info = display.getDisplayInfoLocked();
- if (info.hasAccess(callingUid)) {
- displayIds[n++] = mLogicalDisplays.keyAt(i);
- }
- }
- if (n != count) {
- displayIds = Arrays.copyOfRange(displayIds, 0, n);
- }
- return displayIds;
- }
- }
-
private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid) {
synchronized (mSyncRoot) {
if (mCallbacks.get(callingPid) != null) {
@@ -802,7 +779,7 @@ public final class DisplayManagerService extends SystemService {
private void requestColorModeInternal(int displayId, int colorMode) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null &&
display.getRequestedColorModeLocked() != colorMode) {
display.setRequestedColorModeLocked(colorMode);
@@ -828,8 +805,18 @@ public final class DisplayManagerService extends SystemService {
return -1;
}
- handleDisplayDeviceAddedLocked(device);
- LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+ // DisplayDevice events are handled manually for Virtual Displays.
+ // TODO: multi-display Fix this so that generic add/remove events are not handled in a
+ // different code path for virtual displays. Currently this happens so that we can
+ // return a valid display ID synchronously upon successful Virtual Display creation.
+ // This code can run on any binder thread, while onDisplayDeviceAdded() callbacks are
+ // called on the DisplayThread (which we don't want to wait for?).
+ // One option would be to actually wait here on the binder thread
+ // to be notified when the virtual display is created (or failed).
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+ DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
if (display != null) {
return display.getDisplayIdLocked();
}
@@ -838,7 +825,8 @@ public final class DisplayManagerService extends SystemService {
Slog.w(TAG, "Rejecting request to create virtual display "
+ "because the logical display was not created.");
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());
- handleDisplayDeviceRemovedLocked(device);
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+ DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
}
return -1;
}
@@ -873,7 +861,9 @@ public final class DisplayManagerService extends SystemService {
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
if (device != null) {
- handleDisplayDeviceRemovedLocked(device);
+ // TODO: multi-display - handle virtual displays the same as other display adapters.
+ mDisplayDeviceRepo.onDisplayDeviceEvent(device,
+ DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
}
}
}
@@ -893,7 +883,7 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
// main display adapter
registerDisplayAdapterLocked(new LocalDisplayAdapter(
- mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+ mSyncRoot, mContext, mHandler, mDisplayDeviceRepo));
// Standalone VR devices rely on a virtual display as their primary display for
// 2D UI. We register virtual display adapter along side the main display adapter
@@ -901,7 +891,7 @@ public final class DisplayManagerService extends SystemService {
// early apps like SetupWizard/Launcher. In particular, SUW is displayed using
// the virtual display inside VR before any VR-specific apps even run.
mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,
- mHandler, mDisplayAdapterListener);
+ mHandler, mDisplayDeviceRepo);
if (mVirtualDisplayAdapter != null) {
registerDisplayAdapterLocked(mVirtualDisplayAdapter);
}
@@ -919,7 +909,7 @@ public final class DisplayManagerService extends SystemService {
private void registerOverlayDisplayAdapterLocked() {
registerDisplayAdapterLocked(new OverlayDisplayAdapter(
- mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+ mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));
}
private void registerWifiDisplayAdapterLocked() {
@@ -927,7 +917,7 @@ public final class DisplayManagerService extends SystemService {
com.android.internal.R.bool.config_enableWifiDisplay)
|| SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
mWifiDisplayAdapter = new WifiDisplayAdapter(
- mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
+ mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,
mPersistentDataStore);
registerDisplayAdapterLocked(mWifiDisplayAdapter);
}
@@ -949,95 +939,42 @@ public final class DisplayManagerService extends SystemService {
}
@VisibleForTesting
- void handleDisplayDeviceAdded(DisplayDevice device) {
+ void handleLogicalDisplayAdded(LogicalDisplay display) {
synchronized (mSyncRoot) {
- handleDisplayDeviceAddedLocked(device);
+ handleLogicalDisplayAddedLocked(display);
}
}
- private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
- DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if (mDisplayDevices.contains(device)) {
- Slog.w(TAG, "Attempted to add already added display device: " + info);
- return;
- }
- Slog.i(TAG, "Display device added: " + info);
- device.mDebugLastLoggedDeviceInfo = info;
-
- mDisplayDevices.add(device);
- LogicalDisplay display = addLogicalDisplayLocked(device);
- Runnable work = updateDisplayStateLocked(device);
- if (work != null) {
- work.run();
+ private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final int displayId = display.getDisplayIdLocked();
+ final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
+ configureColorModeLocked(display, device);
+ if (isDefault) {
+ recordStableDisplayStatsIfNeededLocked(display);
+ recordTopInsetLocked(display);
}
- scheduleTraversalLocked(false);
- }
- @VisibleForTesting
- void handleDisplayDeviceChanged(DisplayDevice device) {
- synchronized (mSyncRoot) {
- DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if (!mDisplayDevices.contains(device)) {
- Slog.w(TAG, "Attempted to change non-existent display device: " + info);
- return;
- }
-
- int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
- if (diff == DisplayDeviceInfo.DIFF_STATE) {
- Slog.i(TAG, "Display device changed state: \"" + info.name
- + "\", " + Display.stateToString(info.state));
- final Optional<Integer> viewportType = getViewportType(info);
- if (viewportType.isPresent()) {
- for (DisplayViewport d : mViewports) {
- if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) {
- // Update display view port power state
- d.isActive = Display.isActiveState(info.state);
- }
- }
- if (mInputManagerInternal != null) {
- mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
- }
- }
- } else if (diff != 0) {
- Slog.i(TAG, "Display device changed: " + info);
- }
- if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {
- try {
- mPersistentDataStore.setColorMode(device, info.colorMode);
- } finally {
- mPersistentDataStore.saveIfNeeded();
- }
- }
- device.mDebugLastLoggedDeviceInfo = info;
+ DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
- device.applyPendingDisplayDeviceInfoChangesLocked();
- if (updateLogicalDisplaysLocked()) {
- scheduleTraversalLocked(false);
- }
+ // Wake up waitForDefaultDisplay.
+ if (isDefault) {
+ mSyncRoot.notifyAll();
}
- }
- private void handleDisplayDeviceRemoved(DisplayDevice device) {
- synchronized (mSyncRoot) {
- handleDisplayDeviceRemovedLocked(device);
- }
- }
+ sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
- private void handleDisplayDeviceRemovedLocked(DisplayDevice device) {
- DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- if (!mDisplayDevices.remove(device)) {
- Slog.w(TAG, "Attempted to remove non-existent display device: " + info);
- return;
+ Runnable work = updateDisplayStateLocked(device);
+ if (work != null) {
+ work.run();
}
-
- Slog.i(TAG, "Display device removed: " + info);
- device.mDebugLastLoggedDeviceInfo = info;
-
- updateLogicalDisplaysLocked();
scheduleTraversalLocked(false);
}
- private void handleLogicalDisplayChanged(int displayId, @NonNull LogicalDisplay display) {
+ private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) {
+ updateViewportPowerStateLocked(display);
+
+ final int displayId = display.getDisplayIdLocked();
if (displayId == Display.DEFAULT_DISPLAY) {
recordTopInsetLocked(display);
}
@@ -1045,22 +982,23 @@ public final class DisplayManagerService extends SystemService {
// display info will trigger a cache invalidation inside of LogicalDisplay before we hit
// this point.
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ scheduleTraversalLocked(false);
}
- private void handleLogicalDisplayRemoved(int displayId) {
+ private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
+ final int displayId = display.getDisplayIdLocked();
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+ scheduleTraversalLocked(false);
}
private void applyGlobalDisplayStateLocked(List<Runnable> workQueue) {
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- DisplayDevice device = mDisplayDevices.get(i);
+ mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
Runnable runnable = updateDisplayStateLocked(device);
if (runnable != null) {
workQueue.add(runnable);
}
- }
+ });
}
private Runnable updateDisplayStateLocked(DisplayDevice device) {
@@ -1068,82 +1006,14 @@ public final class DisplayManagerService extends SystemService {
// by the display power controller (if known).
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+ // TODO - multi-display - The rules regarding what display state to apply to each
+ // display will depend on the configuration/mapping of logical displays.
return device.requestDisplayStateLocked(
mGlobalDisplayState, mGlobalDisplayBrightness);
}
return null;
}
- // Adds a new logical display based on the given display device.
- // Sends notifications if needed.
- private LogicalDisplay addLogicalDisplayLocked(DisplayDevice device) {
- DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
- boolean isDefault = (deviceInfo.flags
- & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
- if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
- Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
- isDefault = false;
- }
-
- if (!isDefault && mSingleDisplayDemoMode) {
- Slog.i(TAG, "Not creating a logical display for a secondary display "
- + " because single display demo mode is enabled: " + deviceInfo);
- return null;
- }
-
- final int displayId = assignDisplayIdLocked(isDefault, deviceInfo.address);
- final int layerStack = assignLayerStackLocked(displayId);
-
- LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
- display.updateLocked(mDisplayDevices);
- if (!display.isValidLocked()) {
- // This should never happen currently.
- Slog.w(TAG, "Ignoring display device because the logical display "
- + "created from it was not considered valid: " + deviceInfo);
- return null;
- }
-
- configureColorModeLocked(display, device);
- if (isDefault) {
- recordStableDisplayStatsIfNeededLocked(display);
- recordTopInsetLocked(display);
- }
-
- mLogicalDisplays.put(displayId, display);
- DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
-
- // Wake up waitForDefaultDisplay.
- if (isDefault) {
- mSyncRoot.notifyAll();
- }
-
- sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
- return display;
- }
-
- private int assignDisplayIdLocked(boolean isDefault) {
- return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
- }
-
- private int assignDisplayIdLocked(boolean isDefault, DisplayAddress address) {
- boolean isDisplayBuiltIn = false;
- if (address instanceof DisplayAddress.Physical) {
- isDisplayBuiltIn =
- (((DisplayAddress.Physical) address).getPort() < 0);
- }
- if (!isDefault && isDisplayBuiltIn) {
- return mNextBuiltInDisplayId++;
- }
-
- return assignDisplayIdLocked(isDefault);
- }
-
- private int assignLayerStackLocked(int displayId) {
- // Currently layer stacks and display ids are the same.
- // This need not be the case.
- return displayId;
- }
-
private void configureColorModeLocked(LogicalDisplay display, DisplayDevice device) {
if (display.getPrimaryDisplayDeviceLocked() == device) {
int colorMode = mPersistentDataStore.getColorMode(device);
@@ -1202,6 +1072,15 @@ public final class DisplayManagerService extends SystemService {
return mWideColorSpace.getId();
}
+ void setShouldAlwaysRespectAppRequestedModeInternal(boolean enabled) {
+ mDisplayModeDirector.setShouldAlwaysRespectAppRequestedMode(enabled);
+ }
+
+
+ boolean shouldAlwaysRespectAppRequestedModeInternal() {
+ return mDisplayModeDirector.shouldAlwaysRespectAppRequestedMode();
+ }
+
private void setBrightnessConfigurationForUserInternal(
@Nullable BrightnessConfiguration c, @UserIdInt int userId,
@Nullable String packageName) {
@@ -1251,51 +1130,16 @@ public final class DisplayManagerService extends SystemService {
}
}
- // Updates all existing logical displays given the current set of display devices.
- // Removes invalid logical displays.
- // Sends notifications if needed.
- private boolean updateLogicalDisplaysLocked() {
- boolean changed = false;
- for (int i = mLogicalDisplays.size(); i-- > 0; ) {
- final int displayId = mLogicalDisplays.keyAt(i);
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
-
- mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
- display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
- display.updateLocked(mDisplayDevices);
- if (!display.isValidLocked()) {
- mLogicalDisplays.removeAt(i);
- handleLogicalDisplayRemoved(displayId);
- changed = true;
- } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
- handleLogicalDisplayChanged(displayId, display);
- changed = true;
- } else {
- // While applications shouldn't know nor care about the non-overridden info, we
- // still need to let WindowManager know so it can update its own internal state for
- // things like display cutouts.
- display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
- if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
- handleLogicalDisplayChanged(displayId, display);
- changed = true;
- }
- }
- }
- return changed;
- }
-
private void performTraversalLocked(SurfaceControl.Transaction t) {
// Clear all viewports before configuring displays so that we can keep
// track of which ones we have configured.
clearViewportsLocked();
// Configure each display device.
- final int count = mDisplayDevices.size();
- for (int i = 0; i < count; i++) {
- DisplayDevice device = mDisplayDevices.get(i);
+ mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
configureDisplayLocked(t, device);
device.performTraversalLocked(t);
- }
+ });
// Tell the input system about these new viewports.
if (mInputManagerInternal != null) {
@@ -1307,7 +1151,7 @@ public final class DisplayManagerService extends SystemService {
float requestedRefreshRate, int requestedModeId, boolean preferMinimalPostProcessing,
boolean inTraversal) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display == null) {
return;
}
@@ -1349,7 +1193,7 @@ public final class DisplayManagerService extends SystemService {
private void setDisplayOffsetsInternal(int displayId, int x, int y) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display == null) {
return;
}
@@ -1367,7 +1211,7 @@ public final class DisplayManagerService extends SystemService {
private void setDisplayScalingDisabledInternal(int displayId, boolean disable) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplays.get(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display == null) {
return;
}
@@ -1403,7 +1247,7 @@ public final class DisplayManagerService extends SystemService {
@Nullable
private IBinder getDisplayToken(int displayId) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplays.get(displayId);
+ final LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
if (device != null) {
@@ -1421,7 +1265,7 @@ public final class DisplayManagerService extends SystemService {
if (token == null) {
return null;
}
- final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
+ final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getLocked(displayId);
if (logicalDisplay == null) {
return null;
}
@@ -1511,33 +1355,6 @@ public final class DisplayManagerService extends SystemService {
}
}
- private void onDesiredDisplayModeSpecsChangedInternal() {
- boolean changed = false;
- synchronized (mSyncRoot) {
- final int count = mLogicalDisplays.size();
- for (int i = 0; i < count; i++) {
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
- int displayId = mLogicalDisplays.keyAt(i);
- DisplayModeDirector.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
- mDisplayModeDirector.getDesiredDisplayModeSpecs(displayId);
- DisplayModeDirector.DesiredDisplayModeSpecs existingDesiredDisplayModeSpecs =
- display.getDesiredDisplayModeSpecsLocked();
- if (DEBUG) {
- Slog.i(TAG,
- "Comparing display specs: " + desiredDisplayModeSpecs
- + ", existing: " + existingDesiredDisplayModeSpecs);
- }
- if (!desiredDisplayModeSpecs.equals(existingDesiredDisplayModeSpecs)) {
- display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs);
- changed = true;
- }
- }
- if (changed) {
- scheduleTraversalLocked(false);
- }
- }
- }
-
private void clearViewportsLocked() {
mViewports.clear();
}
@@ -1565,15 +1382,15 @@ public final class DisplayManagerService extends SystemService {
// Find the logical display that the display device is showing.
// Certain displays only ever show their own content.
- LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(device);
if (!ownContent) {
if (display != null && !display.hasContentLocked()) {
// If the display does not have any content of its own, then
// automatically mirror the requested logical display contents if possible.
- display = mLogicalDisplays.get(device.getDisplayIdToMirrorLocked());
+ display = mLogicalDisplayMapper.getLocked(device.getDisplayIdToMirrorLocked());
}
if (display == null) {
- display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+ display = mLogicalDisplayMapper.getLocked(Display.DEFAULT_DISPLAY);
}
}
@@ -1637,15 +1454,21 @@ public final class DisplayManagerService extends SystemService {
viewport.isActive = Display.isActiveState(info.state);
}
- private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) {
- final int count = mLogicalDisplays.size();
- for (int i = 0; i < count; i++) {
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
- if (display.getPrimaryDisplayDeviceLocked() == device) {
- return display;
+ private void updateViewportPowerStateLocked(LogicalDisplay display) {
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+ final Optional<Integer> viewportType = getViewportType(info);
+ if (viewportType.isPresent()) {
+ for (DisplayViewport d : mViewports) {
+ if (d.type == viewportType.get() && info.uniqueId.equals(d.uniqueId)) {
+ // Update display view port power state
+ d.isActive = Display.isActiveState(info.state);
+ }
+ }
+ if (mInputManagerInternal != null) {
+ mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
}
}
- return null;
}
private void sendDisplayEventLocked(int displayId, int event) {
@@ -1717,10 +1540,8 @@ public final class DisplayManagerService extends SystemService {
pw.println(" mSafeMode=" + mSafeMode);
pw.println(" mPendingTraversal=" + mPendingTraversal);
pw.println(" mGlobalDisplayState=" + Display.stateToString(mGlobalDisplayState));
- pw.println(" mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
pw.println(" mViewports=" + mViewports);
pw.println(" mDefaultDisplayDefaultColorMode=" + mDefaultDisplayDefaultColorMode);
- pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
pw.println(" mStableDisplaySize=" + mStableDisplaySize);
pw.println(" mMinimumBrightnessCurve=" + mMinimumBrightnessCurve);
@@ -1736,21 +1557,14 @@ public final class DisplayManagerService extends SystemService {
}
pw.println();
- pw.println("Display Devices: size=" + mDisplayDevices.size());
- for (DisplayDevice device : mDisplayDevices) {
+ pw.println("Display Devices: size=" + mDisplayDeviceRepo.sizeLocked());
+ mDisplayDeviceRepo.forEachLocked(device -> {
pw.println(" " + device.getDisplayDeviceInfoLocked());
device.dumpLocked(ipw);
- }
+ });
- final int logicalDisplayCount = mLogicalDisplays.size();
pw.println();
- pw.println("Logical Displays: size=" + logicalDisplayCount);
- for (int i = 0; i < logicalDisplayCount; i++) {
- int displayId = mLogicalDisplays.keyAt(i);
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
- pw.println(" Display " + displayId + ":");
- display.dumpLocked(ipw);
- }
+ mLogicalDisplayMapper.dumpLocked(pw);
pw.println();
mDisplayModeDirector.dump(pw);
@@ -1810,7 +1624,7 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
DisplayDeviceInfo getDisplayDeviceInfoInternal(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
return displayDevice.getDisplayDeviceInfoLocked();
@@ -1822,7 +1636,7 @@ public final class DisplayManagerService extends SystemService {
@VisibleForTesting
int getDisplayIdToMirrorInternal(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
return displayDevice.getDisplayIdToMirrorLocked();
@@ -1889,20 +1703,20 @@ public final class DisplayManagerService extends SystemService {
}
}
- private final class DisplayAdapterListener implements DisplayAdapter.Listener {
+ private final class LogicalDisplayListener implements LogicalDisplayMapper.Listener {
@Override
- public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+ public void onLogicalDisplayEventLocked(LogicalDisplay display, int event) {
switch (event) {
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
- handleDisplayDeviceAdded(device);
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED:
+ handleLogicalDisplayAddedLocked(display);
break;
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
- handleDisplayDeviceChanged(device);
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED:
+ handleLogicalDisplayChangedLocked(display);
break;
- case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
- handleDisplayDeviceRemoved(device);
+ case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED:
+ handleLogicalDisplayRemovedLocked(display);
break;
}
}
@@ -1974,7 +1788,9 @@ public final class DisplayManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- return getDisplayIdsInternal(callingUid);
+ synchronized (mSyncRoot) {
+ return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
+ }
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2435,7 +2251,8 @@ public final class DisplayManagerService extends SystemService {
@Override // Binder call
public boolean isMinimalPostProcessingRequested(int displayId) {
synchronized (mSyncRoot) {
- return mLogicalDisplays.get(displayId).getRequestedMinimalPostProcessingLocked();
+ return mLogicalDisplayMapper.getLocked(displayId)
+ .getRequestedMinimalPostProcessingLocked();
}
}
@@ -2497,6 +2314,32 @@ public final class DisplayManagerService extends SystemService {
}
}
+ @Override // Binder call
+ public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+ "Permission required to override display mode requests.");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setShouldAlwaysRespectAppRequestedModeInternal(enabled);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public boolean shouldAlwaysRespectAppRequestedMode() {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+ "Permission required to override display mode requests.");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return shouldAlwaysRespectAppRequestedModeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean validatePackageName(int uid, String packageName) {
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
@@ -2559,7 +2402,7 @@ public final class DisplayManagerService extends SystemService {
synchronized (mSyncRoot) {
DisplayBlanker blanker = new DisplayBlanker() {
@Override
- public void requestDisplayState(int state, float brightness) {
+ public void requestDisplayState(int displayId, int state, float brightness) {
// The order of operations is important for legacy reasons.
if (state == Display.STATE_OFF) {
requestGlobalDisplayStateInternal(state, brightness);
@@ -2574,7 +2417,7 @@ public final class DisplayManagerService extends SystemService {
};
mDisplayPowerController = new DisplayPowerController(
mContext, callbacks, handler, sensorManager, blanker,
- mDisplayDevices.get(Display.DEFAULT_DISPLAY));
+ Display.DEFAULT_DISPLAY);
mSensorManager = sensorManager;
}
@@ -2614,7 +2457,7 @@ public final class DisplayManagerService extends SystemService {
@Override
public Point getDisplayPosition(int displayId) {
synchronized (mSyncRoot) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
+ LogicalDisplay display = mLogicalDisplayMapper.getLocked(displayId);
if (display != null) {
return display.getDisplayPosition();
}
@@ -2688,9 +2531,7 @@ public final class DisplayManagerService extends SystemService {
@Override
public void onOverlayChanged() {
synchronized (mSyncRoot) {
- for (int i = 0; i < mDisplayDevices.size(); i++) {
- mDisplayDevices.get(i).onOverlayChangedLocked();
- }
+ mDisplayDeviceRepo.forEachLocked(DisplayDevice::onOverlayChangedLocked);
}
}
@@ -2721,8 +2562,36 @@ public final class DisplayManagerService extends SystemService {
class DesiredDisplayModeSpecsObserver
implements DisplayModeDirector.DesiredDisplayModeSpecsListener {
+
+ private final Consumer<LogicalDisplay> mSpecsChangedConsumer = display -> {
+ int displayId = display.getDisplayIdLocked();
+ DisplayModeDirector.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
+ mDisplayModeDirector.getDesiredDisplayModeSpecs(displayId);
+ DisplayModeDirector.DesiredDisplayModeSpecs existingDesiredDisplayModeSpecs =
+ display.getDesiredDisplayModeSpecsLocked();
+ if (DEBUG) {
+ Slog.i(TAG,
+ "Comparing display specs: " + desiredDisplayModeSpecs
+ + ", existing: " + existingDesiredDisplayModeSpecs);
+ }
+ if (!desiredDisplayModeSpecs.equals(existingDesiredDisplayModeSpecs)) {
+ display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs);
+ mChanged = true;
+ }
+ };
+
+ @GuardedBy("mSyncRoot")
+ private boolean mChanged = false;
+
public void onDesiredDisplayModeSpecsChanged() {
- onDesiredDisplayModeSpecsChangedInternal();
+ synchronized (mSyncRoot) {
+ mChanged = false;
+ mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer);
+ if (mChanged) {
+ scheduleTraversalLocked(false);
+ mChanged = false;
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index b1c91a690d7f..4f5a0faee8dd 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -56,8 +56,8 @@ import java.util.List;
import java.util.Objects;
/**
- * The DisplayModeDirector is responsible for determining what modes are allowed to be
- * automatically picked by the system based on system-wide and display-specific configuration.
+ * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
+ * picked by the system based on system-wide and display-specific configuration.
*/
public class DisplayModeDirector {
private static final String TAG = "DisplayModeDirector";
@@ -97,17 +97,20 @@ public class DisplayModeDirector {
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
private DesiredDisplayModeSpecsListener mDesiredDisplayModeSpecsListener;
+ private boolean mAlwaysRespectAppRequest;
+
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
mContext = context;
mHandler = new DisplayModeDirectorHandler(handler.getLooper());
mVotesByDisplay = new SparseArray<>();
mSupportedModesByDisplay = new SparseArray<>();
- mDefaultModeByDisplay = new SparseArray<>();
+ mDefaultModeByDisplay = new SparseArray<>();
mAppRequestObserver = new AppRequestObserver();
mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
mBrightnessObserver = new BrightnessObserver(context, handler);
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+ mAlwaysRespectAppRequest = false;
}
/**
@@ -127,7 +130,6 @@ public class DisplayModeDirector {
// notify them to pick up our newly initialized state.
notifyDesiredDisplayModeSpecsChangedLocked();
}
-
}
@NonNull
@@ -173,9 +175,14 @@ public class DisplayModeDirector {
// VoteSummary is returned as an output param to cut down a bit on the number of temporary
// objects.
private void summarizeVotes(
- SparseArray<Vote> votes, int lowestConsideredPriority, /*out*/ VoteSummary summary) {
+ SparseArray<Vote> votes,
+ int lowestConsideredPriority,
+ int highestConsideredPriority,
+ /*out*/ VoteSummary summary) {
summary.reset();
- for (int priority = Vote.MAX_PRIORITY; priority >= lowestConsideredPriority; priority--) {
+ for (int priority = highestConsideredPriority;
+ priority >= lowestConsideredPriority;
+ priority--) {
Vote vote = votes.get(priority);
if (vote == null) {
continue;
@@ -217,8 +224,16 @@ public class DisplayModeDirector {
int[] availableModes = new int[]{defaultMode.getModeId()};
VoteSummary primarySummary = new VoteSummary();
int lowestConsideredPriority = Vote.MIN_PRIORITY;
- while (lowestConsideredPriority <= Vote.MAX_PRIORITY) {
- summarizeVotes(votes, lowestConsideredPriority, primarySummary);
+ int highestConsideredPriority = Vote.MAX_PRIORITY;
+
+ if (mAlwaysRespectAppRequest) {
+ lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_REFRESH_RATE;
+ highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
+ }
+
+ while (lowestConsideredPriority <= highestConsideredPriority) {
+ summarizeVotes(
+ votes, lowestConsideredPriority, highestConsideredPriority, primarySummary);
// If we don't have anything specifying the width / height of the display, just use
// the default width and height. We don't want these switching out from underneath
@@ -261,7 +276,10 @@ public class DisplayModeDirector {
VoteSummary appRequestSummary = new VoteSummary();
summarizeVotes(
- votes, Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, appRequestSummary);
+ votes,
+ Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF,
+ Vote.MAX_PRIORITY,
+ appRequestSummary);
appRequestSummary.minRefreshRate =
Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate);
appRequestSummary.maxRefreshRate =
@@ -338,8 +356,7 @@ public class DisplayModeDirector {
}
/**
- * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate
- * ranges.
+ * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges.
*/
public void setDesiredDisplayModeSpecsListener(
@Nullable DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener) {
@@ -349,6 +366,26 @@ public class DisplayModeDirector {
}
/**
+ * When enabled the app requested display mode is always selected and all
+ * other votes will be ignored. This is used for testing purposes.
+ */
+ public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
+ synchronized (mLock) {
+ mAlwaysRespectAppRequest = enabled;
+ }
+ }
+
+ /**
+ * Returns whether we are running in a mode which always selects the app requested display mode
+ * and ignores user settings and policies for low brightness, low battery etc.
+ */
+ public boolean shouldAlwaysRespectAppRequestedMode() {
+ synchronized (mLock) {
+ return mAlwaysRespectAppRequest;
+ }
+ }
+
+ /**
* Print the object's state and debug information into the given stream.
*
* @param pw The stream to dump information to.
@@ -380,6 +417,7 @@ public class DisplayModeDirector {
pw.println(" " + Vote.priorityToString(p) + " -> " + vote);
}
}
+ pw.println(" mAlwaysRespectAppRequest: " + mAlwaysRespectAppRequest);
mSettingsObserver.dumpLocked(pw);
mAppRequestObserver.dumpLocked(pw);
mBrightnessObserver.dumpLocked(pw);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58ef9d11ef97..0211876c864d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -163,8 +163,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// The display blanker.
private final DisplayBlanker mBlanker;
- // The display device.
- private final DisplayDevice mDisplayDevice;
+ // The ID of the LogicalDisplay tied to this DisplayPowerController.
+ private final int mDisplayId;
// Tracker for brightness changes.
private final BrightnessTracker mBrightnessTracker;
@@ -406,7 +406,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
*/
public DisplayPowerController(Context context,
DisplayPowerCallbacks callbacks, Handler handler,
- SensorManager sensorManager, DisplayBlanker blanker, DisplayDevice displayDevice) {
+ SensorManager sensorManager, DisplayBlanker blanker, int displayId) {
mHandler = new DisplayControllerHandler(handler.getLooper());
mBrightnessTracker = new BrightnessTracker(context, null);
mSettingsObserver = new SettingsObserver(mHandler);
@@ -417,10 +417,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mBlanker = blanker;
mContext = context;
mBrightnessSynchronizer = new BrightnessSynchronizer(context);
- mDisplayDevice = displayDevice;
+ mDisplayId = displayId;
PowerManager pm = context.getSystemService(PowerManager.class);
- DisplayDeviceConfig displayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig();
final Resources resources = context.getResources();
@@ -515,7 +514,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
- screenBrightnessThresholds, context, displayDeviceConfig);
+ screenBrightnessThresholds, context);
} else {
mUseSoftwareAutoBrightnessConfig = false;
}
@@ -684,7 +683,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Initialize the power state object for the default display.
// In the future, we might manage multiple displays independently.
mPowerState = new DisplayPowerState(mBlanker,
- mColorFadeEnabled ? new ColorFade(Display.DEFAULT_DISPLAY) : null);
+ mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId);
if (mColorFadeEnabled) {
mColorFadeOnAnimator = ObjectAnimator.ofFloat(
@@ -1153,7 +1152,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (ready && state != Display.STATE_OFF
&& mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
- mWindowManagerPolicy.screenTurnedOn();
+ mWindowManagerPolicy.screenTurnedOn(mDisplayId);
}
// Grab a wake lock if we have unfinished business.
@@ -1277,7 +1276,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
blockScreenOff();
- mWindowManagerPolicy.screenTurningOff(mPendingScreenOffUnblocker);
+ mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
unblockScreenOff();
} else if (mPendingScreenOffUnblocker != null) {
// Abort doing the state change until screen off is unblocked.
@@ -1309,14 +1308,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& !mScreenOffBecauseOfProximity) {
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
unblockScreenOn();
- mWindowManagerPolicy.screenTurnedOff();
+ mWindowManagerPolicy.screenTurnedOff(mDisplayId);
} else if (!isOff
&& mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
// We told policy already that screen was turning off, but now we changed our minds.
// Complete the full state transition on -> turningOff -> off.
unblockScreenOff();
- mWindowManagerPolicy.screenTurnedOff();
+ mWindowManagerPolicy.screenTurnedOff(mDisplayId);
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
}
if (!isOff && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF) {
@@ -1326,7 +1325,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
} else {
unblockScreenOn();
}
- mWindowManagerPolicy.screenTurningOn(mPendingScreenOnUnblocker);
+ mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
}
// Return true if the screen isn't blocked.
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 4b6430d5197c..54f30a954c33 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -58,6 +58,7 @@ final class DisplayPowerState {
private final DisplayBlanker mBlanker;
private final ColorFade mColorFade;
private final PhotonicModulator mPhotonicModulator;
+ private final int mDisplayId;
private int mScreenState;
private float mScreenBrightness;
@@ -71,13 +72,14 @@ final class DisplayPowerState {
private Runnable mCleanListener;
- public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade) {
+ public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) {
mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
mColorFade = colorFade;
mPhotonicModulator = new PhotonicModulator();
mPhotonicModulator.start();
+ mDisplayId = displayId;
// At boot time, we know that the screen is on and the electron beam
// animation is not playing. We don't know the screen's brightness though,
@@ -434,10 +436,10 @@ final class DisplayPowerState {
// Apply pending change.
if (DEBUG) {
- Slog.d(TAG, "Updating screen state: state="
+ Slog.d(TAG, "Updating screen state: id=" + mDisplayId + ", state="
+ Display.stateToString(state) + ", backlight=" + brightnessState);
}
- mBlanker.requestDisplayState(state, brightnessState);
+ mBlanker.requestDisplayState(mDisplayId, state, brightnessState);
}
}
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 24bfc9371bb1..540d58052504 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -391,11 +391,11 @@ final class LocalDisplayAdapter extends DisplayAdapter {
Spline sysToNits = null;
// Load the mapping from nits to HAL brightness range (display-device-config.xml)
- DisplayDeviceConfig config = DisplayDeviceConfig.create(mPhysicalDisplayId);
- mDisplayDeviceConfig = config;
- if (config == null) {
+ mDisplayDeviceConfig = DisplayDeviceConfig.create(mPhysicalDisplayId);
+ if (mDisplayDeviceConfig == null) {
return;
}
+
final float[] halNits = mDisplayDeviceConfig.getNits();
final float[] halBrightness = mDisplayDeviceConfig.getBrightness();
if (halNits == null || halBrightness == null) {
@@ -964,7 +964,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
for (int i = 0; i < mSupportedModes.size(); i++) {
pw.println(" " + mSupportedModes.valueAt(i));
}
- pw.print("mSupportedColorModes=" + mSupportedColorModes.toString());
+ pw.println("mSupportedColorModes=" + mSupportedColorModes.toString());
+ pw.print("mDisplayDeviceConfig=" + mDisplayDeviceConfig);
}
private int findDisplayConfigIdLocked(int modeId, int configGroup) {
@@ -990,6 +991,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
private int findMatchingModeIdLocked(int configId) {
+ if (configId < 0 || configId >= mDisplayConfigs.length) {
+ Slog.e(TAG, "Invalid display config index " + configId);
+ return NO_DISPLAY_MODE_ID;
+ }
SurfaceControl.DisplayConfig config = mDisplayConfigs[configId];
for (int i = 0; i < mSupportedModes.size(); i++) {
DisplayModeRecord record = mSupportedModes.valueAt(i);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8556f084a072..bf8b891cffb8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -28,7 +28,6 @@ import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.List;
import java.util.Objects;
/**
@@ -220,16 +219,16 @@ final class LogicalDisplay {
* The logical display might become invalid if it is attached to a display device
* that no longer exists.
*
- * @param devices The list of all connected display devices.
+ * @param deviceRepo Repository of active {@link DisplayDevice}s.
*/
- public void updateLocked(List<DisplayDevice> devices) {
+ public void updateLocked(DisplayDeviceRepository deviceRepo) {
// Nothing to update if already invalid.
if (mPrimaryDisplayDevice == null) {
return;
}
// Check whether logical display has become invalid.
- if (!devices.contains(mPrimaryDisplayDevice)) {
+ if (!deviceRepo.containsLocked(mPrimaryDisplayDevice)) {
mPrimaryDisplayDevice = null;
return;
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
new file mode 100644
index 000000000000..03b4d0163cc8
--- /dev/null
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -0,0 +1,258 @@
+/*
+ * 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.display;
+
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.DisplayAddress;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * Responsible for creating {@link LogicalDisplay}s and associating them to the
+ * {@link DisplayDevice} objects supplied through {@link DisplayAdapter.Listener}.
+ *
+ * For devices with a single internal display, the mapping is done once and left
+ * alone. For devices with multiple built-in displays, such as foldable devices,
+ * {@link LogicalDisplay}s can be remapped to different {@link DisplayDevice}s.
+ */
+class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
+ private static final String TAG = "LogicalDisplayMapper";
+
+ public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1;
+ public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
+ public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
+
+ /**
+ * Temporary display info, used for comparing display configurations.
+ */
+ private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
+ private final DisplayInfo mTempNonOverrideDisplayInfo = new DisplayInfo();
+
+ /**
+ * True if the display mapper service should pretend there is only one display
+ * and only tell applications about the existence of the default logical display.
+ * The display manager can still mirror content to secondary displays but applications
+ * cannot present unique content on those displays.
+ * Used for demonstration purposes only.
+ */
+ private final boolean mSingleDisplayDemoMode;
+
+ /**
+ * List of all logical displays indexed by logical display id.
+ * Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
+ * TODO: multi-display - Move the aforementioned comment?
+ */
+ private final SparseArray<LogicalDisplay> mLogicalDisplays =
+ new SparseArray<LogicalDisplay>();
+ private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+ private int mNextBuiltInDisplayId = 4096;
+
+ private final DisplayDeviceRepository mDisplayDeviceRepo;
+ private final PersistentDataStore mPersistentDataStore;
+ private final Listener mListener;
+
+ LogicalDisplayMapper(DisplayDeviceRepository repo, Listener listener,
+ PersistentDataStore persistentDataStore) {
+ mDisplayDeviceRepo = repo;
+ mPersistentDataStore = persistentDataStore;
+ mListener = listener;
+ mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
+ mDisplayDeviceRepo.addListener(this);
+ }
+
+ @Override
+ public void onDisplayDeviceEventLocked(DisplayDevice device, int event) {
+ switch (event) {
+ case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED:
+ handleDisplayDeviceAddedLocked(device);
+ break;
+
+ case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED:
+ updateLogicalDisplaysLocked();
+ break;
+
+ case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED:
+ updateLogicalDisplaysLocked();
+ break;
+ }
+ }
+
+ @Override
+ public void onTraversalRequested() {
+ mListener.onTraversalRequested();
+ }
+
+ public LogicalDisplay getLocked(int displayId) {
+ return mLogicalDisplays.get(displayId);
+ }
+
+ public LogicalDisplay getLocked(DisplayDevice device) {
+ final int count = mLogicalDisplays.size();
+ for (int i = 0; i < count; i++) {
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ if (display.getPrimaryDisplayDeviceLocked() == device) {
+ return display;
+ }
+ }
+ return null;
+ }
+
+ public int[] getDisplayIdsLocked(int callingUid) {
+ final int count = mLogicalDisplays.size();
+ int[] displayIds = new int[count];
+ int n = 0;
+ for (int i = 0; i < count; i++) {
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ DisplayInfo info = display.getDisplayInfoLocked();
+ if (info.hasAccess(callingUid)) {
+ displayIds[n++] = mLogicalDisplays.keyAt(i);
+ }
+ }
+ if (n != count) {
+ displayIds = Arrays.copyOfRange(displayIds, 0, n);
+ }
+ return displayIds;
+ }
+
+ public void forEachLocked(Consumer<LogicalDisplay> consumer) {
+ final int count = mLogicalDisplays.size();
+ for (int i = 0; i < count; i++) {
+ consumer.accept(mLogicalDisplays.valueAt(i));
+ }
+ }
+
+ public void dumpLocked(PrintWriter pw) {
+ pw.println("LogicalDisplayMapper:");
+ pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
+ pw.println(" mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+
+ final int logicalDisplayCount = mLogicalDisplays.size();
+ pw.println();
+ pw.println(" Logical Displays: size=" + logicalDisplayCount);
+
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
+
+ for (int i = 0; i < logicalDisplayCount; i++) {
+ int displayId = mLogicalDisplays.keyAt(i);
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ pw.println(" Display " + displayId + ":");
+ display.dumpLocked(ipw);
+ }
+ }
+
+ private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
+ DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
+ boolean isDefault = (deviceInfo.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+ if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
+ Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
+ isDefault = false;
+ }
+
+ if (!isDefault && mSingleDisplayDemoMode) {
+ Slog.i(TAG, "Not creating a logical display for a secondary display "
+ + " because single display demo mode is enabled: " + deviceInfo);
+ return;
+ }
+
+ final int displayId = assignDisplayIdLocked(isDefault, deviceInfo.address);
+ final int layerStack = assignLayerStackLocked(displayId);
+
+ LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+ display.updateLocked(mDisplayDeviceRepo);
+ if (!display.isValidLocked()) {
+ // This should never happen currently.
+ Slog.w(TAG, "Ignoring display device because the logical display "
+ + "created from it was not considered valid: " + deviceInfo);
+ return;
+ }
+
+ mLogicalDisplays.put(displayId, display);
+
+ mListener.onLogicalDisplayEventLocked(display,
+ LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
+ }
+
+ /**
+ * Updates all existing logical displays given the current set of display devices.
+ * Removes invalid logical displays. Sends notifications if needed.
+ */
+ private void updateLogicalDisplaysLocked() {
+ for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
+ final int displayId = mLogicalDisplays.keyAt(i);
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+
+ mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
+ display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
+ display.updateLocked(mDisplayDeviceRepo);
+ if (!display.isValidLocked()) {
+ mLogicalDisplays.removeAt(i);
+
+ mListener.onLogicalDisplayEventLocked(display,
+ LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
+ } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
+ mListener.onLogicalDisplayEventLocked(display,
+ LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
+ } else {
+ // While applications shouldn't know nor care about the non-overridden info, we
+ // still need to let WindowManager know so it can update its own internal state for
+ // things like display cutouts.
+ display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
+ if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
+ mListener.onLogicalDisplayEventLocked(display,
+ LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
+ }
+ }
+ }
+ }
+
+ private int assignDisplayIdLocked(boolean isDefault) {
+ return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
+ }
+
+ private int assignDisplayIdLocked(boolean isDefault, DisplayAddress address) {
+ boolean isDisplayBuiltIn = false;
+ if (address instanceof DisplayAddress.Physical) {
+ isDisplayBuiltIn =
+ (((DisplayAddress.Physical) address).getPort() < 0);
+ }
+ if (!isDefault && isDisplayBuiltIn) {
+ return mNextBuiltInDisplayId++;
+ }
+
+ return assignDisplayIdLocked(isDefault);
+ }
+
+ private int assignLayerStackLocked(int displayId) {
+ // Currently layer stacks and display ids are the same.
+ // This need not be the case.
+ return displayId;
+ }
+
+ public interface Listener {
+ void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
+ void onTraversalRequested();
+ }
+}
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 9aec43b6eaed..09b0b3a8dee2 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -216,7 +216,7 @@ final class PersistentDataStore {
}
public boolean forgetWifiDisplay(String deviceAddress) {
- loadIfNeeded();
+ loadIfNeeded();
int index = findRememberedWifiDisplay(deviceAddress);
if (index >= 0) {
mRememberedWifiDisplays.remove(index);
@@ -259,17 +259,17 @@ final class PersistentDataStore {
return false;
}
- public Point getStableDisplaySize() {
- loadIfNeeded();
- return mStableDeviceValues.getDisplaySize();
- }
-
- public void setStableDisplaySize(Point size) {
- loadIfNeeded();
- if (mStableDeviceValues.setDisplaySize(size)) {
- setDirty();
- }
- }
+ public Point getStableDisplaySize() {
+ loadIfNeeded();
+ return mStableDeviceValues.getDisplaySize();
+ }
+
+ public void setStableDisplaySize(Point size) {
+ loadIfNeeded();
+ if (mStableDeviceValues.setDisplaySize(size)) {
+ setDirty();
+ }
+ }
public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial,
@Nullable String packageName) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
new file mode 100644
index 000000000000..2878a94fb860
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -0,0 +1,192 @@
+/*
+ * 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.hdmi;
+
+import android.stats.hdmi.HdmiStatsEnums;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+class HdmiCecAtomWriter {
+
+ private static final int FEATURE_ABORT_OPCODE_UNKNOWN = 0x100;
+ private static final int ERROR_CODE_UNKNOWN = -1;
+
+ /**
+ * Writes a HdmiCecMessageReported atom representing an HDMI CEC message.
+ * Should only be directly used for sent messages; for received messages,
+ * use the overloaded version with the errorCode argument omitted.
+ *
+ * @param message The HDMI CEC message
+ * @param direction Whether the message is incoming, outgoing, or neither
+ * @param errorCode The error code from the final attempt to send the message
+ */
+ public void messageReported(HdmiCecMessage message, int direction, int errorCode) {
+ MessageReportedGenericArgs genericArgs = createMessageReportedGenericArgs(
+ message, direction, errorCode);
+ MessageReportedSpecialArgs specialArgs = createMessageReportedSpecialArgs(message);
+ messageReportedBase(genericArgs, specialArgs);
+ }
+
+ /**
+ * Version of messageReported for received messages, where no error code is present.
+ *
+ * @param message The HDMI CEC message
+ * @param direction Whether the message is incoming, outgoing, or neither
+ */
+ public void messageReported(HdmiCecMessage message, int direction) {
+ messageReported(message, direction, ERROR_CODE_UNKNOWN);
+ }
+
+ /**
+ * Constructs the generic arguments for logging a HDMI CEC message.
+ *
+ * @param message The HDMI CEC message
+ * @param direction Whether the message is incoming, outgoing, or neither
+ * @param errorCode The error code of the message if it's outgoing;
+ * otherwise, ERROR_CODE_UNKNOWN
+ */
+ private MessageReportedGenericArgs createMessageReportedGenericArgs(
+ HdmiCecMessage message, int direction, int errorCode) {
+ int sendMessageResult = errorCode == ERROR_CODE_UNKNOWN
+ ? HdmiStatsEnums.SEND_MESSAGE_RESULT_UNKNOWN
+ : errorCode + 10;
+ return new MessageReportedGenericArgs(direction, message.getSource(),
+ message.getDestination(), message.getOpcode(), sendMessageResult);
+ }
+
+ /**
+ * Constructs the special arguments for logging an HDMI CEC message.
+ *
+ * @param message The HDMI CEC message to log
+ * @return An object containing the special arguments for the message
+ */
+ private MessageReportedSpecialArgs createMessageReportedSpecialArgs(HdmiCecMessage message) {
+ // Special arguments depend on message opcode
+ switch (message.getOpcode()) {
+ case Constants.MESSAGE_USER_CONTROL_PRESSED:
+ return createUserControlPressedSpecialArgs(message);
+ case Constants.MESSAGE_FEATURE_ABORT:
+ return createFeatureAbortSpecialArgs(message);
+ default:
+ return new MessageReportedSpecialArgs();
+ }
+ }
+
+ /**
+ * Constructs the special arguments for a <User Control Pressed> message.
+ *
+ * @param message The HDMI CEC message to log
+ */
+ private MessageReportedSpecialArgs createUserControlPressedSpecialArgs(
+ HdmiCecMessage message) {
+ MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
+
+ int keycode = message.getParams()[0];
+ if (keycode >= 0x1E && keycode <= 0x29) {
+ specialArgs.mUserControlPressedCommand = HdmiStatsEnums.NUMBER;
+ } else {
+ specialArgs.mUserControlPressedCommand = keycode + 0x100;
+ }
+
+ return specialArgs;
+ }
+
+ /**
+ * Constructs method for constructing the special arguments for a <Feature Abort> message.
+ *
+ * @param message The HDMI CEC message to log
+ */
+ private MessageReportedSpecialArgs createFeatureAbortSpecialArgs(HdmiCecMessage message) {
+ MessageReportedSpecialArgs specialArgs = new MessageReportedSpecialArgs();
+
+ specialArgs.mFeatureAbortOpcode = message.getParams()[0] & 0xFF; // Unsigned byte
+ specialArgs.mFeatureAbortReason = message.getParams()[1] + 10;
+
+ return specialArgs;
+ }
+
+ /**
+ * Writes a HdmiCecMessageReported atom.
+ *
+ * @param genericArgs Generic arguments; shared by all HdmiCecMessageReported atoms
+ * @param specialArgs Special arguments; depends on the opcode of the message
+ */
+ private void messageReportedBase(MessageReportedGenericArgs genericArgs,
+ MessageReportedSpecialArgs specialArgs) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED,
+ 0, // Placeholder field
+ genericArgs.mDirection,
+ genericArgs.mInitiatorLogicalAddress,
+ genericArgs.mDestinationLogicalAddress,
+ genericArgs.mOpcode,
+ genericArgs.mSendMessageResult,
+ specialArgs.mUserControlPressedCommand,
+ specialArgs.mFeatureAbortOpcode,
+ specialArgs.mFeatureAbortReason);
+ }
+
+
+ /**
+ * Writes a HdmiCecActiveSourceChanged atom representing a change in the active source.
+ *
+ * @param logicalAddress The Logical Address of the new active source
+ * @param physicalAddress The Physical Address of the new active source
+ * @param relationshipToActiveSource The relationship between this device and the active source
+ */
+ public void activeSourceChanged(int logicalAddress, int physicalAddress,
+ @Constants.PathRelationship int relationshipToActiveSource) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.HDMI_CEC_ACTIVE_SOURCE_CHANGED,
+ logicalAddress,
+ physicalAddress,
+ relationshipToActiveSource
+ );
+ }
+
+ /**
+ * Contains the required arguments for creating any HdmiCecMessageReported atom
+ */
+ private class MessageReportedGenericArgs {
+ final int mDirection;
+ final int mInitiatorLogicalAddress;
+ final int mDestinationLogicalAddress;
+ final int mOpcode;
+ final int mSendMessageResult;
+
+ MessageReportedGenericArgs(int direction, int initiatorLogicalAddress,
+ int destinationLogicalAddress, int opcode, int sendMessageResult) {
+ this.mDirection = direction;
+ this.mInitiatorLogicalAddress = initiatorLogicalAddress;
+ this.mDestinationLogicalAddress = destinationLogicalAddress;
+ this.mOpcode = opcode;
+ this.mSendMessageResult = sendMessageResult;
+ }
+ }
+
+ /**
+ * Contains the opcode-dependent arguments for creating a HdmiCecMessageReported atom. Each
+ * field is initialized to a null-like value by default. Therefore, a freshly constructed
+ * instance of this object represents a HDMI CEC message whose type does not require any
+ * additional arguments.
+ */
+ private class MessageReportedSpecialArgs {
+ int mUserControlPressedCommand = HdmiStatsEnums.USER_CONTROL_PRESSED_COMMAND_UNKNOWN;
+ int mFeatureAbortOpcode = FEATURE_ABORT_OPCODE_UNKNOWN;
+ int mFeatureAbortReason = HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 7dc4d6e8efcf..96679c3ab767 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.tv.cec.V1_0.CecMessage;
import android.hardware.tv.cec.V1_0.HotplugEvent;
@@ -28,9 +30,11 @@ import android.os.Handler;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.stats.hdmi.HdmiStatsEnums;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -123,10 +127,14 @@ final class HdmiCecController {
private final NativeWrapper mNativeWrapperImpl;
+ private final HdmiCecAtomWriter mHdmiCecAtomWriter;
+
// Private constructor. Use HdmiCecController.create().
- private HdmiCecController(HdmiControlService service, NativeWrapper nativeWrapper) {
+ private HdmiCecController(
+ HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
mService = service;
mNativeWrapperImpl = nativeWrapper;
+ mHdmiCecAtomWriter = atomWriter;
}
/**
@@ -134,21 +142,22 @@ final class HdmiCecController {
* inner device or has no device it will return {@code null}.
*
* <p>Declared as package-private, accessed by {@link HdmiControlService} only.
- * @param service {@link HdmiControlService} instance used to create internal handler
- * and to pass callback for incoming message or event.
+ * @param service {@link HdmiControlService} instance used to create internal handler
+ * and to pass callback for incoming message or event.
+ * @param atomWriter {@link HdmiCecAtomWriter} instance for writing atoms for metrics.
* @return {@link HdmiCecController} if device is initialized successfully. Otherwise,
* returns {@code null}.
*/
- static HdmiCecController create(HdmiControlService service) {
- return createWithNativeWrapper(service, new NativeWrapperImpl());
+ static HdmiCecController create(HdmiControlService service, HdmiCecAtomWriter atomWriter) {
+ return createWithNativeWrapper(service, new NativeWrapperImpl(), atomWriter);
}
/**
* A factory method with injection of native methods for testing.
*/
static HdmiCecController createWithNativeWrapper(
- HdmiControlService service, NativeWrapper nativeWrapper) {
- HdmiCecController controller = new HdmiCecController(service, nativeWrapper);
+ HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
+ HdmiCecController controller = new HdmiCecController(service, nativeWrapper, atomWriter);
String nativePtr = nativeWrapper.nativeInit();
if (nativePtr == null) {
HdmiLogger.warning("Couldn't get tv.cec service.");
@@ -619,7 +628,7 @@ final class HdmiCecController {
public void run() {
HdmiLogger.debug("[S]:" + cecMessage);
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
- int i = 0;
+ int retransmissionCount = 0;
int errorCode = SendMessageResult.SUCCESS;
do {
errorCode = mNativeWrapperImpl.nativeSendCecCommand(
@@ -627,20 +636,25 @@ final class HdmiCecController {
if (errorCode == SendMessageResult.SUCCESS) {
break;
}
- } while (i++ < HdmiConfig.RETRANSMISSION_COUNT);
+ } while (retransmissionCount++ < HdmiConfig.RETRANSMISSION_COUNT);
final int finalError = errorCode;
if (finalError != SendMessageResult.SUCCESS) {
Slog.w(TAG, "Failed to send " + cecMessage + " with errorCode=" + finalError);
}
- if (callback != null) {
- runOnServiceThread(new Runnable() {
- @Override
- public void run() {
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ mHdmiCecAtomWriter.messageReported(
+ cecMessage,
+ FrameworkStatsLog.HDMI_CEC_MESSAGE_REPORTED__DIRECTION__OUTGOING,
+ finalError
+ );
+ if (callback != null) {
callback.onSendCompleted(finalError);
}
- });
- }
+ }
+ });
}
});
}
@@ -654,10 +668,39 @@ final class HdmiCecController {
HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
HdmiLogger.debug("[R]:" + command);
addCecMessageToHistory(true /* isReceived */, command);
+
+ mHdmiCecAtomWriter.messageReported(command,
+ incomingMessageDirection(srcAddress, dstAddress));
+
onReceiveCommand(command);
}
/**
+ * Computes the direction of an incoming message, as implied by the source and
+ * destination addresses. This will usually return INCOMING; if not, it can indicate a bug.
+ */
+ private int incomingMessageDirection(int srcAddress, int dstAddress) {
+ boolean sourceIsLocal = false;
+ boolean destinationIsLocal = false;
+ for (HdmiCecLocalDevice localDevice : getLocalDeviceList()) {
+ int logicalAddress = localDevice.getDeviceInfo().getLogicalAddress();
+ if (logicalAddress == srcAddress) {
+ sourceIsLocal = true;
+ }
+ if (logicalAddress == dstAddress) {
+ destinationIsLocal = true;
+ }
+ }
+
+ if (!sourceIsLocal && destinationIsLocal) {
+ return HdmiStatsEnums.INCOMING;
+ } else if (sourceIsLocal && destinationIsLocal) {
+ return HdmiStatsEnums.TO_SELF;
+ }
+ return HdmiStatsEnums.MESSAGE_DIRECTION_OTHER;
+ }
+
+ /**
* Called when a hotplug event issues.
*/
@ServiceThreadOnly
@@ -733,6 +776,7 @@ final class HdmiCecController {
private IHdmiCec mHdmiCec;
private final Object mLock = new Object();
private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+ @Nullable private HdmiCecCallback mCallback;
@Override
public String nativeInit() {
@@ -741,7 +785,7 @@ final class HdmiCecController {
boolean connectToHal() {
try {
- mHdmiCec = IHdmiCec.getService();
+ mHdmiCec = IHdmiCec.getService(true);
try {
mHdmiCec.linkToDeath(this, HDMI_CEC_HAL_DEATH_COOKIE);
} catch (RemoteException e) {
@@ -755,7 +799,8 @@ final class HdmiCecController {
}
@Override
- public void setCallback(HdmiCecCallback callback) {
+ public void setCallback(@NonNull HdmiCecCallback callback) {
+ mCallback = callback;
try {
mHdmiCec.setCallback(callback);
} catch (RemoteException e) {
@@ -895,6 +940,10 @@ final class HdmiCecController {
if (cookie == HDMI_CEC_HAL_DEATH_COOKIE) {
HdmiLogger.error("Service died cookie : " + cookie + "; reconnecting");
connectToHal();
+ // Reconnect the callback
+ if (mCallback != null) {
+ setCallback(mCallback);
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 68473c1830ae..29bdd6cb40c3 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -379,7 +379,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
assertRunOnServiceThread();
if (reason == mService.INITIATED_BY_ENABLE_CEC) {
mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+ getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST,
+ "HdmiCecLocalDeviceAudioSystem#onAddressAllocated()");
}
mService.sendCecCommand(
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
@@ -1324,7 +1325,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
mService.setAndBroadcastActiveSourceFromOneDeviceType(
- message.getSource(), mService.getPhysicalAddress());
+ message.getSource(), mService.getPhysicalAddress(),
+ "HdmiCecLocalDeviceAudioSystem#handleRoutingChangeAndInformationForSwitch()");
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index f2f6dbe9bde5..6257032cf709 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import android.annotation.CallSuper;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -58,11 +59,6 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// If true, turn off TV upon standby. False by default.
private boolean mAutoTvOff;
- // Local active port number used for Routing Control.
- // Default 0 means HOME is the current active path. Temp solution only.
- // TODO(amyjojo): adding system constants for input ports to TIF mapping.
- private int mLocalActivePath = 0;
-
// Determines what action should be taken upon receiving Routing Control messages.
@VisibleForTesting
protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -101,7 +97,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
assertRunOnServiceThread();
if (reason == mService.INITIATED_BY_ENABLE_CEC) {
mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
+ getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST,
+ "HdmiCecLocalDevicePlayback#onAddressAllocated()");
}
mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
mAddress, mService.getPhysicalAddress(), mDeviceType));
@@ -170,7 +167,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
mCecMessageCache.flushAll();
- // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+ // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3.
if (!connected) {
getWakeLock().release();
}
@@ -183,13 +180,12 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
if (!mService.isControlEnabled()) {
return;
}
- if (mIsActiveSource) {
+ if (isActiveSource()) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
mAddress, mService.getPhysicalAddress()));
}
- boolean wasActiveSource = mIsActiveSource;
+ boolean wasActiveSource = isActiveSource();
// Invalidate the internal active source record when goes to standby
- // This set will also update mIsActiveSource
mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
"HdmiCecLocalDevicePlayback#onStandby()");
if (initiatedByCec || !mAutoTvOff || !wasActiveSource) {
@@ -234,12 +230,13 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
}
@Override
+ @CallSuper
@ServiceThreadOnly
@VisibleForTesting
- void setIsActiveSource(boolean on) {
+ protected void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
assertRunOnServiceThread();
- super.setIsActiveSource(on);
- if (on) {
+ super.setActiveSource(logicalAddress, physicalAddress, caller);
+ if (isActiveSource()) {
getWakeLock().acquire();
} else {
getWakeLock().release();
@@ -296,7 +293,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
@Override
protected void wakeUpIfActiveSource() {
- if (!mIsActiveSource) {
+ if (!isActiveSource()) {
return;
}
// Wake up the device if the power is in standby mode, or its screen is off -
@@ -404,9 +401,16 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
"HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
return;
}
+ if (!isActiveSource()) {
+ // If routing is changed to the device while Active Source, don't invalidate the
+ // Active Source
+ setActiveSource(physicalAddress,
+ "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
+ }
switch (mPlaybackDeviceActionOnRoutingControl) {
case WAKE_UP_AND_SEND_ACTIVE_SOURCE:
- setAndBroadcastActiveSource(message, physicalAddress);
+ setAndBroadcastActiveSource(message, physicalAddress,
+ "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
break;
case WAKE_UP_ONLY:
mService.wakeUp();
@@ -438,20 +442,10 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
checkIfPendingActionsCleared();
}
- private void routeToPort(int portId) {
- // TODO(AMYJOJO): route to specific input of the port
- mLocalActivePath = portId;
- }
-
- @VisibleForTesting
- protected int getLocalActivePath() {
- return mLocalActivePath;
- }
-
@Override
protected void dump(final IndentingPrintWriter pw) {
super.dump(pw);
- pw.println("mIsActiveSource: " + mIsActiveSource);
+ pw.println("isActiveSource(): " + isActiveSource());
pw.println("mAutoTvOff:" + mAutoTvOff);
}
@@ -472,7 +466,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
@Override
public void acquire() {
mWakeLock.acquire();
- HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
+ HdmiLogger.debug("active source: %b. Wake lock acquired", isActiveSource());
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 4ff36c4b65db..4325f797416e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import android.annotation.CallSuper;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
@@ -36,10 +37,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
private static final String TAG = "HdmiCecLocalDeviceSource";
- // Indicate if current device is Active Source or not
- @VisibleForTesting
- protected boolean mIsActiveSource = false;
-
// Device has cec switch functionality or not.
// Default is false.
protected boolean mIsSwitchDevice = HdmiProperties.is_switch().orElse(false);
@@ -78,7 +75,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
mCecMessageCache.flushAll();
}
- // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
+ // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3.
if (connected) {
mService.wakeUp();
}
@@ -118,10 +115,21 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
// Nothing to do.
}
+ @Override
+ @CallSuper
+ @ServiceThreadOnly
+ void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
+ boolean wasActiveSource = isActiveSource();
+ super.setActiveSource(logicalAddress, physicalAddress, caller);
+ if (wasActiveSource && !isActiveSource()) {
+ onActiveSourceLost();
+ }
+ }
+
@ServiceThreadOnly
protected void setActiveSource(int physicalAddress, String caller) {
assertRunOnServiceThread();
- // Invalidate the internal active source record. This will also update mIsActiveSource.
+ // Invalidate the internal active source record.
ActiveSource activeSource = ActiveSource.of(Constants.ADDR_INVALID, physicalAddress);
setActiveSource(activeSource, caller);
}
@@ -135,7 +143,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
if (!getActiveSource().equals(activeSource)) {
setActiveSource(activeSource, "HdmiCecLocalDeviceSource#handleActiveSource()");
}
- setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
if (isRoutingControlFeatureEnabled()) {
switchInputOnReceivingNewActivePath(physicalAddress);
@@ -159,9 +166,11 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
// If current device is the target path, set to Active Source.
// If the path is under the current device, should switch
if (physicalAddress == mService.getPhysicalAddress() && mService.isPlaybackDevice()) {
- setAndBroadcastActiveSource(message, physicalAddress);
- }
- if (physicalAddress != mService.getPhysicalAddress()) {
+ setAndBroadcastActiveSource(message, physicalAddress,
+ "HdmiCecLocalDeviceSource#handleSetStreamPath()");
+ } else if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
+ // Invalidate the active source if stream path is set to other physical address or
+ // our physical address while not active source
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleSetStreamPath()");
}
switchInputOnReceivingNewActivePath(physicalAddress);
@@ -173,19 +182,15 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
protected boolean handleRoutingChange(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
- if (physicalAddress != mService.getPhysicalAddress()) {
+ if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
+ // Invalidate the active source if routing is changed to other physical address or
+ // our physical address while not active source
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingChange()");
}
if (!isRoutingControlFeatureEnabled()) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
- // if the current device is a pure playback device
- if (!mIsSwitchDevice
- && physicalAddress == mService.getPhysicalAddress()
- && mService.isPlaybackDevice()) {
- setAndBroadcastActiveSource(message, physicalAddress);
- }
handleRoutingChangeAndInformation(physicalAddress, message);
return true;
}
@@ -195,19 +200,15 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
protected boolean handleRoutingInformation(HdmiCecMessage message) {
assertRunOnServiceThread();
int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
- if (physicalAddress != mService.getPhysicalAddress()) {
+ if (physicalAddress != mService.getPhysicalAddress() || !isActiveSource()) {
+ // Invalidate the active source if routing is changed to other physical address or
+ // our physical address while not active source
setActiveSource(physicalAddress, "HdmiCecLocalDeviceSource#handleRoutingInformation()");
}
if (!isRoutingControlFeatureEnabled()) {
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return true;
}
- // if the current device is a pure playback device
- if (!mIsSwitchDevice
- && physicalAddress == mService.getPhysicalAddress()
- && mService.isPlaybackDevice()) {
- setAndBroadcastActiveSource(message, physicalAddress);
- }
handleRoutingChangeAndInformation(physicalAddress, message);
return true;
}
@@ -236,23 +237,21 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
// since service can decide who will be the active source when the device supports
// multiple device types in this method.
// This method should only be called when the device can be the active source.
- protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress) {
+ protected void setAndBroadcastActiveSource(HdmiCecMessage message, int physicalAddress,
+ String caller) {
mService.setAndBroadcastActiveSource(
- physicalAddress, getDeviceInfo().getDeviceType(), message.getSource());
+ physicalAddress, getDeviceInfo().getDeviceType(), message.getSource(), caller);
}
+ // Indicates if current device is the active source or not
@ServiceThreadOnly
- void setIsActiveSource(boolean on) {
- assertRunOnServiceThread();
- boolean wasActiveSource = mIsActiveSource;
- mIsActiveSource = on;
- if (wasActiveSource && !mIsActiveSource) {
- onActiveSourceLost();
- }
+ protected boolean isActiveSource() {
+ return getActiveSource().equals(getDeviceInfo().getLogicalAddress(),
+ getDeviceInfo().getPhysicalAddress());
}
protected void wakeUpIfActiveSource() {
- if (!mIsActiveSource) {
+ if (!isActiveSource()) {
return;
}
// Wake up the device
@@ -261,7 +260,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
}
protected void maySendActiveSource(int dest) {
- if (!mIsActiveSource) {
+ if (!isActiveSource()) {
return;
}
addAndStartAction(new ActiveSourceAction(this, dest));
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 0b4f31d365d3..28bd97e4843c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -117,14 +117,15 @@ public class HdmiCecMessageValidator {
// TODO: Validate more than length for the following messages.
// Messages for the One Touch Record.
- FixedLengthValidator oneByteValidator = new FixedLengthValidator(1);
addValidationInfo(Constants.MESSAGE_RECORD_ON,
new VariableLengthValidator(1, 8), DEST_DIRECT);
- addValidationInfo(Constants.MESSAGE_RECORD_STATUS, oneByteValidator, DEST_DIRECT);
+ addValidationInfo(Constants.MESSAGE_RECORD_STATUS,
+ new RecordStatusInfoValidator(), DEST_DIRECT);
// TODO: Handle messages for the Timer Programming.
// Messages for the System Information.
+ FixedLengthValidator oneByteValidator = new FixedLengthValidator(1);
addValidationInfo(Constants.MESSAGE_CEC_VERSION, oneByteValidator, DEST_DIRECT);
addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
new FixedLengthValidator(3), DEST_BROADCAST);
@@ -339,4 +340,23 @@ public class HdmiCecMessageValidator {
isValidPhysicalAddress(params, 0) && isValidPhysicalAddress(params, 2));
}
}
+
+ /**
+ * Check if the given record status message parameter is valid.
+ * A valid parameter should lie within the range description of Record Status Info defined in
+ * CEC 1.4 Specification : Operand Descriptions (Section 17)
+ */
+ private class RecordStatusInfoValidator implements ParameterValidator {
+ @Override
+ public int isValid(byte[] params) {
+ if (params.length < 1) {
+ return ERROR_PARAMETER_SHORT;
+ }
+ return toErrorCode(isWithinRange(params[0], 0x01, 0x07)
+ || isWithinRange(params[0], 0x09, 0x0E)
+ || isWithinRange(params[0], 0x10, 0x17)
+ || isWithinRange(params[0], 0x1A, 0x1B)
+ || params[0] == 0x1F);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 44b6a63faea1..27bd0563cea5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -397,6 +397,10 @@ public class HdmiControlService extends SystemService {
// Set to true if the logical address allocation is completed.
private boolean mAddressAllocated = false;
+ // Object that handles logging statsd atoms.
+ // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing.
+ private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter();
+
// Buffer for processing the incoming cec messages while allocating logical addresses.
private final class CecMessageBuffer {
private List<HdmiCecMessage> mBuffer = new ArrayList<>();
@@ -509,7 +513,7 @@ public class HdmiControlService extends SystemService {
mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
if (mCecController == null) {
- mCecController = HdmiCecController.create(this);
+ mCecController = HdmiCecController.create(this, getAtomWriter());
}
if (mCecController != null) {
if (mHdmiControlEnabled) {
@@ -1596,7 +1600,7 @@ public class HdmiControlService extends SystemService {
if (isPlaybackDevice()) {
// if playback device itself is the active source,
// return its own device info.
- if (playback() != null && playback().mIsActiveSource) {
+ if (playback() != null && playback().isActiveSource()) {
return playback().getDeviceInfo();
}
// Otherwise get the active source and look for it from the device list
@@ -2236,7 +2240,7 @@ public class HdmiControlService extends SystemService {
@Override
public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
enforceAccessPermission();
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
HdmiControlService.this.setHdmiCecVolumeControlEnabled(
isHdmiCecVolumeControlEnabled);
@@ -3233,21 +3237,17 @@ public class HdmiControlService extends SystemService {
mActiveSource.logicalAddress = logicalAddress;
mActiveSource.physicalAddress = physicalAddress;
}
+
+ getAtomWriter().activeSourceChanged(logicalAddress, physicalAddress,
+ HdmiUtils.pathRelationship(getPhysicalAddress(), physicalAddress));
+
// If the current device is a source device, check if the current Active Source matches
- // the local device info. Set mIsActiveSource of the local device accordingly.
+ // the local device info.
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
- // mIsActiveSource only exists in source device, ignore this setting if the current
- // device is not an HdmiCecLocalDeviceSource.
- if (!(device instanceof HdmiCecLocalDeviceSource)) {
- device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
- false, caller);
- continue;
- }
boolean deviceIsActiveSource =
logicalAddress == device.getDeviceInfo().getLogicalAddress()
&& physicalAddress == getPhysicalAddress();
- ((HdmiCecLocalDeviceSource) device).setIsActiveSource(deviceIsActiveSource);
device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
deviceIsActiveSource, caller);
}
@@ -3258,22 +3258,22 @@ public class HdmiControlService extends SystemService {
// For example, when receiving broadcast messages, all the device types will call this
// method but only one of them will be the Active Source.
protected void setAndBroadcastActiveSource(
- int physicalAddress, int deviceType, int source) {
+ int physicalAddress, int deviceType, int source, String caller) {
// If the device has both playback and audio system logical addresses,
// playback will claim active source. Otherwise audio system will.
if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
HdmiCecLocalDevicePlayback playback = playback();
- playback.setIsActiveSource(true);
+ playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
+ caller);
playback.wakeUpIfActiveSource();
playback.maySendActiveSource(source);
}
if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
- if (playback() != null) {
- audioSystem.setIsActiveSource(false);
- } else {
- audioSystem.setIsActiveSource(true);
+ if (playback() == null) {
+ audioSystem.setActiveSource(audioSystem.getDeviceInfo().getLogicalAddress(),
+ physicalAddress, caller);
audioSystem.wakeUpIfActiveSource();
audioSystem.maySendActiveSource(source);
}
@@ -3286,24 +3286,21 @@ public class HdmiControlService extends SystemService {
// and this method updates Active Source in all the device types sharing the same
// Physical Address.
protected void setAndBroadcastActiveSourceFromOneDeviceType(
- int sourceAddress, int physicalAddress) {
+ int sourceAddress, int physicalAddress, String caller) {
// If the device has both playback and audio system logical addresses,
// playback will claim active source. Otherwise audio system will.
HdmiCecLocalDevicePlayback playback = playback();
HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
if (playback != null) {
- playback.setIsActiveSource(true);
+ playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress,
+ caller);
playback.wakeUpIfActiveSource();
playback.maySendActiveSource(sourceAddress);
- if (audioSystem != null) {
- audioSystem.setIsActiveSource(false);
- }
- } else {
- if (audioSystem != null) {
- audioSystem.setIsActiveSource(true);
- audioSystem.wakeUpIfActiveSource();
- audioSystem.maySendActiveSource(sourceAddress);
- }
+ } else if (audioSystem != null) {
+ audioSystem.setActiveSource(audioSystem.getDeviceInfo().getLogicalAddress(),
+ physicalAddress, caller);
+ audioSystem.wakeUpIfActiveSource();
+ audioSystem.maySendActiveSource(sourceAddress);
}
}
@@ -3363,6 +3360,11 @@ public class HdmiControlService extends SystemService {
}
}
+ @VisibleForTesting
+ HdmiCecAtomWriter getAtomWriter() {
+ return mAtomWriter;
+ }
+
boolean isMhlInputChangeEnabled() {
synchronized (mLock) {
return mMhlInputChangeEnabled;
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 4962af176f18..e78a86c21453 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -85,7 +85,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
// Because only source device can create this action, it's safe to cast.
HdmiCecLocalDeviceSource source = source();
source.mService.setAndBroadcastActiveSourceFromOneDeviceType(
- mTargetAddress, getSourcePath());
+ mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()");
// When OneTouchPlay is called, client side should be responsible to send out the intent
// of which internal source, for example YouTube, it would like to switch to.
// Here we only update the active port and the active source records in the local
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
index 0907e5d03c78..acafda6619f3 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java
@@ -119,7 +119,8 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction {
// claim Active Source and start to query TV system audio mode support.
if (audioSystem().mService.isPlaybackDevice()) {
audioSystem().mService.setAndBroadcastActiveSourceFromOneDeviceType(
- Constants.ADDR_BROADCAST, getSourcePath());
+ Constants.ADDR_BROADCAST, getSourcePath(),
+ "SystemAudioInitiationActionFromAvr#handleActiveSourceTimeout()");
mState = STATE_WAITING_FOR_TV_SUPPORT;
queryTvSystemAudioModeSupport();
} else {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6c14b2cbed09..3f4ddea22a24 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -46,6 +46,7 @@ import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerInternal;
+import android.hardware.input.InputManagerInternal.LidSwitchCallback;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.TouchCalibration;
import android.media.AudioManager;
@@ -54,6 +55,8 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
+import android.os.InputEventInjectionResult;
+import android.os.InputEventInjectionSync;
import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
@@ -189,6 +192,10 @@ public class InputManagerService extends IInputManager.Stub
private Map<IBinder, VibratorToken> mVibratorTokens = new ArrayMap<IBinder, VibratorToken>();
private int mNextVibratorTokenValue;
+ // State for lid switch
+ private final Object mLidSwitchLock = new Object();
+ private List<LidSwitchCallback> mLidSwitchCallbacks = new ArrayList<>();
+
// State for the currently installed input filter.
final Object mInputFilterLock = new Object();
IInputFilter mInputFilter; // guarded by mInputFilterLock
@@ -219,13 +226,15 @@ public class InputManagerService extends IInputManager.Stub
int deviceId, int sourceMask, int sw);
private static native boolean nativeHasKeys(long ptr,
int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
- private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel);
- private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
- int displayId, boolean isGestureMonitor);
- private static native void nativeUnregisterInputChannel(long ptr, IBinder connectionToken);
+ private static native InputChannel nativeCreateInputChannel(long ptr, String name);
+ private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId,
+ boolean isGestureMonitor, String name);
+ private static native void nativeRemoveInputChannel(long ptr, IBinder connectionToken);
private static native void nativePilferPointers(long ptr, IBinder token);
private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
private static native void nativeSetInTouchMode(long ptr, boolean inTouchMode);
+ private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
+ private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
private static native int nativeInjectInputEvent(long ptr, InputEvent event,
int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
int policyFlags);
@@ -233,7 +242,7 @@ public class InputManagerService extends IInputManager.Stub
private static native void nativeToggleCapsLock(long ptr, int deviceId);
private static native void nativeDisplayRemoved(long ptr, int displayId);
private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
- private static native void nativeSetSystemUiVisibility(long ptr, int visibility);
+ private static native void nativeSetSystemUiLightsOut(long ptr, boolean lightsOut);
private static native void nativeSetFocusedApplication(long ptr,
int displayId, InputApplicationHandle application);
private static native void nativeSetFocusedDisplay(long ptr, int displayId);
@@ -261,12 +270,6 @@ public class InputManagerService extends IInputManager.Stub
private static native void nativeNotifyPortAssociationsChanged(long ptr);
private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled);
- // Input event injection constants defined in InputDispatcher.h.
- private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
- private static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1;
- private static final int INPUT_EVENT_INJECTION_FAILED = 2;
- private static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3;
-
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -330,6 +333,9 @@ public class InputManagerService extends IInputManager.Stub
public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER;
public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE;
+ /** Indicates an open state for the lid switch. */
+ public static final int SW_STATE_LID_OPEN = 0;
+
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
final boolean mUseDevInputEventForAudioJack;
@@ -353,13 +359,33 @@ public class InputManagerService extends IInputManager.Stub
}
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
+ if (mWindowManagerCallbacks != null) {
+ unregisterLidSwitchCallbackInternal(mWindowManagerCallbacks);
+ }
mWindowManagerCallbacks = callbacks;
+ registerLidSwitchCallbackInternal(mWindowManagerCallbacks);
}
public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) {
mWiredAccessoryCallbacks = callbacks;
}
+ void registerLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) {
+ boolean lidOpen;
+ synchronized (mLidSwitchLock) {
+ mLidSwitchCallbacks.add(callback);
+ lidOpen = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID)
+ == SW_STATE_LID_OPEN;
+ }
+ callback.notifyLidSwitchChanged(0 /* whenNanos */, lidOpen);
+ }
+
+ void unregisterLidSwitchCallbackInternal(@NonNull LidSwitchCallback callback) {
+ synchronized (mLidSwitchLock) {
+ mLidSwitchCallbacks.remove(callback);
+ }
+ }
+
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(mPtr);
@@ -371,6 +397,8 @@ public class InputManagerService extends IInputManager.Stub
registerShowTouchesSettingObserver();
registerAccessibilityLargePointerSettingObserver();
registerLongPressTimeoutObserver();
+ registerMaximumObscuringOpacityForTouchSettingObserver();
+ registerBlockUntrustedTouchesModeSettingObserver();
mContext.registerReceiver(new BroadcastReceiver() {
@Override
@@ -386,6 +414,8 @@ public class InputManagerService extends IInputManager.Stub
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
updateDeepPressStatusFromSettings("just booted");
+ updateMaximumObscuringOpacityForTouchFromSettings();
+ updateBlockUntrustedTouchesModeFromSettings();
}
// TODO(BT) Pass in parameter for bluetooth system
@@ -522,10 +552,8 @@ public class InputManagerService extends IInputManager.Stub
throw new IllegalArgumentException("displayId must >= 0.");
}
- InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
- nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);
- inputChannels[0].dispose(); // don't need to retain the Java object reference
- return inputChannels[1];
+ return nativeCreateInputMonitor(mPtr, displayId, false /* isGestureMonitor */,
+ inputChannelName);
}
/**
@@ -552,38 +580,32 @@ public class InputManagerService extends IInputManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
- InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
- InputMonitorHost host = new InputMonitorHost(inputChannels[0]);
- nativeRegisterInputMonitor(
- mPtr, inputChannels[0], displayId, true /*isGestureMonitor*/);
+ InputChannel inputChannel = nativeCreateInputMonitor(
+ mPtr, displayId, true /*isGestureMonitor*/, inputChannelName);
+ InputMonitorHost host = new InputMonitorHost(inputChannel.getToken());
synchronized (mGestureMonitorPidsLock) {
- mGestureMonitorPidsByToken.put(inputChannels[1].getToken(), pid);
+ mGestureMonitorPidsByToken.put(inputChannel.getToken(), pid);
}
- return new InputMonitor(inputChannels[1], host);
+ return new InputMonitor(inputChannel, host);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
- * Registers an input channel so that it can be used as an input event target. The channel is
- * registered with a generated token.
+ * Creates an input channel to be used as an input event target.
*
- * @param inputChannel The input channel to register.
+ * @param name The name of this input channel
*/
- public void registerInputChannel(InputChannel inputChannel) {
- if (inputChannel == null) {
- throw new IllegalArgumentException("inputChannel must not be null.");
- }
-
- nativeRegisterInputChannel(mPtr, inputChannel);
+ public InputChannel createInputChannel(String name) {
+ return nativeCreateInputChannel(mPtr, name);
}
/**
- * Unregisters an input channel.
+ * Removes an input channel.
* @param connectionToken The input channel to unregister.
*/
- public void unregisterInputChannel(IBinder connectionToken) {
+ public void removeInputChannel(IBinder connectionToken) {
if (connectionToken == null) {
throw new IllegalArgumentException("connectionToken must not be null.");
}
@@ -591,7 +613,7 @@ public class InputManagerService extends IInputManager.Stub
mGestureMonitorPidsByToken.remove(connectionToken);
}
- nativeUnregisterInputChannel(mPtr, connectionToken);
+ nativeRemoveInputChannel(mPtr, connectionToken);
}
/**
@@ -665,9 +687,9 @@ public class InputManagerService extends IInputManager.Stub
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
- if (mode != InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
- && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
- && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
+ if (mode != InputEventInjectionSync.NONE
+ && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+ && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
throw new IllegalArgumentException("mode is invalid");
}
@@ -682,16 +704,16 @@ public class InputManagerService extends IInputManager.Stub
Binder.restoreCallingIdentity(ident);
}
switch (result) {
- case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ case InputEventInjectionResult.PERMISSION_DENIED:
Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
throw new SecurityException(
"Injecting to another application requires INJECT_EVENTS permission");
- case INPUT_EVENT_INJECTION_SUCCEEDED:
+ case InputEventInjectionResult.SUCCEEDED:
return true;
- case INPUT_EVENT_INJECTION_TIMED_OUT:
+ case InputEventInjectionResult.TIMED_OUT:
Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
return false;
- case INPUT_EVENT_INJECTION_FAILED:
+ case InputEventInjectionResult.FAILED:
default:
Slog.w(TAG, "Input event injection from pid " + pid + " failed.");
return false;
@@ -1568,8 +1590,8 @@ public class InputManagerService extends IInputManager.Stub
nativeSetInputDispatchMode(mPtr, enabled, frozen);
}
- public void setSystemUiVisibility(int visibility) {
- nativeSetSystemUiVisibility(mPtr, visibility);
+ public void setSystemUiLightsOut(boolean lightsOut) {
+ nativeSetSystemUiLightsOut(mPtr, lightsOut);
}
/**
@@ -1719,6 +1741,46 @@ public class InputManagerService extends IInputManager.Stub
}, UserHandle.USER_ALL);
}
+ private void registerBlockUntrustedTouchesModeSettingObserver() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BLOCK_UNTRUSTED_TOUCHES_MODE),
+ /* notifyForDescendants */ true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateBlockUntrustedTouchesModeFromSettings();
+ }
+ }, UserHandle.USER_ALL);
+ }
+
+ private void updateBlockUntrustedTouchesModeFromSettings() {
+ final int mode = InputManager.getInstance().getBlockUntrustedTouchesMode(mContext);
+ nativeSetBlockUntrustedTouchesMode(mPtr, mode);
+ }
+
+ private void registerMaximumObscuringOpacityForTouchSettingObserver() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
+ /* notifyForDescendants */ true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateMaximumObscuringOpacityForTouchFromSettings();
+ }
+ }, UserHandle.USER_ALL);
+ }
+
+ private void updateMaximumObscuringOpacityForTouchFromSettings() {
+ final float opacity = InputManager.getInstance().getMaximumObscuringOpacityForTouch(
+ mContext);
+ if (opacity < 0 || opacity > 1) {
+ Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
+ + ", it should be >= 0 and <= 1, rejecting update.");
+ return;
+ }
+ nativeSetMaximumObscuringOpacityForTouch(mPtr, opacity);
+ }
+
private int getShowTouchesSetting(int defaultValue) {
int result = defaultValue;
try {
@@ -1942,6 +2004,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mInputFilterLock) { }
synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
synchronized (mGestureMonitorPidsLock) { /* Test if blocked by gesture monitor pids lock */}
+ synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
nativeMonitor(mPtr);
}
@@ -1972,7 +2035,15 @@ public class InputManagerService extends IInputManager.Stub
if ((switchMask & SW_LID_BIT) != 0) {
final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
- mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
+
+ ArrayList<LidSwitchCallback> callbacksCopy;
+ synchronized (mLidSwitchLock) {
+ callbacksCopy = new ArrayList<>(mLidSwitchCallbacks);
+ }
+ for (int i = 0; i < callbacksCopy.size(); i++) {
+ LidSwitchCallback callbacks = callbacksCopy.get(i);
+ callbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
+ }
}
if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) {
@@ -2018,6 +2089,16 @@ public class InputManagerService extends IInputManager.Stub
}
}
+ // Native callback
+ private void notifyUntrustedTouch(String packageName) {
+ // TODO(b/169067926): Remove toast after gathering feedback on dogfood.
+ DisplayThread.getHandler().post(() ->
+ Toast.makeText(mContext,
+ "Touch obscured by " + packageName
+ + " will be blocked. Check go/s-untrusted-touches",
+ Toast.LENGTH_SHORT).show());
+ }
+
// Native callback.
private long notifyANR(InputApplicationHandle inputApplicationHandle, IBinder token,
String reason) {
@@ -2271,20 +2352,13 @@ public class InputManagerService extends IInputManager.Stub
/**
* Callback interface implemented by the Window Manager.
*/
- public interface WindowManagerCallbacks {
+ public interface WindowManagerCallbacks extends LidSwitchCallback {
/**
* This callback is invoked when the confuguration changes.
*/
public void notifyConfigurationChanged();
/**
- * This callback is invoked when the lid switch changes state.
- * @param whenNanos the time when the change occurred
- * @param lidOpen true if the lid is open
- */
- public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
-
- /**
* This callback is invoked when the camera lens cover switch changes state.
* @param whenNanos the time when the change occurred
* @param lensCovered true is the lens is covered
@@ -2442,21 +2516,20 @@ public class InputManagerService extends IInputManager.Stub
* Interface for the system to handle request from InputMonitors.
*/
private final class InputMonitorHost extends IInputMonitorHost.Stub {
- private final InputChannel mInputChannel;
+ private final IBinder mToken;
- InputMonitorHost(InputChannel channel) {
- mInputChannel = channel;
+ InputMonitorHost(IBinder token) {
+ mToken = token;
}
@Override
public void pilferPointers() {
- nativePilferPointers(mPtr, mInputChannel.getToken());
+ nativePilferPointers(mPtr, mToken);
}
@Override
public void dispose() {
- nativeUnregisterInputChannel(mPtr, mInputChannel.getToken());
- mInputChannel.dispose();
+ nativeRemoveInputChannel(mPtr, mToken);
}
}
@@ -2612,6 +2685,16 @@ public class InputManagerService extends IInputManager.Stub
@NonNull IBinder toChannelToken) {
return InputManagerService.this.transferTouchFocus(fromChannelToken, toChannelToken);
}
+
+ @Override
+ public void registerLidSwitchCallback(LidSwitchCallback callbacks) {
+ registerLidSwitchCallbackInternal(callbacks);
+ }
+
+ @Override
+ public void unregisterLidSwitchCallback(LidSwitchCallback callbacks) {
+ unregisterLidSwitchCallbackInternal(callbacks);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
index 845fca1127dd..78c414452ddd 100644
--- a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
+++ b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
@@ -73,7 +73,7 @@ final class InputContentUriTokenHandler extends IInputContentUriToken.Stub {
}
private void doTakeLocked(@NonNull IBinder permissionOwner) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
try {
UriGrantsManager.getService().grantUriPermissionFromOwner(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bba248c2ab74..9947ecd42e31 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -15,8 +15,37 @@
package com.android.server.inputmethod;
+import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
+import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_SEQ;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID;
+import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION;
+import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY;
+import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN;
+import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
+import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
+import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
+import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED;
+import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
+import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
+import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_REQUESTED;
+import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.CLIENTS;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ELAPSED_REALTIME_NANOS;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.INPUT_METHOD_MANAGER_SERVICE;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.INPUT_METHOD_SERVICE;
+import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.ENTRY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -34,8 +63,6 @@ import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
-import android.app.AlertDialog;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
@@ -47,9 +74,6 @@ import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
@@ -61,10 +85,8 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Matrix;
-import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManagerInternal;
import android.inputmethodservice.InputMethodService;
@@ -74,6 +96,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IInterface;
import android.os.LocaleList;
@@ -96,20 +119,19 @@ import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.ContextThemeWrapper;
+import android.util.imetracing.ImeTracing;
+import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.IWindowManager;
import android.view.InputChannel;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
@@ -123,12 +145,6 @@ import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.ArrayAdapter;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.RadioButton;
-import android.widget.Switch;
-import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
@@ -159,7 +175,6 @@ import com.android.internal.view.InputBindResult;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
@@ -324,10 +339,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
new ArrayMap<>();
private final boolean mIsLowRam;
- private final HardKeyboardListener mHardKeyboardListener;
private final AppOpsManager mAppOpsManager;
private final UserManager mUserManager;
private final UserManagerInternal mUserManagerInternal;
+ private final InputMethodMenuController mMenuController;
/**
* Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -345,7 +360,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
- private final InputMethodSubtypeSwitchingController mSwitchingController;
+ final InputMethodSubtypeSwitchingController mSwitchingController;
/**
* Tracks how many times {@link #mMethodMap} was updated.
@@ -374,7 +389,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Ongoing notification
private NotificationManager mNotificationManager;
- private KeyguardManager mKeyguardManager;
+ KeyguardManager mKeyguardManager;
private @Nullable StatusBarManagerService mStatusBar;
private Notification.Builder mImeSwitcherNotification;
private PendingIntent mImeSwitchPendingIntent;
@@ -718,21 +733,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
*/
int mImeWindowVis;
- private AlertDialog.Builder mDialogBuilder;
- private AlertDialog mSwitchingDialog;
- private IBinder mSwitchingDialogToken = new Binder();
- private View mSwitchingDialogTitleView;
- private InputMethodInfo[] mIms;
- private int[] mSubtypeIds;
private LocaleList mLastSystemLocales;
- private boolean mShowImeWithHardKeyboard;
private boolean mAccessibilityRequestingNoSoftKeyboard;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
private final IPackageManager mIPackageManager;
private final String mSlotIme;
+ private HandlerThread mTracingThread;
+
/**
- * Registered {@link InputMethodListListeners}.
+ * Registered {@link InputMethodListListener}.
* This variable can be accessed from both of MainThread and BinderThread.
*/
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
@@ -1132,7 +1142,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
synchronized (mMethodMap) {
if (showImeUri.equals(uri)) {
- updateKeyboardFromSettingsLocked();
+ mMenuController.updateKeyboardFromSettingsLocked();
} else if (accessibilityRequestingNoImeUri.equals(uri)) {
final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
@@ -1220,7 +1230,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
}
- hideInputMethodMenu();
+ mMenuController.hideInputMethodMenu();
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
@@ -1511,7 +1521,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void sessionCreated(IInputMethodSession session) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mParentIMMS.onSessionCreated(mMethod, session, mChannel);
} finally {
@@ -1520,29 +1530,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private class HardKeyboardListener
- implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
- @Override
- public void onHardKeyboardStatusChange(boolean available) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
- available ? 1 : 0));
- }
-
- public void handleHardKeyboardStatusChange(boolean available) {
- if (DEBUG) {
- Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
- }
- synchronized(mMethodMap) {
- if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
- && mSwitchingDialog.isShowing()) {
- mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_section).setVisibility(
- available ? View.VISIBLE : View.GONE);
- }
- }
- }
- }
-
private static final class UserSwitchHandlerTask implements Runnable {
final InputMethodManagerService mService;
@@ -1681,7 +1668,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mUserManager = mContext.getSystemService(UserManager.class);
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
- mHardKeyboardListener = new HardKeyboardListener();
mHasFeature = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_INPUT_METHODS);
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
@@ -1702,7 +1688,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
.setPackage(mContext.getPackageName());
- mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
mShowOngoingImeSwitcherForPhones = false;
@@ -1724,6 +1711,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mSettings, context);
+ mMenuController = new InputMethodMenuController(this);
+
+ mTracingThread = new HandlerThread("android.tracing", Process.THREAD_PRIORITY_BACKGROUND);
+ mTracingThread.start();
}
private void resetDefaultImeLocked(Context context) {
@@ -1854,8 +1845,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
+ final InputMethodMenuController.HardKeyboardListener hardKeyboardListener =
+ mMenuController.getHardKeyboardListener();
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
- mHardKeyboardListener);
+ hardKeyboardListener);
}
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
@@ -2324,7 +2317,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurClient = null;
mCurActivityViewToScreenMatrix = null;
- hideInputMethodMenuLocked();
+ mMenuController.hideInputMethodMenuLocked();
}
}
@@ -2530,7 +2523,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
+ mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
+ PendingIntent.FLAG_IMMUTABLE));
if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
mLastBindTime = SystemClock.uptimeMillis();
@@ -2805,7 +2799,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
private boolean shouldShowImeSwitcherLocked(int visibility) {
if (!mShowOngoingImeSwitcherForPhones) return false;
- if (mSwitchingDialog != null) return false;
+ if (mMenuController.getSwitchingDialogLocked() != null) return false;
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
&& mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2922,6 +2916,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ void updateSystemUiLocked() {
+ updateSystemUiLocked(mImeWindowVis, mBackDisposition);
+ }
+
// Caution! This method is called in this class. Handle multi-user carefully
private void updateSystemUiLocked(int vis, int backDisposition) {
if (mCurToken == null) {
@@ -2992,7 +2990,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void updateFromSettingsLocked(boolean enabledMayChange) {
updateInputMethodsFromSettingsLocked(enabledMayChange);
- updateKeyboardFromSettingsLocked();
+ mMenuController.updateKeyboardFromSettingsLocked();
}
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
@@ -3049,17 +3047,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
- public void updateKeyboardFromSettingsLocked() {
- mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
- if (mSwitchingDialog != null
- && mSwitchingDialogTitleView != null
- && mSwitchingDialog.isShowing()) {
- final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_switch);
- hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
- }
- }
-
/* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
@@ -3463,6 +3450,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
+ final boolean startInputByWinGainedFocus =
+ (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
@@ -3506,7 +3496,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
InputBindResult res = null;
switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
- if (!isTextEditor || !doAutoShow) {
+ if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) {
if (LayoutParams.mayUseInputMethod(windowFlags)) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
@@ -3555,7 +3545,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
break;
case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
- if (isImeVisible()) {
+ if (!sameWindowFocused) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
hideCurrentInputLocked(mCurFocusedWindow, 0, null,
SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
@@ -3584,7 +3574,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Window asks to always show input");
if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
unverifiedTargetSdkVersion, startInputFlags)) {
- if (!isImeVisible()) {
+ if (!sameWindowFocused) {
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, missingMethods,
attribute, startInputFlags, startInputReason);
@@ -3603,17 +3593,26 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!didStart) {
if (attribute != null) {
- if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
+ if (sameWindowFocused) {
+ // On previous platforms, when Dialogs re-gained focus, the Activity behind
+ // would briefly gain focus first, and dismiss the IME.
+ // On R that behavior has been fixed, but unfortunately apps have come
+ // to rely on this behavior to hide the IME when the editor no longer has focus
+ // To maintain compatibility, we are now hiding the IME when we don't have
+ // an editor upon refocusing a window.
+ if (startInputByWinGainedFocus) {
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
+ }
+ res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
+ startInputFlags, startInputReason);
+ } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
|| (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
startInputFlags, startInputReason);
} else {
res = InputBindResult.NO_EDITOR;
}
- } else if (sameWindowFocused) {
- return new InputBindResult(
- InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
- null, null, null, -1, null);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -3677,10 +3676,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public boolean isInputMethodPickerShownForTest() {
synchronized(mMethodMap) {
- if (mSwitchingDialog == null) {
- return false;
- }
- return mSwitchingDialog.isShowing();
+ return mMenuController.isisInputMethodPickerShownForTestLocked();
}
}
@@ -3915,7 +3911,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
* {@link InputMethodService#onCreate()}.
*
* <p>TODO(Bug 113914148): Check if we can remove this.</p>
- * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight()}
+ * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
*/
@Override
public int getInputMethodWindowVisibleHeight() {
@@ -4018,6 +4014,112 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
}
+ /**
+ * Starting point for dumping the IME tracing information in proto format.
+ *
+ * @param clientProtoDump dump information from the IME client side
+ */
+ @BinderThread
+ @Override
+ @GuardedBy("mMethodMap")
+ public void startProtoDump(byte[] clientProtoDump) {
+ mTracingThread.getThreadHandler().post(() -> {
+ if (!ImeTracing.getInstance().isAvailable() || !ImeTracing.getInstance().isEnabled()) {
+ return;
+ }
+ if (clientProtoDump == null && mCurClient == null) {
+ return;
+ }
+
+ ProtoOutputStream proto = new ProtoOutputStream();
+ final long token = proto.start(ENTRY);
+ proto.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ dumpDebug(proto, INPUT_METHOD_MANAGER_SERVICE);
+
+ IBinder service = null;
+ synchronized (mMethodMap) {
+ if (mCurMethod != null) {
+ service = mCurMethod.asBinder();
+ }
+ }
+
+ if (service != null) {
+ try {
+ proto.write(INPUT_METHOD_SERVICE,
+ TransferPipe.dumpAsync(service, ImeTracing.PROTO_ARG));
+ } catch (IOException | RemoteException e) {
+ Log.e(TAG, "Exception while collecting ime process dump", e);
+ }
+ }
+
+ if (clientProtoDump != null) {
+ proto.write(CLIENTS, clientProtoDump);
+ } else {
+ IBinder client = null;
+ synchronized (mMethodMap) {
+ if (mCurClient != null && mCurClient.client != null) {
+ client = mCurClient.client.asBinder();
+ }
+ }
+
+ if (client != null) {
+ try {
+ proto.write(CLIENTS,
+ TransferPipe.dumpAsync(client, ImeTracing.PROTO_ARG));
+ } catch (IOException | RemoteException e) {
+ Log.e(TAG, "Exception while collecting client side ime dump", e);
+ }
+ }
+ }
+ proto.end(token);
+ ImeTracing.getInstance().addToBuffer(proto);
+ });
+ }
+
+ @BinderThread
+ @Override
+ public boolean isImeTraceEnabled() {
+ return ImeTracing.getInstance().isEnabled();
+ }
+
+ @GuardedBy("mMethodMap")
+ private void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ synchronized (mMethodMap) {
+ final long token = proto.start(fieldId);
+ proto.write(CUR_METHOD_ID, mCurMethodId);
+ proto.write(CUR_SEQ, mCurSeq);
+ proto.write(CUR_CLIENT, Objects.toString(mCurClient));
+ proto.write(CUR_FOCUSED_WINDOW_NAME,
+ mWindowManagerInternal.getWindowName(mCurFocusedWindow));
+ proto.write(LAST_IME_TARGET_WINDOW_NAME,
+ mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
+ proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
+ InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+ if (mCurAttribute != null) {
+ mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE);
+ }
+ proto.write(CUR_ID, mCurId);
+ proto.write(SHOW_REQUESTED, mShowRequested);
+ proto.write(SHOW_EXPLICITLY_REQUESTED, mShowExplicitlyRequested);
+ proto.write(SHOW_FORCED, mShowForced);
+ proto.write(INPUT_SHOWN, mInputShown);
+ proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
+ proto.write(CUR_TOKEN, Objects.toString(mCurToken));
+ proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
+ proto.write(SYSTEM_READY, mSystemReady);
+ proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
+ proto.write(HAVE_CONNECTION, mHaveConnection);
+ proto.write(BOUND_TO_METHOD, mBoundToMethod);
+ proto.write(IS_INTERACTIVE, mIsInteractive);
+ proto.write(BACK_DISPOSITION, mBackDisposition);
+ proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis);
+ proto.write(SHOW_IME_WITH_HARD_KEYBOARD, mMenuController.getShowImeWithHardKeyboard());
+ proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD,
+ mAccessibilityRequestingNoSoftKeyboard);
+ proto.end(token);
+ }
+ }
+
@BinderThread
private void notifyUserAction(@NonNull IBinder token) {
if (DEBUG) {
@@ -4105,7 +4207,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!calledWithValidTokenLocked(token)) {
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
hideCurrentInputLocked(
mLastImeTargetWindow, flags, null,
@@ -4123,7 +4225,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!calledWithValidTokenLocked(token)) {
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
showCurrentInputLocked(mLastImeTargetWindow, flags, null,
SoftInputShowHideReason.SHOW_MY_SOFT_INPUT);
@@ -4178,7 +4280,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
return false;
}
- showInputMethodMenu(showAuxSubtypes, displayId);
+ mMenuController.showInputMethodMenu(showAuxSubtypes, displayId);
return true;
case MSG_SHOW_IM_SUBTYPE_ENABLER:
@@ -4415,7 +4517,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
+ final InputMethodMenuController.HardKeyboardListener hardKeyboardListener =
+ mMenuController.getHardKeyboardListener();
+ hardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
return true;
case MSG_SYSTEM_UNLOCK_USER: {
final int userId = msg.arg1;
@@ -4666,208 +4770,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
}
- private boolean isScreenLocked() {
- return mKeyguardManager != null
- && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
- }
-
- private void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
- if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
-
- final boolean isScreenLocked = isScreenLocked();
-
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
- int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
- if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
-
- synchronized (mMethodMap) {
- final List<ImeSubtypeListItem> imList =
- mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
- showAuxSubtypes, isScreenLocked);
- if (imList.isEmpty()) {
- return;
- }
-
- hideInputMethodMenuLocked();
-
- if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
- final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
- if (currentSubtype != null) {
- final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
- lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
- currentImi, currentSubtype.hashCode());
- }
- }
-
- final int N = imList.size();
- mIms = new InputMethodInfo[N];
- mSubtypeIds = new int[N];
- int checkedItem = 0;
- for (int i = 0; i < N; ++i) {
- final ImeSubtypeListItem item = imList.get(i);
- mIms[i] = item.mImi;
- mSubtypeIds[i] = item.mSubtypeId;
- if (mIms[i].getId().equals(lastInputMethodId)) {
- int subtypeId = mSubtypeIds[i];
- if ((subtypeId == NOT_A_SUBTYPE_ID)
- || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
- || (subtypeId == lastInputMethodSubtypeId)) {
- checkedItem = i;
- }
- }
- }
-
- final ActivityThread currentThread = ActivityThread.currentActivityThread();
- final Context settingsContext = new ContextThemeWrapper(
- displayId == DEFAULT_DISPLAY ? currentThread.getSystemUiContext()
- : currentThread.createSystemUiContext(displayId),
- com.android.internal.R.style.Theme_DeviceDefault_Settings);
-
- mDialogBuilder = new AlertDialog.Builder(settingsContext);
- mDialogBuilder.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- hideInputMethodMenu();
- }
- });
-
- final Context dialogContext = mDialogBuilder.getContext();
- final TypedArray a = dialogContext.obtainStyledAttributes(null,
- com.android.internal.R.styleable.DialogPreference,
- com.android.internal.R.attr.alertDialogStyle, 0);
- final Drawable dialogIcon = a.getDrawable(
- com.android.internal.R.styleable.DialogPreference_dialogIcon);
- a.recycle();
-
- mDialogBuilder.setIcon(dialogIcon);
-
- final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
- final View tv = inflater.inflate(
- com.android.internal.R.layout.input_method_switch_dialog_title, null);
- mDialogBuilder.setCustomTitle(tv);
-
- // Setup layout for a toggle switch of the hardware keyboard
- mSwitchingDialogTitleView = tv;
- mSwitchingDialogTitleView
- .findViewById(com.android.internal.R.id.hard_keyboard_section)
- .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
- ? View.VISIBLE : View.GONE);
- final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
- com.android.internal.R.id.hard_keyboard_switch);
- hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
- hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- mSettings.setShowImeWithHardKeyboard(isChecked);
- // Ensure that the input method dialog is dismissed when changing
- // the hardware keyboard state.
- hideInputMethodMenu();
- }
- });
-
- final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
- com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
- final OnClickListener choiceListener = new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- synchronized (mMethodMap) {
- if (mIms == null || mIms.length <= which || mSubtypeIds == null
- || mSubtypeIds.length <= which) {
- return;
- }
- final InputMethodInfo im = mIms[which];
- int subtypeId = mSubtypeIds[which];
- adapter.mCheckedItem = which;
- adapter.notifyDataSetChanged();
- hideInputMethodMenu();
- if (im != null) {
- if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
- subtypeId = NOT_A_SUBTYPE_ID;
- }
- setInputMethodLocked(im.getId(), subtypeId);
- }
- }
- }
- };
- mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
-
- mSwitchingDialog = mDialogBuilder.create();
- mSwitchingDialog.setCanceledOnTouchOutside(true);
- final Window w = mSwitchingDialog.getWindow();
- final LayoutParams attrs = w.getAttributes();
- w.setType(LayoutParams.TYPE_INPUT_METHOD_DIALOG);
- // Use an alternate token for the dialog for that window manager can group the token
- // with other IME windows based on type vs. grouping based on whichever token happens
- // to get selected by the system later on.
- attrs.token = mSwitchingDialogToken;
- attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- attrs.setTitle("Select input method");
- w.setAttributes(attrs);
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- mSwitchingDialog.show();
- }
- }
-
- private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
- private final LayoutInflater mInflater;
- private final int mTextViewResourceId;
- private final List<ImeSubtypeListItem> mItemsList;
- public int mCheckedItem;
- public ImeSubtypeListAdapter(Context context, int textViewResourceId,
- List<ImeSubtypeListItem> itemsList, int checkedItem) {
- super(context, textViewResourceId, itemsList);
-
- mTextViewResourceId = textViewResourceId;
- mItemsList = itemsList;
- mCheckedItem = checkedItem;
- mInflater = context.getSystemService(LayoutInflater.class);
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final View view = convertView != null ? convertView
- : mInflater.inflate(mTextViewResourceId, null);
- if (position < 0 || position >= mItemsList.size()) return view;
- final ImeSubtypeListItem item = mItemsList.get(position);
- final CharSequence imeName = item.mImeName;
- final CharSequence subtypeName = item.mSubtypeName;
- final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
- final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
- if (TextUtils.isEmpty(subtypeName)) {
- firstTextView.setText(imeName);
- secondTextView.setVisibility(View.GONE);
- } else {
- firstTextView.setText(subtypeName);
- secondTextView.setText(imeName);
- secondTextView.setVisibility(View.VISIBLE);
- }
- final RadioButton radioButton =
- (RadioButton)view.findViewById(com.android.internal.R.id.radio);
- radioButton.setChecked(position == mCheckedItem);
- return view;
- }
- }
-
- void hideInputMethodMenu() {
- synchronized (mMethodMap) {
- hideInputMethodMenuLocked();
- }
- }
-
- void hideInputMethodMenuLocked() {
- if (DEBUG) Slog.v(TAG, "Hide switching menu");
-
- if (mSwitchingDialog != null) {
- mSwitchingDialog.dismiss();
- mSwitchingDialog = null;
- mSwitchingDialogTitleView = null;
- }
-
- updateSystemUiLocked(mImeWindowVis, mBackDisposition);
- mDialogBuilder = null;
- mIms = null;
- }
-
// ----------------------------------------------------------------------
/**
@@ -4971,7 +4873,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
+ InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
if (mCurMethodId == null) {
return null;
}
@@ -5010,6 +4912,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mCurrentSubtype;
}
+ @Nullable
+ String getCurrentMethodId() {
+ return mCurMethodId;
+ }
+
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (mMethodMap) {
return getInputMethodListLocked(userId);
@@ -5412,6 +5319,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return mService.handleShellCommandSetInputMethod(this);
case "reset":
return mService.handleShellCommandResetInputMethod(this);
+ case "tracing":
+ int result = ImeTracing.getInstance().onShellCommand(this);
+ boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
+ for (ClientState state : mService.mClients.values()) {
+ if (state != null) {
+ try {
+ state.client.setImeTraceEnabled(isImeTraceEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG,
+ "Error while trying to enable/disable ime "
+ + "trace on client window", e);
+ }
+ }
+ }
+ return result;
default:
getOutPrintWriter().println("Unknown command: " + imeCommand);
return ShellCommandResult.FAILURE;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
new file mode 100644
index 000000000000..7b29e5bcebb8
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -0,0 +1,354 @@
+/*
+ * 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.inputmethod;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
+import static com.android.server.inputmethod.InputMethodManagerService.MSG_HARD_KEYBOARD_SWITCH_CHANGED;
+import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+
+import android.app.ActivityThread;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ArrayAdapter;
+import android.widget.RadioButton;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.List;
+
+/** A controller to show/hide the input method menu */
+@VisibleForTesting(visibility = PACKAGE)
+public class InputMethodMenuController {
+ private static final String TAG = InputMethodMenuController.class.getSimpleName();
+
+ private final InputMethodManagerService mService;
+ private final InputMethodUtils.InputMethodSettings mSettings;
+ private final InputMethodSubtypeSwitchingController mSwitchingController;
+ private final Handler mHandler;
+ private final ArrayMap<String, InputMethodInfo> mMethodMap;
+ private final KeyguardManager mKeyguardManager;
+ private final HardKeyboardListener mHardKeyboardListener;
+ private final WindowManagerInternal mWindowManagerInternal;
+
+ private Context mSettingsContext;
+ private AlertDialog.Builder mDialogBuilder;
+ private AlertDialog mSwitchingDialog;
+ private IBinder mSwitchingDialogToken;
+ private View mSwitchingDialogTitleView;
+ private InputMethodInfo[] mIms;
+ private int[] mSubtypeIds;
+
+ private boolean mShowImeWithHardKeyboard;
+
+ @VisibleForTesting(visibility = PACKAGE)
+ public InputMethodMenuController(InputMethodManagerService service) {
+ mService = service;
+ mSettings = mService.mSettings;
+ mSwitchingController = mService.mSwitchingController;
+ mHandler = mService.mHandler;
+ mMethodMap = mService.mMethodMap;
+ mKeyguardManager = mService.mKeyguardManager;
+ mHardKeyboardListener = new HardKeyboardListener();
+ mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ }
+
+ void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
+ if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
+
+ final boolean isScreenLocked = isScreenLocked();
+
+ final String lastInputMethodId = mSettings.getSelectedInputMethod();
+ int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
+
+ synchronized (mMethodMap) {
+ final List<ImeSubtypeListItem> imList = mSwitchingController
+ .getSortedInputMethodAndSubtypeListLocked(showAuxSubtypes, isScreenLocked);
+ if (imList.isEmpty()) {
+ return;
+ }
+
+ hideInputMethodMenuLocked();
+
+ if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+ final InputMethodSubtype currentSubtype =
+ mService.getCurrentInputMethodSubtypeLocked();
+ if (currentSubtype != null) {
+ final String curMethodId = mService.getCurrentMethodId();
+ final InputMethodInfo currentImi = mMethodMap.get(curMethodId);
+ lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+ currentImi, currentSubtype.hashCode());
+ }
+ }
+
+ final int size = imList.size();
+ mIms = new InputMethodInfo[size];
+ mSubtypeIds = new int[size];
+ int checkedItem = 0;
+ for (int i = 0; i < size; ++i) {
+ final ImeSubtypeListItem item = imList.get(i);
+ mIms[i] = item.mImi;
+ mSubtypeIds[i] = item.mSubtypeId;
+ if (mIms[i].getId().equals(lastInputMethodId)) {
+ int subtypeId = mSubtypeIds[i];
+ if ((subtypeId == NOT_A_SUBTYPE_ID)
+ || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
+ || (subtypeId == lastInputMethodSubtypeId)) {
+ checkedItem = i;
+ }
+ }
+ }
+
+ final Context settingsContext = getSettingsContext(displayId);
+ mDialogBuilder = new AlertDialog.Builder(settingsContext);
+ mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
+
+ final Context dialogContext = mDialogBuilder.getContext();
+ final TypedArray a = dialogContext.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.DialogPreference,
+ com.android.internal.R.attr.alertDialogStyle, 0);
+ final Drawable dialogIcon = a.getDrawable(
+ com.android.internal.R.styleable.DialogPreference_dialogIcon);
+ a.recycle();
+
+ mDialogBuilder.setIcon(dialogIcon);
+
+ final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
+ final View tv = inflater.inflate(
+ com.android.internal.R.layout.input_method_switch_dialog_title, null);
+ mDialogBuilder.setCustomTitle(tv);
+
+ // Setup layout for a toggle switch of the hardware keyboard
+ mSwitchingDialogTitleView = tv;
+ mSwitchingDialogTitleView
+ .findViewById(com.android.internal.R.id.hard_keyboard_section)
+ .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
+ ? View.VISIBLE : View.GONE);
+ final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
+ com.android.internal.R.id.hard_keyboard_switch);
+ hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
+ hardKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ mSettings.setShowImeWithHardKeyboard(isChecked);
+ // Ensure that the input method dialog is dismissed when changing
+ // the hardware keyboard state.
+ hideInputMethodMenu();
+ });
+
+ final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
+ com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
+ final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
+ synchronized (mMethodMap) {
+ if (mIms == null || mIms.length <= which || mSubtypeIds == null
+ || mSubtypeIds.length <= which) {
+ return;
+ }
+ final InputMethodInfo im = mIms[which];
+ int subtypeId = mSubtypeIds[which];
+ adapter.mCheckedItem = which;
+ adapter.notifyDataSetChanged();
+ hideInputMethodMenu();
+ if (im != null) {
+ if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
+ subtypeId = NOT_A_SUBTYPE_ID;
+ }
+ mService.setInputMethodLocked(im.getId(), subtypeId);
+ }
+ }
+ };
+ mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
+
+ mSwitchingDialog = mDialogBuilder.create();
+ mSwitchingDialog.setCanceledOnTouchOutside(true);
+ final Window w = mSwitchingDialog.getWindow();
+ final WindowManager.LayoutParams attrs = w.getAttributes();
+ w.setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+ // Use an alternate token for the dialog for that window manager can group the token
+ // with other IME windows based on type vs. grouping based on whichever token happens
+ // to get selected by the system later on.
+ attrs.token = mSwitchingDialogToken;
+ attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ attrs.setTitle("Select input method");
+ w.setAttributes(attrs);
+ mService.updateSystemUiLocked();
+ mSwitchingDialog.show();
+ }
+ }
+
+ /**
+ * Returns the window context for IME switch dialogs to receive configuration changes.
+ *
+ * This method initializes the window context if it was not initialized. This method also moves
+ * the context to the targeted display if the current display of context is different than
+ * the display specified by {@code displayId}.
+ */
+ @VisibleForTesting
+ public Context getSettingsContext(int displayId) {
+ if (mSettingsContext == null) {
+ final Context systemUiContext = ActivityThread.currentActivityThread()
+ .createSystemUiContext(displayId);
+ final Context windowContext = systemUiContext.createWindowContext(
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */);
+ mSettingsContext = new ContextThemeWrapper(
+ windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
+ mSwitchingDialogToken = mSettingsContext.getActivityToken();
+ }
+ // TODO(b/159767464): register the listener to another display again if window token is not
+ // yet created.
+ if (mSettingsContext.getDisplayId() != displayId) {
+ mWindowManagerInternal.moveWindowTokenToDisplay(mSwitchingDialogToken, displayId);
+ }
+ return mSettingsContext;
+ }
+
+ private boolean isScreenLocked() {
+ return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
+ && mKeyguardManager.isKeyguardSecure();
+ }
+
+ void updateKeyboardFromSettingsLocked() {
+ mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
+ if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
+ && mSwitchingDialog.isShowing()) {
+ final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
+ com.android.internal.R.id.hard_keyboard_switch);
+ hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
+ }
+ }
+
+ void hideInputMethodMenu() {
+ synchronized (mMethodMap) {
+ hideInputMethodMenuLocked();
+ }
+ }
+
+ void hideInputMethodMenuLocked() {
+ if (DEBUG) Slog.v(TAG, "Hide switching menu");
+
+ if (mSwitchingDialog != null) {
+ mSwitchingDialog.dismiss();
+ mSwitchingDialog = null;
+ mSwitchingDialogTitleView = null;
+ }
+
+ mService.updateSystemUiLocked();
+ mDialogBuilder = null;
+ mIms = null;
+ }
+
+ HardKeyboardListener getHardKeyboardListener() {
+ return mHardKeyboardListener;
+ }
+
+ AlertDialog getSwitchingDialogLocked() {
+ return mSwitchingDialog;
+ }
+
+ boolean getShowImeWithHardKeyboard() {
+ return mShowImeWithHardKeyboard;
+ }
+
+ boolean isisInputMethodPickerShownForTestLocked() {
+ if (mSwitchingDialog == null) {
+ return false;
+ }
+ return mSwitchingDialog.isShowing();
+ }
+
+ class HardKeyboardListener implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
+ @Override
+ public void onHardKeyboardStatusChange(boolean available) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
+ available ? 1 : 0));
+ }
+
+ public void handleHardKeyboardStatusChange(boolean available) {
+ if (DEBUG) {
+ Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
+ }
+ synchronized (mMethodMap) {
+ if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
+ && mSwitchingDialog.isShowing()) {
+ mSwitchingDialogTitleView.findViewById(
+ com.android.internal.R.id.hard_keyboard_section).setVisibility(
+ available ? View.VISIBLE : View.GONE);
+ }
+ }
+ }
+ }
+
+ private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
+ private final LayoutInflater mInflater;
+ private final int mTextViewResourceId;
+ private final List<ImeSubtypeListItem> mItemsList;
+ public int mCheckedItem;
+ private ImeSubtypeListAdapter(Context context, int textViewResourceId,
+ List<ImeSubtypeListItem> itemsList, int checkedItem) {
+ super(context, textViewResourceId, itemsList);
+
+ mTextViewResourceId = textViewResourceId;
+ mItemsList = itemsList;
+ mCheckedItem = checkedItem;
+ mInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final View view = convertView != null ? convertView
+ : mInflater.inflate(mTextViewResourceId, null);
+ if (position < 0 || position >= mItemsList.size()) return view;
+ final ImeSubtypeListItem item = mItemsList.get(position);
+ final CharSequence imeName = item.mImeName;
+ final CharSequence subtypeName = item.mSubtypeName;
+ final TextView firstTextView = view.findViewById(android.R.id.text1);
+ final TextView secondTextView = view.findViewById(android.R.id.text2);
+ if (TextUtils.isEmpty(subtypeName)) {
+ firstTextView.setText(imeName);
+ secondTextView.setVisibility(View.GONE);
+ } else {
+ firstTextView.setText(subtypeName);
+ secondTextView.setText(imeName);
+ secondTextView.setVisibility(View.VISIBLE);
+ }
+ final RadioButton radioButton = view.findViewById(com.android.internal.R.id.radio);
+ radioButton.setChecked(position == mCheckedItem);
+ return view;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index b518eb1ab6d0..a6ca25b0e6c1 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1805,5 +1805,16 @@ public final class MultiClientInputMethodManagerService {
mUserDataMap.dump(fd, ipw, args);
}
}
+
+ @BinderThread
+ @Override
+ public void startProtoDump(byte[] clientProtoDump) throws RemoteException {
+ }
+
+ @BinderThread
+ @Override
+ public boolean isImeTraceEnabled() throws RemoteException {
+ return false;
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index 25ef9facb216..c09ade9e075f 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -4,3 +4,4 @@ ogunwale@google.com
yukawa@google.com
tarandeep@google.com
lumark@google.com
+roosa@google.com
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index 56982a81f83b..bd2676e60825 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -222,7 +222,7 @@ public abstract class AbstractLocationProvider {
// we know that we only updated the state, so the listener for the old state is the same as
// the listener for the new state.
if (oldInternalState.listener != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
} finally {
@@ -246,7 +246,7 @@ public abstract class AbstractLocationProvider {
// we know that we only updated the state, so the listener for the old state is the same as
// the listener for the new state.
if (oldInternalState.listener != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
} finally {
@@ -305,7 +305,7 @@ public abstract class AbstractLocationProvider {
protected void reportLocation(Location location) {
Listener listener = mInternalState.get().listener;
if (listener != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// copy location so if provider makes further changes they do not propagate
listener.onReportLocation(new Location(location));
@@ -321,7 +321,7 @@ public abstract class AbstractLocationProvider {
protected void reportLocation(List<Location> locations) {
Listener listener = mInternalState.get().listener;
if (listener != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// copy location so if provider makes further changes they do not propagate
ArrayList<Location> copy = new ArrayList<>(locations.size());
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
index 6f35c8ba1e0a..ecdd429cd0c8 100644
--- a/services/core/java/com/android/server/location/LocationFudger.java
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -111,7 +111,7 @@ public class LocationFudger {
*/
public Location createCoarse(Location fine) {
synchronized (this) {
- if (fine == mCachedFineLocation) {
+ if (fine == mCachedFineLocation || fine == mCachedCoarseLocation) {
return mCachedCoarseLocation;
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 5518f23e31d3..4a54fee76074 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -17,16 +17,18 @@
package com.android.server.location;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.compat.CompatChanges.isChangeEnabled;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PREVENT_PENDING_INTENT_SYSTEM_API_USAGE;
+import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
-import static com.android.server.location.LocationProviderManager.FASTEST_COARSE_INTERVAL_MS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -36,6 +38,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.Intent;
import android.location.Criteria;
@@ -74,7 +77,6 @@ import android.os.WorkSource.WorkChain;
import android.stats.location.LocationStatsEnums;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.location.ProviderProperties;
@@ -82,21 +84,21 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.location.LocationPermissions.PermissionLevel;
-import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
-import com.android.server.location.LocationRequestStatistics.PackageStatistics;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssManagerService;
+import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationAttributionHelper;
+import com.android.server.location.util.LocationEventLog;
import com.android.server.location.util.LocationPermissionsHelper;
import com.android.server.location.util.LocationPowerSaveModeHelper;
import com.android.server.location.util.LocationUsageLogger;
import com.android.server.location.util.ScreenInteractiveHelper;
import com.android.server.location.util.SettingsHelper;
+import com.android.server.location.util.SystemAlarmHelper;
import com.android.server.location.util.SystemAppForegroundHelper;
import com.android.server.location.util.SystemAppOpsHelper;
import com.android.server.location.util.SystemLocationPermissionsHelper;
@@ -112,9 +114,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -216,7 +216,7 @@ public class LocationManagerService extends ILocationManager.Stub {
private final LocalService mLocalService;
private final GeofenceManager mGeofenceManager;
- @Nullable private volatile GnssManagerService mGnssManagerService = null;
+ private volatile @Nullable GnssManagerService mGnssManagerService = null;
private String mComboNlpPackageName;
private String mComboNlpReadyMarker;
@@ -429,6 +429,8 @@ public class LocationManagerService extends ILocationManager.Stub {
Log.d(TAG, "[u" + userId + "] location enabled = " + enabled);
}
+ mInjector.getLocationEventLog().logLocationEnabled(userId, enabled);
+
Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION)
.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
@@ -581,7 +583,7 @@ public class LocationManagerService extends ILocationManager.Stub {
new IllegalArgumentException());
}
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ request = validateLocationRequest(request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -603,7 +605,22 @@ public class LocationManagerService extends ILocationManager.Stub {
// clients in the system process must have an attribution tag set
Preconditions.checkArgument(identity.getPid() != Process.myPid() || attributionTag != null);
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ // pending intents requests may not use system apis because we do not keep track if clients
+ // lose the relevant permissions, and thus should not get the benefit of those apis. its
+ // simplest to ensure these apis are simply never set for pending intent requests. the same
+ // does not apply for listener requests since those will have the process (including the
+ // listener) killed on permission removal
+ boolean usesSystemApi = request.isLowPower()
+ || request.isHiddenFromAppOps()
+ || request.isLocationSettingsIgnored()
+ || !request.getWorkSource().isEmpty();
+ if (usesSystemApi
+ && isChangeEnabled(PREVENT_PENDING_INTENT_SYSTEM_API_USAGE, identity.getUid())) {
+ throw new SecurityException(
+ "PendingIntent location requests may not use system APIs: " + request);
+ }
+
+ request = validateLocationRequest(request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
@@ -612,10 +629,9 @@ public class LocationManagerService extends ILocationManager.Stub {
manager.registerLocationRequest(request, identity, permissionLevel, pendingIntent);
}
- private LocationRequest validateAndSanitizeLocationRequest(LocationRequest request,
- @PermissionLevel int permissionLevel) {
- WorkSource workSource = request.getWorkSource();
- if (workSource != null && !workSource.isEmpty()) {
+ private LocationRequest validateLocationRequest(LocationRequest request,
+ CallerIdentity identity) {
+ if (!request.getWorkSource().isEmpty()) {
mContext.enforceCallingOrSelfPermission(
permission.UPDATE_DEVICE_STATS,
"setting a work source requires " + permission.UPDATE_DEVICE_STATS);
@@ -632,43 +648,39 @@ public class LocationManagerService extends ILocationManager.Stub {
}
LocationRequest.Builder sanitized = new LocationRequest.Builder(request);
- if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE) != PERMISSION_GRANTED) {
- sanitized.setLowPower(false);
- }
- if (permissionLevel < PERMISSION_FINE) {
- switch (request.getQuality()) {
- case LocationRequest.ACCURACY_FINE:
- sanitized.setQuality(LocationRequest.ACCURACY_BLOCK);
- break;
- case LocationRequest.POWER_HIGH:
- sanitized.setQuality(LocationRequest.POWER_LOW);
- break;
- }
- if (request.getIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
- sanitized.setIntervalMillis(FASTEST_COARSE_INTERVAL_MS);
+ if (CompatChanges.isChangeEnabled(LOW_POWER_EXCEPTIONS, Binder.getCallingUid())) {
+ if (request.isLowPower()) {
+ mContext.enforceCallingOrSelfPermission(
+ permission.LOCATION_HARDWARE,
+ "low power request requires " + permission.LOCATION_HARDWARE);
}
- if (request.getMinUpdateIntervalMillis() < FASTEST_COARSE_INTERVAL_MS) {
- sanitized.clearMinUpdateIntervalMillis();
+ } else {
+ if (mContext.checkCallingPermission(permission.LOCATION_HARDWARE)
+ != PERMISSION_GRANTED) {
+ sanitized.setLowPower(false);
}
}
- if (request.getWorkSource() != null) {
- if (request.getWorkSource().isEmpty()) {
- sanitized.setWorkSource(null);
- } else if (request.getWorkSource().getPackageName(0) == null) {
- Log.w(TAG, "received (and ignoring) illegal worksource with no package name");
- sanitized.setWorkSource(null);
- } else {
- List<WorkChain> workChains = request.getWorkSource().getWorkChains();
- if (workChains != null && !workChains.isEmpty() && workChains.get(
- 0).getAttributionTag() == null) {
- Log.w(TAG,
- "received (and ignoring) illegal worksource with no attribution tag");
- sanitized.setWorkSource(null);
- }
+
+ WorkSource workSource = new WorkSource(request.getWorkSource());
+ if (workSource.size() > 0 && workSource.getPackageName(0) == null) {
+ Log.w(TAG, "received (and ignoring) illegal worksource with no package name");
+ workSource.clear();
+ } else {
+ List<WorkChain> workChains = workSource.getWorkChains();
+ if (workChains != null && !workChains.isEmpty()
+ && workChains.get(0).getAttributionTag() == null) {
+ Log.w(TAG,
+ "received (and ignoring) illegal worksource with no attribution tag");
+ workSource.clear();
}
}
+ if (workSource.isEmpty()) {
+ identity.addToWorkSource(workSource);
+ }
+ sanitized.setWorkSource(workSource);
+
return sanitized.build();
}
@@ -702,21 +714,14 @@ public class LocationManagerService extends ILocationManager.Stub {
return null;
}
- Location location = manager.getLastLocation(identity, permissionLevel, false);
-
- // lastly - note app ops
- if (!mInjector.getAppOpsHelper().noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
- identity)) {
- return null;
- }
-
- return location;
+ return manager.getLastLocation(identity, permissionLevel, false);
}
+ @Nullable
@Override
- public void getCurrentLocation(String provider, LocationRequest request,
- ICancellationSignal cancellationTransport, ILocationCallback consumer,
- String packageName, String attributionTag, String listenerId) {
+ public ICancellationSignal getCurrentLocation(String provider, LocationRequest request,
+ ILocationCallback consumer, String packageName, String attributionTag,
+ String listenerId) {
CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag,
listenerId);
int permissionLevel = LocationPermissions.getPermissionLevel(mContext, identity.getUid(),
@@ -727,14 +732,13 @@ public class LocationManagerService extends ILocationManager.Stub {
// clients in the system process must have an attribution tag set
Preconditions.checkState(identity.getPid() != Process.myPid() || attributionTag != null);
- request = validateAndSanitizeLocationRequest(request, permissionLevel);
+ request = validateLocationRequest(request, identity);
LocationProviderManager manager = getLocationProviderManager(provider);
Preconditions.checkArgument(manager != null,
"provider \"" + provider + "\" does not exist");
- manager.getCurrentLocation(request, identity, permissionLevel, cancellationTransport,
- consumer);
+ return manager.getCurrentLocation(request, identity, permissionLevel, consumer);
}
@Override
@@ -745,9 +749,8 @@ public class LocationManagerService extends ILocationManager.Stub {
return null;
}
- // use fine permission level to avoid creating unnecessary coarse locations
Location location = gpsManager.getLastLocationUnsafe(UserHandle.USER_ALL,
- PERMISSION_FINE, false);
+ PERMISSION_FINE, false, Long.MAX_VALUE);
if (location == null) {
return null;
}
@@ -1101,19 +1104,6 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- @NonNull
- public List<LocationRequest> getTestProviderCurrentRequests(String provider) {
- mContext.enforceCallingOrSelfPermission(permission.READ_DEVICE_CONFIG, null);
-
- LocationProviderManager manager = getLocationProviderManager(provider);
- if (manager == null) {
- throw new IllegalArgumentException("provider doesn't exist: " + provider);
- }
-
- return manager.getMockProviderRequests();
- }
-
- @Override
public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
ParcelFileDescriptor err, String[] args) {
return new LocationShellCommand(this).exec(
@@ -1134,9 +1124,8 @@ public class LocationManagerService extends ILocationManager.Stub {
return;
}
- ipw.print("Location Manager State:");
+ ipw.println("Location Manager State:");
ipw.increaseIndent();
- ipw.println("Elapsed Realtime: " + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
ipw.println("User Info:");
ipw.increaseIndent();
@@ -1148,18 +1137,6 @@ public class LocationManagerService extends ILocationManager.Stub {
mInjector.getSettingsHelper().dump(fd, ipw, args);
ipw.decreaseIndent();
- ipw.println("Historical Records by Provider:");
- ipw.increaseIndent();
- TreeMap<PackageProviderKey, PackageStatistics> sorted = new TreeMap<>(
- mInjector.getLocationRequestStatistics().statistics);
- for (Map.Entry<PackageProviderKey, PackageStatistics> entry
- : sorted.entrySet()) {
- ipw.println(entry.getKey() + ": " + entry.getValue());
- }
- ipw.decreaseIndent();
-
- mInjector.getLocationRequestStatistics().history.dump(ipw);
-
synchronized (mLock) {
if (mExtraLocationControllerPackage != null) {
ipw.println(
@@ -1187,6 +1164,13 @@ public class LocationManagerService extends ILocationManager.Stub {
ipw.increaseIndent();
mGeofenceManager.dump(fd, ipw, args);
ipw.decreaseIndent();
+
+ ipw.println("Event Log:");
+ ipw.increaseIndent();
+ for (String log : mInjector.getLocationEventLog()) {
+ ipw.println(log);
+ }
+ ipw.decreaseIndent();
}
private class LocalService extends LocationManagerInternal {
@@ -1248,7 +1232,9 @@ public class LocationManagerService extends ILocationManager.Stub {
private static class SystemInjector implements Injector {
+ private final LocationEventLog mLocationEventLog;
private final UserInfoHelper mUserInfoHelper;
+ private final AlarmHelper mAlarmHelper;
private final SystemAppOpsHelper mAppOpsHelper;
private final SystemLocationPermissionsHelper mLocationPermissionsHelper;
private final SystemSettingsHelper mSettingsHelper;
@@ -1257,20 +1243,21 @@ public class LocationManagerService extends ILocationManager.Stub {
private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLocationUsageLogger;
- private final LocationRequestStatistics mLocationRequestStatistics;
SystemInjector(Context context, UserInfoHelper userInfoHelper) {
+ mLocationEventLog = new LocationEventLog();
mUserInfoHelper = userInfoHelper;
+ mAlarmHelper = new SystemAlarmHelper(context);
mAppOpsHelper = new SystemAppOpsHelper(context);
mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context,
mAppOpsHelper);
mSettingsHelper = new SystemSettingsHelper(context);
mAppForegroundHelper = new SystemAppForegroundHelper(context);
- mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context);
+ mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context,
+ mLocationEventLog);
mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
- mLocationRequestStatistics = new LocationRequestStatistics();
}
void onSystemReady() {
@@ -1288,6 +1275,11 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
+ public AlarmHelper getAlarmHelper() {
+ return mAlarmHelper;
+ }
+
+ @Override
public AppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;
}
@@ -1328,8 +1320,8 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
- public LocationRequestStatistics getLocationRequestStatistics() {
- return mLocationRequestStatistics;
+ public LocationEventLog getLocationEventLog() {
+ return mLocationEventLog;
}
}
}
diff --git a/services/core/java/com/android/server/location/LocationProviderManager.java b/services/core/java/com/android/server/location/LocationProviderManager.java
index 2a5e035e0c72..52065710f38e 100644
--- a/services/core/java/com/android/server/location/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/LocationProviderManager.java
@@ -16,13 +16,14 @@
package com.android.server.location;
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
+import static android.app.compat.CompatChanges.isChangeEnabled;
+import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
import static android.location.LocationManager.FUSED_PROVIDER;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.KEY_LOCATION_CHANGED;
import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
import static android.location.LocationManager.PASSIVE_PROVIDER;
+import static android.location.LocationRequest.PASSIVE_INTERVAL;
import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
@@ -30,17 +31,18 @@ import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF
import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
import static com.android.internal.location.ProviderRequest.EMPTY_REQUEST;
+import static com.android.internal.location.ProviderRequest.INTERVAL_DISABLED;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import static java.lang.Math.max;
import static java.lang.Math.min;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
import android.annotation.Nullable;
-import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -88,11 +90,13 @@ import com.android.server.PendingIntentUtils;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.listeners.ListenerMultiplexer;
import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.util.AlarmHelper;
import com.android.server.location.util.AppForegroundHelper;
import com.android.server.location.util.AppForegroundHelper.AppForegroundListener;
import com.android.server.location.util.AppOpsHelper;
import com.android.server.location.util.Injector;
import com.android.server.location.util.LocationAttributionHelper;
+import com.android.server.location.util.LocationEventLog;
import com.android.server.location.util.LocationPermissionsHelper;
import com.android.server.location.util.LocationPermissionsHelper.LocationPermissionsListener;
import com.android.server.location.util.LocationPowerSaveModeHelper;
@@ -118,12 +122,12 @@ class LocationProviderManager extends
LocationProviderManager.Registration, ProviderRequest> implements
AbstractLocationProvider.Listener {
- // fastest interval at which clients may receive coarse locations
- public static final long FASTEST_COARSE_INTERVAL_MS = 10 * 60 * 1000;
-
private static final String WAKELOCK_TAG = "*location*";
private static final long WAKELOCK_TIMEOUT_MS = 30 * 1000;
+ // fastest interval at which clients may receive coarse locations
+ private static final long MIN_COARSE_INTERVAL_MS = 10 * 60 * 1000;
+
// max interval to be considered "high power" request
private static final long MAX_HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
@@ -133,8 +137,15 @@ class LocationProviderManager extends
// max timeout allowed for getting the current location
private static final long GET_CURRENT_LOCATION_MAX_TIMEOUT_MS = 30 * 1000;
- // max jitter allowed for fastest interval evaluation
- private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 100;
+ // max jitter allowed for min update interval as a percentage of the interval
+ private static final float FASTEST_INTERVAL_JITTER_PERCENTAGE = .10f;
+
+ // max absolute jitter allowed for min update interval evaluation
+ private static final int MAX_FASTEST_INTERVAL_JITTER_MS = 5 * 1000;
+
+ // minimum amount of request delay in order to respect the delay, below this value the request
+ // will just be scheduled immediately
+ private static final long MIN_REQUEST_DELAY_MS = 30 * 1000;
protected interface LocationTransport {
@@ -221,14 +232,14 @@ class LocationProviderManager extends
/**
* Must be implemented to return the location this operation intends to deliver.
*/
+ @Nullable
Location getLocation();
}
protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
LocationTransport, LocationListenerOperation> {
- @PermissionLevel protected final int mPermissionLevel;
- private final WorkSource mWorkSource;
+ private final @PermissionLevel int mPermissionLevel;
// we cache these values because checking/calculating on the fly is more expensive
private boolean mPermitted;
@@ -236,22 +247,17 @@ class LocationProviderManager extends
private LocationRequest mProviderLocationRequest;
private boolean mIsUsingHighPower;
- @Nullable private Location mLastLocation = null;
+ private @Nullable Location mLastLocation = null;
protected Registration(LocationRequest request, CallerIdentity identity,
LocationTransport transport, @PermissionLevel int permissionLevel) {
super(Objects.requireNonNull(request), identity, transport);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
- mPermissionLevel = permissionLevel;
+ Preconditions.checkArgument(!request.getWorkSource().isEmpty());
- if (request.getWorkSource() != null && !request.getWorkSource().isEmpty()) {
- mWorkSource = request.getWorkSource();
- } else {
- mWorkSource = identity.addToWorkSource(null);
- }
-
- mProviderLocationRequest = super.getRequest();
+ mPermissionLevel = permissionLevel;
+ mProviderLocationRequest = request;
}
@GuardedBy("mLock")
@@ -266,6 +272,8 @@ class LocationProviderManager extends
+ getRequest());
}
+ mLocationEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
@@ -285,6 +293,8 @@ class LocationProviderManager extends
onProviderListenerUnregister();
+ mLocationEventLog.logProviderClientUnregistered(mName, getIdentity());
+
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
}
@@ -312,7 +322,7 @@ class LocationProviderManager extends
mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
}
onHighPowerUsageChanged();
- return null;
+ return onProviderListenerActive();
}
@Override
@@ -325,6 +335,22 @@ class LocationProviderManager extends
if (!getRequest().isHiddenFromAppOps()) {
mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
}
+ return onProviderListenerInactive();
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onActive()}.
+ */
+ @GuardedBy("mLock")
+ protected LocationListenerOperation onProviderListenerActive() {
+ return null;
+ }
+
+ /**
+ * Subclasses may override this instead of {@link #onInactive()} ()}.
+ */
+ @GuardedBy("mLock")
+ protected LocationListenerOperation onProviderListenerInactive() {
return null;
}
@@ -333,10 +359,22 @@ class LocationProviderManager extends
return mProviderLocationRequest;
}
+ @GuardedBy("mLock")
+ final void initializeLastLocation(@Nullable Location location) {
+ if (mLastLocation == null) {
+ mLastLocation = location;
+ }
+ }
+
+ @GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
return mLastLocation;
}
+ public @PermissionLevel int getPermissionLevel() {
+ return mPermissionLevel;
+ }
+
public final boolean isForeground() {
return mForeground;
}
@@ -350,10 +388,6 @@ class LocationProviderManager extends
return LocationProviderManager.this;
}
- protected final WorkSource getWorkSource() {
- return mWorkSource;
- }
-
@GuardedBy("mLock")
private void onHighPowerUsageChanged() {
boolean isUsingHighPower = isUsingHighPower();
@@ -465,21 +499,41 @@ class LocationProviderManager extends
}
private LocationRequest calculateProviderLocationRequest() {
- LocationRequest.Builder builder = new LocationRequest.Builder(super.getRequest());
+ LocationRequest baseRequest = super.getRequest();
+ LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
- if (super.getRequest().isLocationSettingsIgnored()) {
+ if (mPermissionLevel < PERMISSION_FINE) {
+ switch (baseRequest.getQuality()) {
+ case LocationRequest.ACCURACY_FINE:
+ builder.setQuality(LocationRequest.ACCURACY_BLOCK);
+ break;
+ case LocationRequest.POWER_HIGH:
+ builder.setQuality(LocationRequest.POWER_LOW);
+ break;
+ }
+ if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
+ }
+ if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ builder.clearMinUpdateIntervalMillis();
+ }
+ }
+
+ boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
+ if (locationSettingsIgnored) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsPackageWhitelist().contains(
getIdentity().getPackageName()) && !mLocationManagerInternal.isProvider(
null, getIdentity())) {
builder.setLocationSettingsIgnored(false);
+ locationSettingsIgnored = false;
}
}
- if (!super.getRequest().isLocationSettingsIgnored() && !isThrottlingExempt()) {
+ if (!locationSettingsIgnored && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
- builder.setIntervalMillis(Math.max(super.getRequest().getIntervalMillis(),
+ builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -534,7 +588,7 @@ class LocationProviderManager extends
}
protected abstract class LocationRegistration extends Registration implements
- AlarmManager.OnAlarmListener, ProviderEnabledListener {
+ OnAlarmListener, ProviderEnabledListener {
private final PowerManager.WakeLock mWakeLock;
@@ -550,7 +604,7 @@ class LocationProviderManager extends
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
mWakeLock.setReferenceCounted(true);
- mWakeLock.setWorkSource(getWorkSource());
+ mWakeLock.setWorkSource(request.getWorkSource());
}
@Override
@@ -561,17 +615,15 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
protected final void onProviderListenerRegister() {
- mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
- SystemClock.elapsedRealtime());
+ long registerTimeMs = SystemClock.elapsedRealtime();
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
- if (mExpirationRealtimeMs < SystemClock.elapsedRealtime()) {
- remove();
+ if (mExpirationRealtimeMs <= registerTimeMs) {
+ onAlarm();
} else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
- 0, this, FgThread.getHandler(), getWorkSource());
+ mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+ getRequest().getWorkSource());
}
// start listening for provider enabled/disabled events
@@ -594,9 +646,7 @@ class LocationProviderManager extends
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.cancel(this);
+ mAlarmHelper.cancel(this);
}
onLocationListenerUnregister();
@@ -614,6 +664,39 @@ class LocationProviderManager extends
@GuardedBy("mLock")
protected void onLocationListenerUnregister() {}
+ @GuardedBy("mLock")
+ @Override
+ protected final LocationListenerOperation onProviderListenerActive() {
+ // a new registration may not get a location immediately, the provider request may be
+ // delayed. therefore we deliver a historical location if available. since delivering an
+ // older location could be considered a breaking change for some applications, we only
+ // do so for apps targeting S+.
+ if (isChangeEnabled(DELIVER_HISTORICAL_LOCATIONS, getIdentity().getUid())) {
+ long maxLocationAgeMs = getRequest().getIntervalMillis();
+ Location lastDeliveredLocation = getLastDeliveredLocation();
+ if (lastDeliveredLocation != null) {
+ // ensure that location is fresher than the last delivered location
+ maxLocationAgeMs = min(maxLocationAgeMs,
+ lastDeliveredLocation.getElapsedRealtimeAgeMillis() - 1);
+ }
+
+ // requests are never delayed less than MIN_REQUEST_DELAY_MS, so it only makes sense
+ // to deliver historical locations to clients with a last location older than that
+ if (maxLocationAgeMs > MIN_REQUEST_DELAY_MS) {
+ Location lastLocation = getLastLocationUnsafe(
+ getIdentity().getUserId(),
+ getPermissionLevel(),
+ getRequest().isLocationSettingsIgnored(),
+ maxLocationAgeMs);
+ if (lastLocation != null) {
+ return acceptLocationChange(lastLocation);
+ }
+ }
+ }
+
+ return null;
+ }
+
@Override
public void onAlarm() {
if (D) {
@@ -624,6 +707,8 @@ class LocationProviderManager extends
synchronized (mLock) {
remove();
+ // no need to remove alarm after it's fired
+ mExpirationRealtimeMs = Long.MAX_VALUE;
}
}
@@ -642,27 +727,17 @@ class LocationProviderManager extends
return null;
}
- Location location;
- switch (mPermissionLevel) {
- case PERMISSION_FINE:
- location = fineLocation;
- break;
- case PERMISSION_COARSE:
- location = mLocationFudger.createCoarse(fineLocation);
- break;
- default:
- // shouldn't be possible to have a client added without location permissions
- throw new AssertionError();
- }
+ Location location = Objects.requireNonNull(
+ getPermittedLocation(fineLocation, getPermissionLevel()));
Location lastDeliveredLocation = getLastDeliveredLocation();
if (lastDeliveredLocation != null) {
// check fastest interval
- long deltaMs = NANOSECONDS.toMillis(
- location.getElapsedRealtimeNanos()
- - lastDeliveredLocation.getElapsedRealtimeNanos());
- if (deltaMs < getRequest().getMinUpdateIntervalMillis()
- - MAX_FASTEST_INTERVAL_JITTER_MS) {
+ long deltaMs = location.getElapsedRealtimeMillis()
+ - lastDeliveredLocation.getElapsedRealtimeMillis();
+ long maxJitterMs = min((long) (FASTEST_INTERVAL_JITTER_PERCENTAGE
+ * getRequest().getIntervalMillis()), MAX_FASTEST_INTERVAL_JITTER_MS);
+ if (deltaMs < getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
return null;
}
@@ -675,7 +750,7 @@ class LocationProviderManager extends
}
// note app ops
- if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()),
getIdentity())) {
if (D) {
Log.w(TAG, "noteOp denied for " + getIdentity());
@@ -710,6 +785,7 @@ class LocationProviderManager extends
listener.deliverOnLocationChanged(deliveryLocation,
location.isFromMockProvider() ? null : mWakeLock::release);
+ mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
}
@Override
@@ -871,7 +947,7 @@ class LocationProviderManager extends
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
- IBinder.DeathRecipient, ProviderEnabledListener, AlarmManager.OnAlarmListener {
+ IBinder.DeathRecipient, OnAlarmListener {
private volatile LocationTransport mTransport;
@@ -883,11 +959,6 @@ class LocationProviderManager extends
mTransport = transport;
}
- @GuardedBy("mLock")
- void deliverLocation(@Nullable Location location) {
- executeSafely(getExecutor(), () -> mTransport, acceptLocationChange(location));
- }
-
@Override
protected void onListenerUnregister() {
mTransport = null;
@@ -902,47 +973,61 @@ class LocationProviderManager extends
remove();
}
- mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(
- SystemClock.elapsedRealtime());
+ long registerTimeMs = SystemClock.elapsedRealtime();
+ mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
// add alarm for expiration
- if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.set(ELAPSED_REALTIME_WAKEUP, mExpirationRealtimeMs, WINDOW_EXACT,
- 0, this, FgThread.getHandler(), getWorkSource());
- }
-
- // if this request is ignoring location settings, then we don't want to immediately fail
- // it if the provider is disabled or becomes disabled.
- if (!getRequest().isLocationSettingsIgnored()) {
- // start listening for provider enabled/disabled events
- addEnabledListener(this);
-
- // if the provider is currently disabled fail immediately
- int userId = getIdentity().getUserId();
- if (!getRequest().isLocationSettingsIgnored() && !isEnabled(userId)) {
- deliverLocation(null);
- }
+ if (mExpirationRealtimeMs <= registerTimeMs) {
+ onAlarm();
+ } else if (mExpirationRealtimeMs < Long.MAX_VALUE) {
+ mAlarmHelper.setDelayedAlarm(mExpirationRealtimeMs - registerTimeMs, this,
+ getRequest().getWorkSource());
}
}
@GuardedBy("mLock")
@Override
protected void onProviderListenerUnregister() {
- // stop listening for provider enabled/disabled events
- removeEnabledListener(this);
-
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
- AlarmManager alarmManager = Objects.requireNonNull(
- mContext.getSystemService(AlarmManager.class));
- alarmManager.cancel(this);
+ mAlarmHelper.cancel(this);
}
((IBinder) getKey()).unlinkToDeath(this, 0);
}
+ @GuardedBy("mLock")
+ @Override
+ protected LocationListenerOperation onProviderListenerActive() {
+ Location lastLocation = getLastLocationUnsafe(
+ getIdentity().getUserId(),
+ getPermissionLevel(),
+ getRequest().isLocationSettingsIgnored(),
+ MAX_CURRENT_LOCATION_AGE_MS);
+ if (lastLocation != null) {
+ return acceptLocationChange(lastLocation);
+ }
+
+ return null;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ protected LocationListenerOperation onProviderListenerInactive() {
+ if (!getRequest().isLocationSettingsIgnored()) {
+ // if we go inactive for any reason, fail immediately
+ return acceptLocationChange(null);
+ }
+
+ return null;
+ }
+
+ void deliverNull() {
+ synchronized (mLock) {
+ executeSafely(getExecutor(), () -> mTransport, acceptLocationChange(null));
+ }
+ }
+
@Override
public void onAlarm() {
if (D) {
@@ -952,7 +1037,10 @@ class LocationProviderManager extends
}
synchronized (mLock) {
- deliverLocation(null);
+ // no need to remove alarm after it's fired
+ mExpirationRealtimeMs = Long.MAX_VALUE;
+
+ deliverNull();
}
}
@@ -964,30 +1052,23 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
+ // check expiration time - alarm is not guaranteed to go off at the right time,
+ // especially for short intervals
+ if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
+ fineLocation = null;
+ }
+
// lastly - note app ops
- Location location;
- if (fineLocation == null) {
- location = null;
- } else if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(mPermissionLevel),
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()),
getIdentity())) {
if (D) {
Log.w(TAG, "noteOp denied for " + getIdentity());
}
- location = null;
- } else {
- switch (mPermissionLevel) {
- case PERMISSION_FINE:
- location = fineLocation;
- break;
- case PERMISSION_COARSE:
- location = mLocationFudger.createCoarse(fineLocation);
- break;
- default:
- // shouldn't be possible to have a client added without location permissions
- throw new AssertionError();
- }
+ fineLocation = null;
}
+ Location location = getPermittedLocation(fineLocation, getPermissionLevel());
+
return new LocationListenerOperation() {
@Override
public Location getLocation() {
@@ -1006,6 +1087,7 @@ class LocationProviderManager extends
// we currently don't hold a wakelock for getCurrentLocation deliveries
try {
listener.deliverOnLocationChanged(deliveryLocation, null);
+ mLocationEventLog.logProviderDeliveredLocation(mName, getIdentity());
} catch (Exception exception) {
if (exception instanceof RemoteException) {
Log.w(TAG, "registration " + this + " failed", exception);
@@ -1022,22 +1104,6 @@ class LocationProviderManager extends
}
@Override
- public void onProviderEnabledChanged(String provider, int userId, boolean enabled) {
- Preconditions.checkState(mName.equals(provider));
-
- if (userId != getIdentity().getUserId()) {
- return;
- }
-
- // if the provider is disabled we give up on current location immediately
- if (!getRequest().isLocationSettingsIgnored() && !enabled) {
- synchronized (mLock) {
- deliverLocation(null);
- }
- }
- }
-
- @Override
public void binderDied() {
try {
if (D) {
@@ -1076,7 +1142,8 @@ class LocationProviderManager extends
protected final LocationManagerInternal mLocationManagerInternal;
protected final SettingsHelper mSettingsHelper;
- protected final UserInfoHelper mUserInfoHelper;
+ protected final UserInfoHelper mUserHelper;
+ protected final AlarmHelper mAlarmHelper;
protected final AppOpsHelper mAppOpsHelper;
protected final LocationPermissionsHelper mLocationPermissionsHelper;
protected final AppForegroundHelper mAppForegroundHelper;
@@ -1085,7 +1152,7 @@ class LocationProviderManager extends
protected final LocationAttributionHelper mLocationAttributionHelper;
protected final LocationUsageLogger mLocationUsageLogger;
protected final LocationFudger mLocationFudger;
- protected final LocationRequestStatistics mLocationRequestStatistics;
+ protected final LocationEventLog mLocationEventLog;
private final UserListener mUserChangedListener = this::onUserChanged;
private final UserSettingChangedListener mLocationEnabledChangedListener =
@@ -1120,6 +1187,9 @@ class LocationProviderManager extends
// acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
+ @GuardedBy("mLock")
+ @Nullable private OnAlarmListener mDelayedRegister;
+
LocationProviderManager(Context context, Injector injector, String name,
@Nullable PassiveLocationProviderManager passiveManager) {
mContext = context;
@@ -1134,7 +1204,8 @@ class LocationProviderManager extends
mLocationManagerInternal = Objects.requireNonNull(
LocalServices.getService(LocationManagerInternal.class));
mSettingsHelper = injector.getSettingsHelper();
- mUserInfoHelper = injector.getUserInfoHelper();
+ mUserHelper = injector.getUserInfoHelper();
+ mAlarmHelper = injector.getAlarmHelper();
mAppOpsHelper = injector.getAppOpsHelper();
mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
mAppForegroundHelper = injector.getAppForegroundHelper();
@@ -1142,7 +1213,7 @@ class LocationProviderManager extends
mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
mLocationAttributionHelper = injector.getLocationAttributionHelper();
mLocationUsageLogger = injector.getLocationUsageLogger();
- mLocationRequestStatistics = injector.getLocationRequestStatistics();
+ mLocationEventLog = injector.getLocationEventLog();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
// initialize last since this lets our reference escape
@@ -1158,10 +1229,10 @@ class LocationProviderManager extends
synchronized (mLock) {
mStarted = true;
- mUserInfoHelper.addListener(mUserChangedListener);
+ mUserHelper.addListener(mUserChangedListener);
mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// initialize enabled state
onUserStarted(UserHandle.USER_ALL);
@@ -1173,20 +1244,19 @@ class LocationProviderManager extends
public void stopManager() {
synchronized (mLock) {
- mUserInfoHelper.removeListener(mUserChangedListener);
+ mUserHelper.removeListener(mUserChangedListener);
mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener);
- // notify and remove all listeners
- long identity = Binder.clearCallingIdentity();
+ mStarted = false;
+
+ final long identity = Binder.clearCallingIdentity();
try {
- onUserStopped(UserHandle.USER_ALL);
+ onEnabledChanged(UserHandle.USER_ALL);
removeRegistrationIf(key -> true);
+ mEnabledListeners.clear();
} finally {
Binder.restoreCallingIdentity(identity);
}
-
- mEnabledListeners.clear();
- mStarted = false;
}
}
@@ -1247,7 +1317,7 @@ class LocationProviderManager extends
synchronized (mLock) {
Preconditions.checkState(mStarted);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mProvider.setRealProvider(provider);
} finally {
@@ -1260,7 +1330,9 @@ class LocationProviderManager extends
synchronized (mLock) {
Preconditions.checkState(mStarted);
- long identity = Binder.clearCallingIdentity();
+ mLocationEventLog.logProviderMocked(mName, provider != null);
+
+ final long identity = Binder.clearCallingIdentity();
try {
mProvider.setMockProvider(provider);
} finally {
@@ -1287,7 +1359,7 @@ class LocationProviderManager extends
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mProvider.setMockProviderAllowed(enabled);
} finally {
@@ -1302,7 +1374,7 @@ class LocationProviderManager extends
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
String locationProvider = location.getProvider();
if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
@@ -1319,16 +1391,6 @@ class LocationProviderManager extends
}
}
- public List<LocationRequest> getMockProviderRequests() {
- synchronized (mLock) {
- if (!mProvider.isMock()) {
- throw new IllegalArgumentException(mName + " provider is not a test provider");
- }
-
- return mProvider.getCurrentRequest().getLocationRequests();
- }
- }
-
@Nullable
public Location getLastLocation(CallerIdentity identity, @PermissionLevel int permissionLevel,
boolean ignoreLocationSettings) {
@@ -1336,41 +1398,54 @@ class LocationProviderManager extends
identity.getPackageName())) {
return null;
}
- if (!mUserInfoHelper.isCurrentUserId(identity.getUserId())) {
- return null;
+ if (!ignoreLocationSettings) {
+ if (!isEnabled(identity.getUserId())) {
+ return null;
+ }
+ if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
+ return null;
+ }
}
- if (!ignoreLocationSettings && !isEnabled(identity.getUserId())) {
+
+ // lastly - note app ops
+ if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel),
+ identity)) {
return null;
}
- Location location = getLastLocationUnsafe(identity.getUserId(), permissionLevel,
- ignoreLocationSettings);
-
- // we don't note op here because we don't know what the client intends to do with the
- // location, the client is responsible for noting if necessary
+ Location location = getPermittedLocation(
+ getLastLocationUnsafe(
+ identity.getUserId(),
+ permissionLevel,
+ ignoreLocationSettings,
+ Long.MAX_VALUE),
+ permissionLevel);
- if (identity.getPid() == Process.myPid() && location != null) {
+ if (location != null && identity.getPid() == Process.myPid()) {
// if delivering to the same process, make a copy of the location first (since
// location is mutable)
- return new Location(location);
- } else {
- return location;
+ location = new Location(location);
}
+
+ return location;
}
/**
* This function does not perform any permissions or safety checks, by calling it you are
- * committing to performing all applicable checks yourself.
+ * committing to performing all applicable checks yourself. This always returns a "fine"
+ * location, even if the permissionLevel is coarse. You are responsible for coarsening the
+ * location if necessary.
*/
@Nullable
public Location getLastLocationUnsafe(int userId, @PermissionLevel int permissionLevel,
- boolean ignoreLocationSettings) {
+ boolean ignoreLocationSettings, long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
+ // find the most recent location across all users
Location lastLocation = null;
- final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+ final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
Location next = getLastLocationUnsafe(runningUserIds[i], permissionLevel,
- ignoreLocationSettings);
+ ignoreLocationSettings, maximumAgeMs);
if (lastLocation == null || (next != null && next.getElapsedRealtimeNanos()
> lastLocation.getElapsedRealtimeNanos())) {
lastLocation = next;
@@ -1381,18 +1456,30 @@ class LocationProviderManager extends
Preconditions.checkArgument(userId >= 0);
+ Location location;
synchronized (mLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
- return null;
+ location = null;
+ } else {
+ location = lastLocation.get(permissionLevel, ignoreLocationSettings);
}
- return lastLocation.get(permissionLevel, ignoreLocationSettings);
}
+
+ if (location == null) {
+ return null;
+ }
+
+ if (location.getElapsedRealtimeAgeMillis() > maximumAgeMs) {
+ return null;
+ }
+
+ return location;
}
public void injectLastLocation(Location location, int userId) {
synchronized (mLock) {
- if (getLastLocationUnsafe(userId, PERMISSION_FINE, false) == null) {
+ if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
}
}
@@ -1400,7 +1487,7 @@ class LocationProviderManager extends
private void setLastLocation(Location location, int userId) {
if (userId == UserHandle.USER_ALL) {
- final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+ final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
setLastLocation(location, runningUserIds[i]);
}
@@ -1416,17 +1503,16 @@ class LocationProviderManager extends
mLastLocations.put(userId, lastLocation);
}
- Location coarseLocation = mLocationFudger.createCoarse(location);
if (isEnabled(userId)) {
- lastLocation.set(location, coarseLocation);
+ lastLocation.set(location);
}
- lastLocation.setBypass(location, coarseLocation);
+ lastLocation.setBypass(location);
}
}
- public void getCurrentLocation(LocationRequest request, CallerIdentity callerIdentity,
- int permissionLevel, ICancellationSignal cancellationTransport,
- ILocationCallback callback) {
+ @Nullable
+ public ICancellationSignal getCurrentLocation(LocationRequest request,
+ CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > GET_CURRENT_LOCATION_MAX_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
.setDurationMillis(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS)
@@ -1436,66 +1522,36 @@ class LocationProviderManager extends
GetCurrentLocationListenerRegistration registration =
new GetCurrentLocationListenerRegistration(
request,
- callerIdentity,
+ identity,
new GetCurrentLocationTransport(callback),
permissionLevel);
synchronized (mLock) {
- if (mSettingsHelper.isLocationPackageBlacklisted(callerIdentity.getUserId(),
- callerIdentity.getPackageName())) {
- registration.deliverLocation(null);
- return;
- }
- if (!mUserInfoHelper.isCurrentUserId(callerIdentity.getUserId())) {
- registration.deliverLocation(null);
- return;
- }
- if (!request.isLocationSettingsIgnored() && !isEnabled(callerIdentity.getUserId())) {
- registration.deliverLocation(null);
- return;
- }
-
- Location lastLocation = getLastLocationUnsafe(callerIdentity.getUserId(),
- permissionLevel, request.isLocationSettingsIgnored());
- if (lastLocation != null) {
- long locationAgeMs = NANOSECONDS.toMillis(
- SystemClock.elapsedRealtimeNanos()
- - lastLocation.getElapsedRealtimeNanos());
- if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) {
- registration.deliverLocation(lastLocation);
- return;
- }
-
- if (!mAppForegroundHelper.isAppForeground(Binder.getCallingUid())
- && locationAgeMs < mSettingsHelper.getBackgroundThrottleIntervalMs()) {
- registration.deliverLocation(null);
- return;
- }
- }
-
- // if last location isn't good enough then we add a location request
- long identity = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
addRegistration(callback.asBinder(), registration);
+ if (!registration.isActive()) {
+ // if the registration never activated, fail it immediately
+ registration.deliverNull();
+ }
} finally {
- Binder.restoreCallingIdentity(identity);
+ Binder.restoreCallingIdentity(ident);
}
}
- CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
- cancellationTransport);
- if (cancellationSignal != null) {
- cancellationSignal.setOnCancelListener(SingleUseCallback.wrap(
- () -> {
- synchronized (mLock) {
- removeRegistration(callback.asBinder(), registration);
- }
- }));
- }
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+ CancellationSignal.fromTransport(cancelTransport)
+ .setOnCancelListener(SingleUseCallback.wrap(
+ () -> {
+ synchronized (mLock) {
+ removeRegistration(callback.asBinder(), registration);
+ }
+ }));
+ return cancelTransport;
}
public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mProvider.sendExtraCommand(uid, pid, command, extras);
} finally {
@@ -1503,36 +1559,36 @@ class LocationProviderManager extends
}
}
- public void registerLocationRequest(LocationRequest request, CallerIdentity callerIdentity,
+ public void registerLocationRequest(LocationRequest request, CallerIdentity identity,
@PermissionLevel int permissionLevel, ILocationListener listener) {
+ LocationListenerRegistration registration = new LocationListenerRegistration(
+ request,
+ identity,
+ new LocationListenerTransport(listener),
+ permissionLevel);
+
synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- addRegistration(
- listener.asBinder(),
- new LocationListenerRegistration(
- request,
- callerIdentity,
- new LocationListenerTransport(listener),
- permissionLevel));
+ addRegistration(listener.asBinder(), registration);
} finally {
- Binder.restoreCallingIdentity(identity);
+ Binder.restoreCallingIdentity(ident);
}
}
}
public void registerLocationRequest(LocationRequest request, CallerIdentity callerIdentity,
@PermissionLevel int permissionLevel, PendingIntent pendingIntent) {
+ LocationPendingIntentRegistration registration = new LocationPendingIntentRegistration(
+ request,
+ callerIdentity,
+ new LocationPendingIntentTransport(mContext, pendingIntent),
+ permissionLevel);
+
synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
- addRegistration(
- pendingIntent,
- new LocationPendingIntentRegistration(
- request,
- callerIdentity,
- new LocationPendingIntentTransport(mContext, pendingIntent),
- permissionLevel));
+ addRegistration(pendingIntent, registration);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1541,7 +1597,7 @@ class LocationProviderManager extends
public void unregisterLocationRequest(ILocationListener listener) {
synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
removeRegistration(listener.asBinder());
} finally {
@@ -1552,7 +1608,7 @@ class LocationProviderManager extends
public void unregisterLocationRequest(PendingIntent pendingIntent) {
synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
removeRegistration(pendingIntent);
} finally {
@@ -1619,13 +1675,16 @@ class LocationProviderManager extends
key instanceof PendingIntent,
/* geofence= */ key instanceof IBinder,
null, registration.isForeground());
+ }
- mLocationRequestStatistics.startRequesting(
- registration.getIdentity().getPackageName(),
- registration.getIdentity().getAttributionTag(),
- mName,
- registration.getRequest().getIntervalMillis(),
- registration.isForeground());
+ @GuardedBy("mLock")
+ @Override
+ protected void onRegistrationReplaced(Object key, Registration oldRegistration,
+ Registration newRegistration) {
+ // by saving the last delivered location state we are able to potentially delay the
+ // resulting provider request longer and save additional power
+ newRegistration.initializeLastLocation(oldRegistration.getLastDeliveredLocation());
+ super.onRegistrationReplaced(key, oldRegistration, newRegistration);
}
@GuardedBy("mLock")
@@ -1635,11 +1694,6 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
- mLocationRequestStatistics.stopRequesting(
- registration.getIdentity().getPackageName(),
- registration.getIdentity().getAttributionTag(),
- mName);
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -1653,21 +1707,68 @@ class LocationProviderManager extends
@GuardedBy("mLock")
@Override
- protected boolean registerWithService(ProviderRequest mergedRequest,
+ protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- mProvider.setRequest(mergedRequest);
- return true;
+ return reregisterWithService(EMPTY_REQUEST, request, registrations);
}
@GuardedBy("mLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
- return registerWithService(newRequest, registrations);
+ if (Build.IS_DEBUGGABLE) {
+ Preconditions.checkState(Thread.holdsLock(mLock));
+ }
+
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
+ // calculate how long the new request should be delayed before sending it off to the
+ // provider, under the assumption that once we send the request off, the provider will
+ // immediately attempt to deliver a new location satisfying that request.
+ long delayMs;
+ if (!oldRequest.isLocationSettingsIgnored() && newRequest.isLocationSettingsIgnored()) {
+ delayMs = 0;
+ } else if (newRequest.getIntervalMillis() > oldRequest.getIntervalMillis()) {
+ // if the interval has increased, tell the provider immediately, so it can save power
+ // (even though technically this could burn extra power in the short term by producing
+ // an extra location - the provider itself is free to detect an increasing interval and
+ // delay its own location)
+ delayMs = 0;
+ } else {
+ delayMs = calculateRequestDelayMillis(newRequest.getIntervalMillis(), registrations);
+ }
+
+ // the delay should never exceed the new interval
+ Preconditions.checkState(delayMs >= 0 && delayMs <= newRequest.getIntervalMillis());
+
+ if (delayMs < MIN_REQUEST_DELAY_MS) {
+ mLocationEventLog.logProviderUpdateRequest(mName, newRequest);
+ mProvider.setRequest(newRequest);
+ } else {
+ if (D) {
+ Log.d(TAG, mName + " provider delaying request update " + newRequest + " by "
+ + TimeUtils.formatDuration(delayMs));
+ }
+
+ mDelayedRegister = new OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ synchronized (mLock) {
+ if (mDelayedRegister == this) {
+ mLocationEventLog.logProviderUpdateRequest(mName, newRequest);
+ mProvider.setRequest(newRequest);
+ mDelayedRegister = null;
+ }
+ }
+ }
+ };
+ mAlarmHelper.setDelayedAlarm(delayMs, mDelayedRegister, newRequest.getWorkSource());
+ }
+
+ return true;
}
@GuardedBy("mLock")
@@ -1677,6 +1778,7 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
+ mLocationEventLog.logProviderUpdateRequest(mName, EMPTY_REQUEST);
mProvider.setRequest(EMPTY_REQUEST);
}
@@ -1697,6 +1799,9 @@ class LocationProviderManager extends
if (!isEnabled(identity.getUserId())) {
return false;
}
+ if (!identity.isSystem() && !mUserHelper.isCurrentUserId(identity.getUserId())) {
+ return false;
+ }
switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
case LOCATION_MODE_FOREGROUND_ONLY:
@@ -1734,42 +1839,46 @@ class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
- ArrayList<Registration> providerRegistrations = new ArrayList<>(registrations.size());
-
- long intervalMs = Long.MAX_VALUE;
+ long intervalMs = INTERVAL_DISABLED;
boolean locationSettingsIgnored = false;
boolean lowPower = true;
ArrayList<LocationRequest> locationRequests = new ArrayList<>(registrations.size());
+
for (Registration registration : registrations) {
- LocationRequest locationRequest = registration.getRequest();
+ LocationRequest request = registration.getRequest();
- // passive requests do not contribute to the provider
- if (locationRequest.getIntervalMillis() == LocationRequest.PASSIVE_INTERVAL) {
+ // passive requests do not contribute to the provider request
+ if (request.getIntervalMillis() == PASSIVE_INTERVAL) {
continue;
}
- providerRegistrations.add(registration);
- intervalMs = min(locationRequest.getIntervalMillis(), intervalMs);
- locationSettingsIgnored |= locationRequest.isLocationSettingsIgnored();
- lowPower &= locationRequest.isLowPower();
- locationRequests.add(locationRequest);
+ intervalMs = min(request.getIntervalMillis(), intervalMs);
+ locationSettingsIgnored |= request.isLocationSettingsIgnored();
+ lowPower &= request.isLowPower();
+ locationRequests.add(request);
+ }
+
+ if (intervalMs == INTERVAL_DISABLED) {
+ return EMPTY_REQUEST;
}
// calculate who to blame for power in a somewhat arbitrary fashion. we pick a threshold
// interval slightly higher that the minimum interval, and spread the blame across all
// contributing registrations under that threshold (since worksource does not allow us to
// represent differing power blame ratios).
+ long thresholdIntervalMs;
+ try {
+ thresholdIntervalMs = Math.multiplyExact(Math.addExact(intervalMs, 1000) / 2, 3);
+ } catch (ArithmeticException e) {
+ // check for and handle overflow by setting to one below the passive interval so passive
+ // requests are automatically skipped
+ thresholdIntervalMs = PASSIVE_INTERVAL - 1;
+ }
+
WorkSource workSource = new WorkSource();
- long thresholdIntervalMs = (intervalMs + 1000) * 3 / 2;
- if (thresholdIntervalMs < 0) {
- // handle overflow by setting to one below the passive interval
- thresholdIntervalMs = Long.MAX_VALUE - 1;
- }
- final int providerRegistrationsSize = providerRegistrations.size();
- for (int i = 0; i < providerRegistrationsSize; i++) {
- Registration registration = providerRegistrations.get(i);
+ for (Registration registration : registrations) {
if (registration.getRequest().getIntervalMillis() <= thresholdIntervalMs) {
- workSource.add(providerRegistrations.get(i).getWorkSource());
+ workSource.add(registration.getRequest().getWorkSource());
}
}
@@ -1782,11 +1891,53 @@ class LocationProviderManager extends
.build();
}
+ @GuardedBy("mLock")
+ protected long calculateRequestDelayMillis(long newIntervalMs,
+ Collection<Registration> registrations) {
+ // calculate the minimum delay across all registrations, ensuring that it is not more than
+ // the requested interval
+ long delayMs = newIntervalMs;
+ for (Registration registration : registrations) {
+ if (delayMs == 0) {
+ break;
+ }
+
+ LocationRequest locationRequest = registration.getRequest();
+ Location last = registration.getLastDeliveredLocation();
+
+ if (last == null && !locationRequest.isLocationSettingsIgnored()) {
+ // if this request has never gotten any location and it's not ignoring location
+ // settings, then we pretend that this request has gotten the last applicable cached
+ // location for our calculations instead. this prevents spammy add/remove behavior
+ last = getLastLocationUnsafe(
+ registration.getIdentity().getUserId(),
+ registration.getPermissionLevel(),
+ false,
+ locationRequest.getIntervalMillis());
+ }
+
+ long registrationDelayMs;
+ if (last == null) {
+ // if this request has never gotten any location then there's no delay
+ registrationDelayMs = 0;
+ } else {
+ // otherwise the delay is the amount of time until the next location is expected
+ registrationDelayMs = max(0,
+ locationRequest.getIntervalMillis() - last.getElapsedRealtimeAgeMillis());
+ }
+
+ delayMs = min(delayMs, registrationDelayMs);
+ }
+
+ return delayMs;
+ }
+
private void onUserChanged(int userId, int change) {
synchronized (mLock) {
switch (change) {
case UserListener.CURRENT_USER_CHANGED:
- onEnabledChanged(userId);
+ updateRegistrations(
+ registration -> registration.getIdentity().getUserId() == userId);
break;
case UserListener.USER_STARTED:
onUserStarted(userId);
@@ -1906,16 +2057,21 @@ class LocationProviderManager extends
return;
}
+ if (mPassiveManager != null) {
+ // don't log location received for passive provider because it's spammy
+ mLocationEventLog.logProviderReceivedLocation(mName);
+ }
+
// update last location
setLastLocation(location, UserHandle.USER_ALL);
+ // attempt listener delivery
+ deliverToListeners(registration -> registration.acceptLocationChange(location));
+
// notify passive provider
if (mPassiveManager != null) {
mPassiveManager.updateLocation(location);
}
-
- // attempt listener delivery
- deliverToListeners(registration -> registration.acceptLocationChange(location));
}
@GuardedBy("mLock")
@@ -1962,13 +2118,10 @@ class LocationProviderManager extends
}
if (userId == UserHandle.USER_ALL) {
- onEnabledChanged(UserHandle.USER_ALL);
mEnabled.clear();
mLastLocations.clear();
} else {
Preconditions.checkArgument(userId >= 0);
-
- onEnabledChanged(userId);
mEnabled.delete(userId);
mLastLocations.remove(userId);
}
@@ -1985,7 +2138,7 @@ class LocationProviderManager extends
// settings for instance) do not support the null user
return;
} else if (userId == UserHandle.USER_ALL) {
- final int[] runningUserIds = mUserInfoHelper.getRunningUserIds();
+ final int[] runningUserIds = mUserHelper.getRunningUserIds();
for (int i = 0; i < runningUserIds.length; i++) {
onEnabledChanged(runningUserIds[i]);
}
@@ -1996,7 +2149,6 @@ class LocationProviderManager extends
boolean enabled = mStarted
&& mProvider.getState().allowed
- && mUserInfoHelper.isCurrentUserId(userId)
&& mSettingsHelper.isLocationEnabled(userId);
int index = mEnabled.indexOfKey(userId);
@@ -2007,8 +2159,12 @@ class LocationProviderManager extends
mEnabled.put(userId, enabled);
- if (D) {
- Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
+ // don't log unknown -> false transitions for brevity
+ if (wasEnabled != null || enabled) {
+ if (D) {
+ Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled);
+ }
+ mLocationEventLog.logProviderEnabled(mName, userId, enabled);
}
// clear last locations if we become disabled
@@ -2048,6 +2204,20 @@ class LocationProviderManager extends
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
+ @Nullable
+ private Location getPermittedLocation(@Nullable Location fineLocation,
+ @PermissionLevel int permissionLevel) {
+ switch (permissionLevel) {
+ case PERMISSION_FINE:
+ return fineLocation;
+ case PERMISSION_COARSE:
+ return fineLocation != null ? mLocationFudger.createCoarse(fineLocation) : null;
+ default:
+ // shouldn't be possible to have a client added without location permissions
+ throw new AssertionError();
+ }
+ }
+
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
synchronized (mLock) {
ipw.print(mName);
@@ -2060,7 +2230,7 @@ class LocationProviderManager extends
super.dump(fd, ipw, args);
- int[] userIds = mUserInfoHelper.getRunningUserIds();
+ int[] userIds = mUserHelper.getRunningUserIds();
for (int userId : userIds) {
if (userIds.length != 1) {
ipw.print("user ");
@@ -2069,7 +2239,7 @@ class LocationProviderManager extends
ipw.increaseIndent();
}
ipw.print("last location=");
- ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false));
+ ipw.println(getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE));
ipw.print("enabled=");
ipw.println(isEnabled(userId));
if (userIds.length != 1) {
@@ -2083,6 +2253,11 @@ class LocationProviderManager extends
ipw.decreaseIndent();
}
+ @Override
+ protected String getServiceState() {
+ return mProvider.getCurrentRequest().toString();
+ }
+
private static class LastLocation {
@Nullable private Location mFineLocation;
@@ -2093,10 +2268,14 @@ class LocationProviderManager extends
public void clearMock() {
if (mFineLocation != null && mFineLocation.isFromMockProvider()) {
mFineLocation = null;
+ }
+ if (mCoarseLocation != null && mCoarseLocation.isFromMockProvider()) {
mCoarseLocation = null;
}
if (mFineBypassLocation != null && mFineBypassLocation.isFromMockProvider()) {
mFineBypassLocation = null;
+ }
+ if (mCoarseBypassLocation != null && mCoarseBypassLocation.isFromMockProvider()) {
mCoarseBypassLocation = null;
}
}
@@ -2127,24 +2306,37 @@ class LocationProviderManager extends
}
}
- public void set(Location location, Location coarseLocation) {
- mFineLocation = location;
- mCoarseLocation = calculateNextCoarse(mCoarseLocation, coarseLocation);
+ public void set(Location location) {
+ mFineLocation = calculateNextFine(mFineLocation, location);
+ mCoarseLocation = calculateNextCoarse(mCoarseLocation, location);
+ }
+
+ public void setBypass(Location location) {
+ mFineBypassLocation = calculateNextFine(mFineBypassLocation, location);
+ mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, location);
}
- public void setBypass(Location location, Location coarseLocation) {
- mFineBypassLocation = location;
- mCoarseBypassLocation = calculateNextCoarse(mCoarseBypassLocation, coarseLocation);
+ private Location calculateNextFine(@Nullable Location oldFine, Location newFine) {
+ if (oldFine == null) {
+ return newFine;
+ }
+
+ // update last fine interval only if more recent
+ if (newFine.getElapsedRealtimeNanos() > oldFine.getElapsedRealtimeNanos()) {
+ return newFine;
+ } else {
+ return oldFine;
+ }
}
private Location calculateNextCoarse(@Nullable Location oldCoarse, Location newCoarse) {
if (oldCoarse == null) {
return newCoarse;
}
+
// update last coarse interval only if enough time has passed
- long timeDeltaMs = NANOSECONDS.toMillis(newCoarse.getElapsedRealtimeNanos())
- - NANOSECONDS.toMillis(oldCoarse.getElapsedRealtimeNanos());
- if (timeDeltaMs > FASTEST_COARSE_INTERVAL_MS) {
+ if (newCoarse.getElapsedRealtimeMillis() - MIN_COARSE_INTERVAL_MS
+ > oldCoarse.getElapsedRealtimeMillis()) {
return newCoarse;
} else {
return oldCoarse;
@@ -2192,7 +2384,7 @@ class LocationProviderManager extends
return;
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
callback.run();
} catch (RuntimeException e) {
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
deleted file mode 100644
index 4984e63de603..000000000000
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/*
- * Copyright 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.location;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.util.IndentingPrintWriter;
-import android.util.Log;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Objects;
-
-/**
- * Holds statistics for location requests (active requests by provider).
- *
- * <p>Must be externally synchronized.
- */
-public class LocationRequestStatistics {
- private static final String TAG = "LocationStats";
-
- // Maps package name and provider to location request statistics.
- public final HashMap<PackageProviderKey, PackageStatistics> statistics
- = new HashMap<PackageProviderKey, PackageStatistics>();
-
- public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
-
- /**
- * Signals that a package has started requesting locations.
- *
- * @param packageName Name of package that has requested locations.
- * @param featureId Feature id associated with the request.
- * @param providerName Name of provider that is requested (e.g. "gps").
- * @param intervalMs The interval that is requested in ms.
- */
- public void startRequesting(String packageName, @Nullable String featureId, String providerName,
- long intervalMs, boolean isForeground) {
- PackageProviderKey key = new PackageProviderKey(packageName, featureId, providerName);
- PackageStatistics stats = statistics.get(key);
- if (stats == null) {
- stats = new PackageStatistics();
- statistics.put(key, stats);
- }
- stats.startRequesting(intervalMs);
- stats.updateForeground(isForeground);
- history.addRequest(packageName, featureId, providerName, intervalMs);
- }
-
- /**
- * Signals that a package has stopped requesting locations.
- *
- * @param packageName Name of package that has stopped requesting locations.
- * @param featureId Feature id associated with the request.
- * @param providerName Provider that is no longer being requested.
- */
- public void stopRequesting(String packageName, @Nullable String featureId,
- String providerName) {
- PackageProviderKey key = new PackageProviderKey(packageName, featureId, providerName);
- PackageStatistics stats = statistics.get(key);
- if (stats != null) {
- stats.stopRequesting();
- }
- history.removeRequest(packageName, featureId, providerName);
- }
-
- /**
- * Signals that a package possibly switched background/foreground.
- *
- * @param packageName Name of package that has stopped requesting locations.
- * @param featureId Feature id associated with the request.
- * @param providerName Provider that is no longer being requested.
- */
- public void updateForeground(String packageName, @Nullable String featureId,
- String providerName, boolean isForeground) {
- PackageProviderKey key = new PackageProviderKey(packageName, featureId, providerName);
- PackageStatistics stats = statistics.get(key);
- if (stats != null) {
- stats.updateForeground(isForeground);
- }
- }
-
- /**
- * A key that holds package, feature id, and provider names.
- */
- public static class PackageProviderKey implements Comparable<PackageProviderKey> {
- /**
- * Name of package requesting location.
- */
- public final String mPackageName;
- /**
- * Feature id associated with the request, which can be used to attribute location access to
- * different parts of the application.
- */
- @Nullable
- public final String mFeatureId;
- /**
- * Name of provider being requested (e.g. "gps").
- */
- public final String mProviderName;
-
- PackageProviderKey(String packageName, @Nullable String featureId, String providerName) {
- this.mPackageName = packageName;
- this.mFeatureId = featureId;
- this.mProviderName = providerName;
- }
-
- @NonNull
- @Override
- public String toString() {
- return mProviderName + ": " + mPackageName
- + (mFeatureId == null ? "" : ": " + mFeatureId);
- }
-
- /**
- * Sort by provider, then package, then feature
- */
- @Override
- public int compareTo(PackageProviderKey other) {
- final int providerCompare = mProviderName.compareTo(other.mProviderName);
- if (providerCompare != 0) {
- return providerCompare;
- }
-
- final int packageCompare = mPackageName.compareTo(other.mPackageName);
- if (packageCompare != 0) {
- return packageCompare;
- }
-
- return Objects.compare(mFeatureId, other.mFeatureId, Comparator
- .nullsFirst(String::compareTo));
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof PackageProviderKey)) {
- return false;
- }
-
- PackageProviderKey otherKey = (PackageProviderKey) other;
- return mPackageName.equals(otherKey.mPackageName)
- && mProviderName.equals(otherKey.mProviderName)
- && Objects.equals(mFeatureId, otherKey.mFeatureId);
- }
-
- @Override
- public int hashCode() {
- int hash = mPackageName.hashCode() + 31 * mProviderName.hashCode();
- if (mFeatureId != null) {
- hash += mFeatureId.hashCode() + 31 * hash;
- }
- return hash;
- }
- }
-
- /**
- * A data structure to hold past requests
- */
- public static class RequestSummaryLimitedHistory {
- @VisibleForTesting
- static final int MAX_SIZE = 100;
-
- final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
-
- /**
- * Append an added location request to the history
- */
- @VisibleForTesting
- void addRequest(String packageName, @Nullable String featureId, String providerName,
- long intervalMs) {
- addRequestSummary(new RequestSummary(packageName, featureId, providerName, intervalMs));
- }
-
- /**
- * Append a removed location request to the history
- */
- @VisibleForTesting
- void removeRequest(String packageName, @Nullable String featureId, String providerName) {
- addRequestSummary(new RequestSummary(
- packageName, featureId, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
- }
-
- private void addRequestSummary(RequestSummary summary) {
- while (mList.size() >= MAX_SIZE) {
- mList.remove(0);
- }
- mList.add(summary);
- }
-
- /**
- * Dump history to a printwriter (for dumpsys location)
- */
- public void dump(IndentingPrintWriter ipw) {
- long systemElapsedOffsetMillis = System.currentTimeMillis()
- - SystemClock.elapsedRealtime();
-
- ipw.println("Last Several Location Requests:");
- ipw.increaseIndent();
-
- for (RequestSummary requestSummary : mList) {
- requestSummary.dump(ipw, systemElapsedOffsetMillis);
- }
-
- ipw.decreaseIndent();
- }
- }
-
- /**
- * A data structure to hold a single request
- */
- static class RequestSummary {
- /**
- * Name of package requesting location.
- */
- private final String mPackageName;
-
- /**
- * Feature id associated with the request for identifying subsystem of an application.
- */
- @Nullable
- private final String mFeatureId;
- /**
- * Name of provider being requested (e.g. "gps").
- */
- private final String mProviderName;
- /**
- * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
- */
- private final long mIntervalMillis;
- /**
- * Elapsed time of request
- */
- private final long mElapsedRealtimeMillis;
-
- /**
- * Placeholder for requested ending (other values indicate request started/changed)
- */
- static final long REQUEST_ENDED_INTERVAL = -1;
-
- RequestSummary(String packageName, @Nullable String featureId, String providerName,
- long intervalMillis) {
- this.mPackageName = packageName;
- this.mFeatureId = featureId;
- this.mProviderName = providerName;
- this.mIntervalMillis = intervalMillis;
- this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
- }
-
- void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
- StringBuilder s = new StringBuilder();
- long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
- s.append("At ").append(TimeUtils.logTimeOfDay(systemTimeMillis)).append(": ")
- .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
- .append(String.format("%7s", mProviderName)).append(" request from ")
- .append(mPackageName);
- if (mFeatureId != null) {
- s.append(" with feature ").append(mFeatureId);
- }
- if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
- s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
- }
- ipw.println(s);
- }
- }
-
- /**
- * Usage statistics for a package/provider pair.
- */
- public static class PackageStatistics {
- // Time when this package first requested location.
- private final long mInitialElapsedTimeMs;
- // Number of active location requests this package currently has.
- private int mNumActiveRequests;
- // Time when this package most recently went from not requesting location to requesting.
- private long mLastActivitationElapsedTimeMs;
- // The fastest interval this package has ever requested.
- private long mFastestIntervalMs;
- // The slowest interval this package has ever requested.
- private long mSlowestIntervalMs;
- // The total time this app has requested location (not including currently running
- // requests).
- private long mTotalDurationMs;
-
- // Time when this package most recently went to foreground, requesting location. 0 means
- // not currently in foreground.
- private long mLastForegroundElapsedTimeMs;
- // The time this app has requested location (not including currently running requests),
- // while in foreground.
- private long mForegroundDurationMs;
-
- // Time when package last went dormant (stopped requesting location)
- private long mLastStopElapsedTimeMs;
-
- private PackageStatistics() {
- mInitialElapsedTimeMs = SystemClock.elapsedRealtime();
- mNumActiveRequests = 0;
- mTotalDurationMs = 0;
- mFastestIntervalMs = Long.MAX_VALUE;
- mSlowestIntervalMs = 0;
- mForegroundDurationMs = 0;
- mLastForegroundElapsedTimeMs = 0;
- mLastStopElapsedTimeMs = 0;
- }
-
- private void startRequesting(long intervalMs) {
- if (mNumActiveRequests == 0) {
- mLastActivitationElapsedTimeMs = SystemClock.elapsedRealtime();
- }
-
- if (intervalMs < mFastestIntervalMs) {
- mFastestIntervalMs = intervalMs;
- }
-
- if (intervalMs > mSlowestIntervalMs) {
- mSlowestIntervalMs = intervalMs;
- }
-
- mNumActiveRequests++;
- }
-
- private void updateForeground(boolean isForeground) {
- long nowElapsedTimeMs = SystemClock.elapsedRealtime();
- // if previous interval was foreground, accumulate before resetting start
- if (mLastForegroundElapsedTimeMs != 0) {
- mForegroundDurationMs += (nowElapsedTimeMs - mLastForegroundElapsedTimeMs);
- }
- mLastForegroundElapsedTimeMs = isForeground ? nowElapsedTimeMs : 0;
- }
-
- private void stopRequesting() {
- if (mNumActiveRequests <= 0) {
- // Shouldn't be a possible code path
- Log.e(TAG, "Reference counting corrupted in usage statistics.");
- return;
- }
-
- mNumActiveRequests--;
- if (mNumActiveRequests == 0) {
- mLastStopElapsedTimeMs = SystemClock.elapsedRealtime();
- long lastDurationMs = mLastStopElapsedTimeMs - mLastActivitationElapsedTimeMs;
- mTotalDurationMs += lastDurationMs;
- updateForeground(false);
- }
- }
-
- /**
- * Returns the duration that this request has been active.
- */
- public long getDurationMs() {
- long currentDurationMs = mTotalDurationMs;
- if (mNumActiveRequests > 0) {
- currentDurationMs
- += SystemClock.elapsedRealtime() - mLastActivitationElapsedTimeMs;
- }
- return currentDurationMs;
- }
-
- /**
- * Returns the duration that this request has been active.
- */
- public long getForegroundDurationMs() {
- long currentDurationMs = mForegroundDurationMs;
- if (mLastForegroundElapsedTimeMs != 0) {
- currentDurationMs
- += SystemClock.elapsedRealtime() - mLastForegroundElapsedTimeMs;
- }
- return currentDurationMs;
- }
-
- /**
- * Returns the time since the initial request in ms.
- */
- public long getTimeSinceFirstRequestMs() {
- return SystemClock.elapsedRealtime() - mInitialElapsedTimeMs;
- }
-
- /**
- * Returns the time since the last request stopped in ms.
- */
- public long getTimeSinceLastRequestStoppedMs() {
- return SystemClock.elapsedRealtime() - mLastStopElapsedTimeMs;
- }
-
- /**
- * Returns the fastest interval that has been tracked.
- */
- public long getFastestIntervalMs() {
- return mFastestIntervalMs;
- }
-
- /**
- * Returns the slowest interval that has been tracked.
- */
- public long getSlowestIntervalMs() {
- return mSlowestIntervalMs;
- }
-
- /**
- * Returns true if a request is active for these tracked statistics.
- */
- public boolean isActive() {
- return mNumActiveRequests > 0;
- }
-
- @Override
- public String toString() {
- StringBuilder s = new StringBuilder();
- if (mFastestIntervalMs == mSlowestIntervalMs) {
- s.append("Interval ").append(mFastestIntervalMs / 1000).append(" seconds");
- } else {
- s.append("Min interval ").append(mFastestIntervalMs / 1000).append(" seconds");
- s.append(": Max interval ").append(mSlowestIntervalMs / 1000).append(" seconds");
- }
- s.append(": Duration requested ")
- .append((getDurationMs() / 1000) / 60)
- .append(" total, ")
- .append((getForegroundDurationMs() / 1000) / 60)
- .append(" foreground, out of the last ")
- .append((getTimeSinceFirstRequestMs() / 1000) / 60)
- .append(" minutes");
- if (isActive()) {
- s.append(": Currently active");
- } else {
- s.append(": Last active ")
- .append((getTimeSinceLastRequestStoppedMs() / 1000) / 60)
- .append(" minutes ago");
- }
- return s.toString();
- }
- }
-}
diff --git a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
index afeb6444c40b..fc10d5fcf1b7 100644
--- a/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/PassiveLocationProviderManager.java
@@ -20,14 +20,12 @@ import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
-import android.location.LocationRequest;
import android.os.Binder;
import com.android.internal.location.ProviderRequest;
import com.android.internal.util.Preconditions;
import com.android.server.location.util.Injector;
-import java.util.ArrayList;
import java.util.Collection;
class PassiveLocationProviderManager extends LocationProviderManager {
@@ -54,7 +52,7 @@ class PassiveLocationProviderManager extends LocationProviderManager {
PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
Preconditions.checkState(passiveProvider != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
passiveProvider.updateLocation(location);
} finally {
@@ -65,17 +63,20 @@ class PassiveLocationProviderManager extends LocationProviderManager {
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
- ProviderRequest.Builder providerRequest = new ProviderRequest.Builder()
- .setIntervalMillis(0);
-
- ArrayList<LocationRequest> requests = new ArrayList<>(registrations.size());
+ boolean locationSettingsIgnored = false;
for (Registration registration : registrations) {
- requests.add(registration.getRequest());
- if (registration.getRequest().isLocationSettingsIgnored()) {
- providerRequest.setLocationSettingsIgnored(true);
- }
+ locationSettingsIgnored |= registration.getRequest().isLocationSettingsIgnored();
}
- return providerRequest.setLocationRequests(requests).build();
+ return new ProviderRequest.Builder()
+ .setIntervalMillis(0)
+ .setLocationSettingsIgnored(locationSettingsIgnored)
+ .build();
+ }
+
+ @Override
+ protected long calculateRequestDelayMillis(long newIntervalMs,
+ Collection<Registration> registrations) {
+ return 0;
}
}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 58e6d593ed2d..c91ee824ff61 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -302,7 +302,7 @@ public class GeofenceManager extends
CallerIdentity callerIdentity = CallerIdentity.fromBinder(mContext, packageName,
attributionTag, AppOpsManager.toReceiverId(pendingIntent));
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
addRegistration(new GeofenceKey(pendingIntent, geofence),
new GeofenceRegistration(geofence, callerIdentity, pendingIntent));
@@ -315,7 +315,7 @@ public class GeofenceManager extends
* Removes the geofence associated with the PendingIntent.
*/
public void removeGeofence(PendingIntent pendingIntent) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
removeRegistrationIf(key -> key.getPendingIntent().equals(pendingIntent));
} finally {
@@ -327,7 +327,7 @@ public class GeofenceManager extends
protected boolean isActive(GeofenceRegistration registration) {
CallerIdentity identity = registration.getIdentity();
return registration.isPermitted()
- && mUserInfoHelper.isCurrentUserId(identity.getUserId())
+ && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
&& mSettingsHelper.isLocationEnabled(identity.getUserId())
&& !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
identity.getPackageName());
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a830e2fe7973..b94f1555cfaa 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -44,7 +44,6 @@ import com.android.server.location.util.SettingsHelper;
import com.android.server.location.util.UserInfoHelper;
import com.android.server.location.util.UserInfoHelper.UserListener;
-import java.io.PrintWriter;
import java.util.Collection;
import java.util.Objects;
@@ -230,7 +229,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
*/
protected void addListener(TRequest request, CallerIdentity callerIdentity,
TListener listener) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
addRegistration(listener.asBinder(),
new GnssListenerRegistration(request, callerIdentity, listener));
@@ -243,7 +242,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
* Removes the given listener.
*/
public void removeListener(TListener listener) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
removeRegistration(listener.asBinder());
} finally {
@@ -260,7 +259,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
CallerIdentity identity = registration.getIdentity();
return registration.isPermitted()
&& (registration.isForeground() || isBackgroundRestrictionExempt(identity))
- && mUserInfoHelper.isCurrentUserId(identity.getUserId())
+ && (identity.isSystem() || mUserInfoHelper.isCurrentUserId(identity.getUserId()))
&& mLocationManagerInternal.isProviderEnabledForUser(GPS_PROVIDER,
identity.getUserId())
&& !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(),
@@ -361,11 +360,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
@Override
- protected void dumpServiceState(PrintWriter pw) {
+ protected String getServiceState() {
if (!isServiceSupported()) {
- pw.print("unsupported");
+ return "unsupported";
} else {
- super.dumpServiceState(pw);
+ return super.getServiceState();
}
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index f879b1929236..e144b403240c 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1120,7 +1120,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
@Override
public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
if ("delete_aiding_data".equals(command)) {
deleteAidingData(extras);
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 87d668a07d70..0318ffb519ba 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -57,6 +57,8 @@ import java.util.function.Predicate;
* <li>{@link #onRegister()}</li>
* <li>{@link ListenerRegistration#onRegister(Object)}</li>
* <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
+ * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
+ * invoked if this registration is replacing a prior registration)</li>
* <li>{@link #onActive()}</li>
* <li>{@link ListenerRegistration#onActive()}</li>
* <li>{@link ListenerRegistration#onInactive()}</li>
@@ -183,6 +185,17 @@ public abstract class ListenerMultiplexer<TKey, TListener,
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
+ * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
+ * registration is replacing an old registration. The old registration will have already been
+ * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
+ * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+ */
+ protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+ @NonNull TRegistration newRegistration) {
+ onRegistrationAdded(key, newRegistration);
+ }
+
+ /**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
@@ -227,9 +240,10 @@ public abstract class ListenerMultiplexer<TKey, TListener,
boolean wasEmpty = mRegistrations.isEmpty();
+ TRegistration oldRegistration = null;
int index = mRegistrations.indexOfKey(key);
if (index >= 0) {
- removeRegistration(index, false);
+ oldRegistration = removeRegistration(index, false);
mRegistrations.setValueAt(index, registration);
} else {
mRegistrations.put(key, registration);
@@ -239,7 +253,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
onRegister();
}
registration.onRegister(key);
- onRegistrationAdded(key, registration);
+ if (oldRegistration == null) {
+ onRegistrationAdded(key, registration);
+ } else {
+ onRegistrationReplaced(key, oldRegistration, registration);
+ }
onRegistrationActiveChanged(registration);
}
}
@@ -320,7 +338,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
@GuardedBy("mRegistrations")
- private void removeRegistration(int index, boolean removeEntry) {
+ private TRegistration removeRegistration(int index, boolean removeEntry) {
if (Build.IS_DEBUGGABLE) {
Preconditions.checkState(Thread.holdsLock(mRegistrations));
}
@@ -347,6 +365,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
}
+
+ return registration;
}
/**
@@ -535,7 +555,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mRegistrations) {
pw.print("service: ");
- dumpServiceState(pw);
+ pw.print(getServiceState());
pw.println();
if (!mRegistrations.isEmpty()) {
@@ -558,18 +578,17 @@ public abstract class ListenerMultiplexer<TKey, TListener,
/**
* May be overridden to provide additional details on service state when dumping the manager
- * state.
+ * state. Invoked while holding the multiplexer's internal lock.
*/
- protected void dumpServiceState(PrintWriter pw) {
+ protected String getServiceState() {
if (mServiceRegistered) {
- pw.print("registered");
if (mMerged != null) {
- pw.print(" [");
- pw.print(mMerged);
- pw.print("]");
+ return mMerged.toString();
+ } else {
+ return "registered";
}
} else {
- pw.print("unregistered");
+ return "unregistered";
}
}
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index 92dabe337d87..43eb3ca128c6 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -18,7 +18,9 @@ package com.android.server.location.timezone;
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.NonNull;
@@ -29,6 +31,7 @@ import android.util.Slog;
import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -77,16 +80,19 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
synchronized (mSharedLock) {
ProviderState currentState = mCurrentState.get();
switch (currentState.stateEnum) {
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN:
+ case PROVIDER_STATE_ENABLED_CERTAIN: {
// Losing a remote provider is treated as becoming uncertain.
String msg = "handleProviderLost reason=" + reason
+ ", mProviderName=" + mProviderName
+ ", currentState=" + currentState;
debugLog(msg);
- // This is an unusual PROVIDER_STATE_ENABLED state because event == null
+ // This is an unusual PROVIDER_STATE_ENABLED_UNCERTAIN state because
+ // event == null
ProviderState newState = currentState.newState(
- PROVIDER_STATE_ENABLED, null, currentState.currentUserConfiguration,
- msg);
+ PROVIDER_STATE_ENABLED_UNCERTAIN, null,
+ currentState.currentUserConfiguration, msg);
setCurrentState(newState, true);
break;
}
@@ -117,7 +123,9 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
synchronized (mSharedLock) {
ProviderState currentState = mCurrentState.get();
switch (currentState.stateEnum) {
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
debugLog("handleOnProviderBound mProviderName=" + mProviderName
+ ", currentState=" + currentState + ": Provider is enabled.");
break;
@@ -142,14 +150,13 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
- void onEnable() {
+ void onEnable(@NonNull Duration initializationTimeout) {
// Set a request on the proxy - it will be sent immediately if the service is bound,
// or will be sent as soon as the service becomes bound.
- // TODO(b/152744911): Decide whether to send a timeout so the provider knows how long
- // it has to generate the first event before it could be bypassed.
LocationTimeZoneProviderRequest request =
new LocationTimeZoneProviderRequest.Builder()
.setReportLocationTimeZone(true)
+ .setInitializationTimeoutMillis(initializationTimeout.toMillis())
.build();
mProxy.setRequest(request);
}
@@ -193,8 +200,8 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
synchronized (mSharedLock) {
return "BinderLocationTimeZoneProvider{"
+ "mProviderName=" + mProviderName
- + "mCurrentState=" + mCurrentState
- + "mProxy=" + mProxy
+ + ", mCurrentState=" + mCurrentState
+ + ", mProxy=" + mProxy
+ '}';
}
}
diff --git a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
index 2e2481c30f22..b1e330668194 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
@@ -22,6 +22,7 @@ import com.android.server.LocalServices;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -30,6 +31,10 @@ import java.util.Objects;
*/
class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
+ private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+ private static final Duration PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+
@NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
@NonNull private final LocationTimeZoneProviderController mController;
@@ -45,7 +50,26 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir
}
@Override
+ @NonNull
ConfigurationInternal getCurrentUserConfigurationInternal() {
return mTimeZoneDetectorInternal.getCurrentUserConfigurationInternal();
}
+
+ @Override
+ @NonNull
+ Duration getProviderInitializationTimeout() {
+ return PROVIDER_INITIALIZATION_TIMEOUT;
+ }
+
+ @Override
+ @NonNull
+ Duration getProviderInitializationTimeoutFuzz() {
+ return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+ }
+
+ @Override
+ @NonNull
+ Duration getUncertaintyDelay() {
+ return PROVIDER_UNCERTAINTY_DELAY;
+ }
}
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
index e31cfc4e8b40..d48263722d38 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/location/timezone/ControllerImpl.java
@@ -24,7 +24,9 @@ import static com.android.server.location.timezone.LocationTimeZoneManagerServic
import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.NonNull;
@@ -33,28 +35,30 @@ import android.location.timezone.LocationTimeZoneEvent;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import java.time.Duration;
+import java.util.List;
import java.util.Objects;
/**
- * A real implementation of {@link LocationTimeZoneProviderController} that supports a single
- * {@link LocationTimeZoneProvider}.
+ * A real implementation of {@link LocationTimeZoneProviderController} that supports a primary and a
+ * secondary {@link LocationTimeZoneProvider}.
*
- * TODO(b/152744911): This implementation currently only supports a single ("primary") provider.
- * Support for a secondary provider will be added in a later commit.
+ * <p>The primary is used until it fails or becomes uncertain. The secondary will then be enabled.
+ * The controller will immediately make suggestions based on "certain" {@link
+ * LocationTimeZoneEvent}s, i.e. events that demonstrate the provider is certain what the time zone
+ * is. The controller will not make immediate suggestions based on "uncertain" events, giving
+ * providers time to change their mind. This also gives the secondary provider time to initialize
+ * when the primary becomes uncertain.
*/
class ControllerImpl extends LocationTimeZoneProviderController {
- @VisibleForTesting
- static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+ @NonNull private final LocationTimeZoneProvider mPrimaryProvider;
- @NonNull private final LocationTimeZoneProvider mProvider;
- @NonNull private final SingleRunnableQueue mDelayedSuggestionQueue;
+ @NonNull private final LocationTimeZoneProvider mSecondaryProvider;
@GuardedBy("mSharedLock")
// Non-null after initialize()
@@ -69,12 +73,11 @@ class ControllerImpl extends LocationTimeZoneProviderController {
private Callback mCallback;
/**
- * Contains any currently pending suggestion on {@link #mDelayedSuggestionQueue}, if there is
- * one.
+ * Used for scheduling uncertainty timeouts, i.e after a provider has reported uncertainty.
+ * This timeout is not provider-specific: it is started when the controller becomes uncertain
+ * due to events it has received from one or other provider.
*/
- @GuardedBy("mSharedLock")
- @Nullable
- private GeolocationTimeZoneSuggestion mPendingSuggestion;
+ @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue;
/** Contains the last suggestion actually made, if there is one. */
@GuardedBy("mSharedLock")
@@ -82,10 +85,12 @@ class ControllerImpl extends LocationTimeZoneProviderController {
private GeolocationTimeZoneSuggestion mLastSuggestion;
ControllerImpl(@NonNull ThreadingDomain threadingDomain,
- @NonNull LocationTimeZoneProvider provider) {
+ @NonNull LocationTimeZoneProvider primaryProvider,
+ @NonNull LocationTimeZoneProvider secondaryProvider) {
super(threadingDomain);
- mDelayedSuggestionQueue = threadingDomain.createSingleRunnableQueue();
- mProvider = Objects.requireNonNull(provider);
+ mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue();
+ mPrimaryProvider = Objects.requireNonNull(primaryProvider);
+ mSecondaryProvider = Objects.requireNonNull(secondaryProvider);
}
@Override
@@ -98,8 +103,13 @@ class ControllerImpl extends LocationTimeZoneProviderController {
mCallback = Objects.requireNonNull(callback);
mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal();
- mProvider.initialize(ControllerImpl.this::onProviderStateChange);
- enableOrDisableProvider(mCurrentUserConfiguration);
+ LocationTimeZoneProvider.ProviderListener providerListener =
+ ControllerImpl.this::onProviderStateChange;
+ mPrimaryProvider.initialize(providerListener);
+ mSecondaryProvider.initialize(providerListener);
+
+ alterProvidersEnabledStateIfRequired(
+ null /* oldConfiguration */, mCurrentUserConfiguration);
}
}
@@ -116,149 +126,281 @@ class ControllerImpl extends LocationTimeZoneProviderController {
if (!newConfig.equals(oldConfig)) {
if (newConfig.getUserId() != oldConfig.getUserId()) {
- // If the user changed, disable the provider if needed. It may be re-enabled for
- // the new user below if their settings allow.
+ // If the user changed, disable the providers if needed. They may be re-enabled
+ // for the new user immediately afterwards if their settings allow.
debugLog("User changed. old=" + oldConfig.getUserId()
- + ", new=" + newConfig.getUserId());
- debugLog("Disabling LocationTimeZoneProviders as needed");
- if (mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED) {
- mProvider.disable();
- }
- }
+ + ", new=" + newConfig.getUserId() + ": Disabling providers");
+ disableProviders();
- enableOrDisableProvider(newConfig);
+ alterProvidersEnabledStateIfRequired(null /* oldConfiguration */, newConfig);
+ } else {
+ alterProvidersEnabledStateIfRequired(oldConfig, newConfig);
+ }
}
}
}
+ @Override
+ boolean isUncertaintyTimeoutSet() {
+ return mUncertaintyTimeoutQueue.hasQueued();
+ }
+
+ @Override
+ long getUncertaintyTimeoutDelayMillis() {
+ return mUncertaintyTimeoutQueue.getQueuedDelayMillis();
+ }
+
@GuardedBy("mSharedLock")
- private void enableOrDisableProvider(@NonNull ConfigurationInternal configuration) {
- ProviderState providerState = mProvider.getCurrentState();
- boolean geoDetectionEnabled = configuration.getGeoDetectionEnabledBehavior();
- boolean providerWasEnabled = providerState.stateEnum == PROVIDER_STATE_ENABLED;
- if (geoDetectionEnabled) {
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_DISABLED: {
- debugLog("Enabling " + mProvider);
- mProvider.enable(configuration);
- break;
- }
- case PROVIDER_STATE_ENABLED: {
- debugLog("No need to enable " + mProvider + ": already enabled");
- break;
- }
- case PROVIDER_STATE_PERM_FAILED: {
- debugLog("Unable to enable " + mProvider + ": it is perm failed");
- break;
- }
- default:
- warnLog("Unknown provider state: " + mProvider);
- break;
+ private void disableProviders() {
+ disableProviderIfEnabled(mPrimaryProvider);
+ disableProviderIfEnabled(mSecondaryProvider);
+
+ // By definition, if both providers are disabled, the controller is uncertain.
+ cancelUncertaintyTimeout();
+ }
+
+ @GuardedBy("mSharedLock")
+ private void disableProviderIfEnabled(@NonNull LocationTimeZoneProvider provider) {
+ if (provider.getCurrentState().isEnabled()) {
+ disableProvider(provider);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void disableProvider(@NonNull LocationTimeZoneProvider provider) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("No need to disable " + provider + ": already disabled");
+ break;
}
- } else {
- switch (providerState.stateEnum) {
- case PROVIDER_STATE_DISABLED: {
- debugLog("No need to disable " + mProvider + ": already enabled");
- break;
- }
- case PROVIDER_STATE_ENABLED: {
- debugLog("Disabling " + mProvider);
- mProvider.disable();
- break;
- }
- case PROVIDER_STATE_PERM_FAILED: {
- debugLog("Unable to disable " + mProvider + ": it is perm failed");
- break;
- }
- default: {
- warnLog("Unknown provider state: " + mProvider);
- break;
- }
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+ debugLog("Disabling " + provider);
+ provider.disable();
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("Unable to disable " + provider + ": it is perm failed");
+ break;
+ }
+ default: {
+ warnLog("Unknown provider state: " + provider);
+ break;
}
}
+ }
+
+ /**
+ * Sets the providers into the correct enabled/disabled state for the {@code newConfiguration}
+ * and, if there is a provider state change, makes any suggestions required to inform the
+ * downstream time zone detection code.
+ *
+ * <p>This is a utility method that exists to avoid duplicated logic for the various cases when
+ * provider enabled / disabled state may need to be set or changed, e.g. during initialization
+ * or when a new configuration has been received.
+ */
+ @GuardedBy("mSharedLock")
+ private void alterProvidersEnabledStateIfRequired(
+ @Nullable ConfigurationInternal oldConfiguration,
+ @NonNull ConfigurationInternal newConfiguration) {
+
+ // Provider enabled / disabled states only need to be changed if geoDetectionEnabled has
+ // changed.
+ boolean oldGeoDetectionEnabled = oldConfiguration != null
+ && oldConfiguration.getGeoDetectionEnabledBehavior();
+ boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
+ if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
+ return;
+ }
- boolean isProviderEnabled =
- mProvider.getCurrentState().stateEnum == PROVIDER_STATE_ENABLED;
-
- if (isProviderEnabled) {
- if (!providerWasEnabled) {
- // When a provider has first been enabled, we allow it some time for it to
- // initialize.
- // This sets up an empty suggestion to trigger if no explicit "certain" or
- // "uncertain" suggestion preempts it within UNCERTAINTY_DELAY. If, for some reason,
- // the provider does provide any events then this scheduled suggestion will ensure
- // the controller makes at least an uncertain suggestion.
- suggestDelayed(createEmptySuggestion(
- "No event received in delay=" + UNCERTAINTY_DELAY), UNCERTAINTY_DELAY);
+ // The check above ensures that the logic below only executes if providers are going from
+ // {enabled *} -> {disabled}, or {disabled} -> {enabled initializing}. If this changes in
+ // future and there could be {enabled *} -> {enabled *} cases, or cases where the provider
+ // can't be assumed to go straight to the {enabled initializing} state, then the logic below
+ // would need to cover extra conditions, for example:
+ // 1) If the primary is in {enabled uncertain}, the secondary should be enabled.
+ // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty
+ // timeout started when the primary entered {enabled uncertain} should be cancelled.
+
+ if (newGeoDetectionEnabled) {
+ // Try to enable the primary provider.
+ tryEnableProvider(mPrimaryProvider, newConfiguration);
+
+ // The secondary should only ever be enabled if the primary now isn't enabled (i.e. it
+ // couldn't become {enabled initializing} because it is {perm failed}).
+ ProviderState newPrimaryState = mPrimaryProvider.getCurrentState();
+ if (!newPrimaryState.isEnabled()) {
+ // If the primary provider is {perm failed} then the controller must try to enable
+ // the secondary.
+ tryEnableProvider(mSecondaryProvider, newConfiguration);
+
+ ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
+ if (!newSecondaryState.isEnabled()) {
+ // If both providers are {perm failed} then the controller immediately
+ // becomes uncertain.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Providers are failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState()
+ + " secondary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
}
} else {
- // Clear any queued suggestions.
- clearDelayedSuggestion();
+ disableProviders();
- // If the provider is now not enabled, and a previous "certain" suggestion has been
- // made, then a new "uncertain" suggestion must be made to indicate the provider no
- // longer has an opinion and will not be sending updates.
+ // There can be an uncertainty timeout set if the controller most recently received
+ // an uncertain event. This is a no-op if there isn't a timeout set.
+ cancelUncertaintyTimeout();
+
+ // If a previous "certain" suggestion has been made, then a new "uncertain"
+ // suggestion must now be made to indicate the controller {does not / no longer has}
+ // an opinion and will not be sending further updates (until at least the config
+ // changes again and providers are re-enabled).
if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) {
- suggestImmediate(createEmptySuggestion(""));
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Provider is disabled:"
+ + " primary=" + mPrimaryProvider.getCurrentState());
+ makeSuggestion(suggestion);
+ }
+ }
+ }
+
+ private void tryEnableProvider(@NonNull LocationTimeZoneProvider provider,
+ @NonNull ConfigurationInternal configuration) {
+ ProviderState providerState = provider.getCurrentState();
+ switch (providerState.stateEnum) {
+ case PROVIDER_STATE_DISABLED: {
+ debugLog("Enabling " + provider);
+ provider.enable(configuration, mEnvironment.getProviderInitializationTimeout(),
+ mEnvironment.getProviderInitializationTimeoutFuzz());
+ break;
+ }
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+ debugLog("No need to enable " + provider + ": already enabled");
+ break;
+ }
+ case PROVIDER_STATE_PERM_FAILED: {
+ debugLog("Unable to enable " + provider + ": it is perm failed");
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Unknown provider state:"
+ + " provider=" + provider);
}
}
}
void onProviderStateChange(@NonNull ProviderState providerState) {
mThreadingDomain.assertCurrentThread();
- assertProviderKnown(providerState.provider);
+ LocationTimeZoneProvider provider = providerState.provider;
+ assertProviderKnown(provider);
synchronized (mSharedLock) {
switch (providerState.stateEnum) {
case PROVIDER_STATE_DISABLED: {
- // This should never happen: entering disabled does not trigger an event.
+ // This should never happen: entering disabled does not trigger a state change.
warnLog("onProviderStateChange: Unexpected state change for disabled provider,"
- + " providerState=" + providerState);
+ + " provider=" + provider);
break;
}
- case PROVIDER_STATE_ENABLED: {
- // Entering enabled does not trigger an event, so this only happens if an event
- // is received while the provider is enabled.
- debugLog("onProviderStateChange: Received notification of an event while"
- + " enabled, providerState=" + providerState);
- providerEnabledProcessEvent(providerState);
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
+ // Entering enabled does not trigger a state change, so this only happens if an
+ // event is received while the provider is enabled.
+ debugLog("onProviderStateChange: Received notification of a state change while"
+ + " enabled, provider=" + provider);
+ handleProviderEnabledStateChange(providerState);
break;
}
case PROVIDER_STATE_PERM_FAILED: {
debugLog("Received notification of permanent failure for"
- + " provider=" + providerState);
- GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
- "provider=" + providerState.provider
- + " permanently failed: " + providerState);
- suggestImmediate(suggestion);
+ + " provider=" + provider);
+ handleProviderFailedStateChange(providerState);
break;
}
default: {
- warnLog("onProviderStateChange: Unexpected providerState=" + providerState);
+ warnLog("onProviderStateChange: Unexpected provider=" + provider);
}
}
}
}
- private void assertProviderKnown(LocationTimeZoneProvider provider) {
- if (provider != mProvider) {
+ private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) {
+ if (provider != mPrimaryProvider && provider != mSecondaryProvider) {
throw new IllegalArgumentException("Unknown provider: " + provider);
}
}
/**
- * Called when a provider has changed state but just moved from a PROVIDER_STATE_ENABLED state
- * to another PROVIDER_STATE_ENABLED state, usually as a result of a new {@link
- * LocationTimeZoneEvent} being received. There are some cases where event can be null.
+ * Called when a provider has reported that it has failed permanently.
*/
- private void providerEnabledProcessEvent(@NonNull ProviderState providerState) {
+ @GuardedBy("mSharedLock")
+ private void handleProviderFailedStateChange(@NonNull ProviderState providerState) {
+ LocationTimeZoneProvider failedProvider = providerState.provider;
+ ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState();
+ ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState();
+
+ // If a provider has failed, the other may need to be enabled.
+ if (failedProvider == mPrimaryProvider) {
+ if (secondaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) {
+ // The primary must have failed. Try to enable the secondary. This does nothing if
+ // the provider is already enabled, and will leave the provider in
+ // {enabled initializing} if the provider is disabled.
+ tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration);
+ }
+ } else if (failedProvider == mSecondaryProvider) {
+ // No-op: The secondary will only be active if the primary is uncertain or is failed.
+ // So, there the primary should not need to be enabled when the secondary fails.
+ if (primaryCurrentState.stateEnum != PROVIDER_STATE_ENABLED_UNCERTAIN
+ && primaryCurrentState.stateEnum != PROVIDER_STATE_PERM_FAILED) {
+ warnLog("Secondary provider unexpected reported a failure:"
+ + " failed provider=" + failedProvider.getName()
+ + ", primary provider=" + mPrimaryProvider
+ + ", secondary provider=" + mSecondaryProvider);
+ }
+ }
+
+ // If both providers are now failed, the controller needs to tell the next component in the
+ // time zone detection process.
+ if (primaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED
+ && secondaryCurrentState.stateEnum == PROVIDER_STATE_PERM_FAILED) {
+
+ // If both providers are newly failed then the controller is uncertain by definition
+ // and it will never recover so it can send a suggestion immediately.
+ cancelUncertaintyTimeout();
+
+ // If both providers are now failed, then a suggestion must be sent informing the time
+ // zone detector that there are no further updates coming in future.
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Both providers are permanently failed:"
+ + " primary=" + primaryCurrentState.provider
+ + ", secondary=" + secondaryCurrentState.provider);
+ makeSuggestion(suggestion);
+ }
+ }
+
+ /**
+ * Called when a provider has changed state but just moved from one enabled state to another
+ * enabled state, usually as a result of a new {@link LocationTimeZoneEvent} being received.
+ * However, there are rare cases where the event can also be null.
+ */
+ @GuardedBy("mSharedLock")
+ private void handleProviderEnabledStateChange(@NonNull ProviderState providerState) {
+ LocationTimeZoneProvider provider = providerState.provider;
LocationTimeZoneEvent event = providerState.event;
if (event == null) {
// Implicit uncertainty, i.e. where the provider is enabled, but a problem has been
// detected without having received an event. For example, if the process has detected
- // the loss of a binder-based provider. This is treated like explicit uncertainty, i.e.
- // where the provider has explicitly told this process it is uncertain.
- scheduleUncertainSuggestionIfNeeded(null);
+ // the loss of a binder-based provider, or initialization took too long. This is treated
+ // the same as explicit uncertainty, i.e. where the provider has explicitly told this
+ // process it is uncertain.
+ handleProviderUncertainty(provider, "provider=" + provider
+ + ", implicit uncertainty, event=null");
return;
}
@@ -272,28 +414,26 @@ class ControllerImpl extends LocationTimeZoneProviderController {
if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) {
// This should not happen: the provider should not be in an enabled state if the user
// does not have geodetection enabled.
- warnLog("Provider=" + providerState + " is enabled, but currentUserConfiguration="
- + mCurrentUserConfiguration + " suggests it shouldn't be.");
+ warnLog("Provider=" + provider + " is enabled, but"
+ + " currentUserConfiguration=" + mCurrentUserConfiguration
+ + " suggests it shouldn't be.");
}
switch (event.getEventType()) {
case EVENT_TYPE_PERMANENT_FAILURE: {
- // This shouldn't happen. Providers cannot be enabled and have this event.
- warnLog("Provider=" + providerState
+ // This shouldn't happen. A provider cannot be enabled and have this event type.
+ warnLog("Provider=" + provider
+ " is enabled, but event suggests it shouldn't be");
break;
}
case EVENT_TYPE_UNCERTAIN: {
- scheduleUncertainSuggestionIfNeeded(event);
+ handleProviderUncertainty(provider, "provider=" + provider
+ + ", explicit uncertainty. event=" + event);
break;
}
case EVENT_TYPE_SUCCESS: {
- GeolocationTimeZoneSuggestion suggestion =
- new GeolocationTimeZoneSuggestion(event.getTimeZoneIds());
- suggestion.addDebugInfo("Event received provider=" + mProvider.getName()
- + ", event=" + event);
- // Rely on the receiver to dedupe events. It is better to over-communicate.
- suggestImmediate(suggestion);
+ handleProviderCertainty(provider, event.getTimeZoneIds(),
+ "Event received provider=" + provider + ", event=" + event);
break;
}
default: {
@@ -304,27 +444,25 @@ class ControllerImpl extends LocationTimeZoneProviderController {
}
/**
- * Indicates a provider has become uncertain with the event (if any) received that indicates
- * that.
- *
- * <p>Providers are expected to report their uncertainty as soon as they become uncertain, as
- * this enables the most flexibility for the controller to enable other providers when there are
- * multiple ones. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion.
- *
- * <p>This method schedules an "uncertain" suggestion (if one isn't already scheduled) to be
- * made later if nothing else preempts it. It can be preempted if the provider becomes certain
- * (or does anything else that calls {@link #suggestImmediate(GeolocationTimeZoneSuggestion)})
- * within UNCERTAINTY_DELAY. Preemption causes the scheduled "uncertain" event to be cancelled.
- * If the provider repeatedly sends uncertainty events within UNCERTAINTY_DELAY, those events
- * are effectively ignored (i.e. the timer is not reset each time).
+ * Called when a provider has become "certain" about the time zone(s).
*/
- private void scheduleUncertainSuggestionIfNeeded(@Nullable LocationTimeZoneEvent event) {
- if (mPendingSuggestion == null || mPendingSuggestion.getZoneIds() != null) {
- GeolocationTimeZoneSuggestion suggestion = createEmptySuggestion(
- "provider=" + mProvider + " became uncertain, event=" + event);
- suggestDelayed(suggestion, UNCERTAINTY_DELAY);
+ @GuardedBy("mSharedLock")
+ private void handleProviderCertainty(
+ @NonNull LocationTimeZoneProvider provider,
+ @Nullable List<String> timeZoneIds,
+ @NonNull String reason) {
+ // By definition, the controller is now certain.
+ cancelUncertaintyTimeout();
+
+ if (provider == mPrimaryProvider) {
+ disableProviderIfEnabled(mSecondaryProvider);
}
+
+ GeolocationTimeZoneSuggestion suggestion =
+ new GeolocationTimeZoneSuggestion(timeZoneIds);
+ suggestion.addDebugInfo(reason);
+ // Rely on the receiver to dedupe suggestions. It is better to over-communicate.
+ makeSuggestion(suggestion);
}
@Override
@@ -334,66 +472,95 @@ class ControllerImpl extends LocationTimeZoneProviderController {
ipw.increaseIndent(); // level 1
ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration);
- ipw.println("mPendingSuggestion=" + mPendingSuggestion);
+ ipw.println("providerInitializationTimeout="
+ + mEnvironment.getProviderInitializationTimeout());
+ ipw.println("providerInitializationTimeoutFuzz="
+ + mEnvironment.getProviderInitializationTimeoutFuzz());
+ ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
ipw.println("mLastSuggestion=" + mLastSuggestion);
- ipw.println("Provider:");
+ ipw.println("Primary Provider:");
+ ipw.increaseIndent(); // level 2
+ mPrimaryProvider.dump(ipw, args);
+ ipw.decreaseIndent(); // level 2
+
+ ipw.println("Secondary Provider:");
ipw.increaseIndent(); // level 2
- mProvider.dump(ipw, args);
+ mSecondaryProvider.dump(ipw, args);
ipw.decreaseIndent(); // level 2
ipw.decreaseIndent(); // level 1
}
}
- /** Sends an immediate suggestion, cancelling any pending suggestion. */
+ /** Sends an immediate suggestion, updating mLastSuggestion. */
@GuardedBy("mSharedLock")
- private void suggestImmediate(@NonNull GeolocationTimeZoneSuggestion suggestion) {
- debugLog("suggestImmediate: Executing suggestion=" + suggestion);
- mDelayedSuggestionQueue.runSynchronously(() -> mCallback.suggest(suggestion));
- mPendingSuggestion = null;
+ private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ debugLog("makeSuggestion: suggestion=" + suggestion);
+ mCallback.suggest(suggestion);
mLastSuggestion = suggestion;
}
- /** Clears any pending suggestion. */
+ /** Clears the uncertainty timeout. */
@GuardedBy("mSharedLock")
- private void clearDelayedSuggestion() {
- mDelayedSuggestionQueue.cancel();
- mPendingSuggestion = null;
+ private void cancelUncertaintyTimeout() {
+ mUncertaintyTimeoutQueue.cancel();
}
-
/**
- * Schedules a delayed suggestion. There can only be one delayed suggestion at a time.
- * If there is a pending scheduled suggestion equal to the one passed, it will not be replaced.
- * Replacing a previous delayed suggestion has the effect of cancelling the timeout associated
- * with that previous suggestion.
+ * Called when a provider has become "uncertain" about the time zone.
+ *
+ * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
+ * this enables the most flexibility for the controller to enable other providers when there are
+ * multiple ones available. The controller is therefore responsible for deciding when to make a
+ * "uncertain" suggestion to the downstream time zone detector.
+ *
+ * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
+ * triggered later if nothing else preempts it. It can be preempted if the provider becomes
+ * certain (or does anything else that calls {@link
+ * #makeSuggestion(GeolocationTimeZoneSuggestion)}) within {@link
+ * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
+ * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
+ * is not reset each time).
*/
@GuardedBy("mSharedLock")
- private void suggestDelayed(@NonNull GeolocationTimeZoneSuggestion suggestion,
- @NonNull Duration delay) {
- Objects.requireNonNull(suggestion);
- Objects.requireNonNull(delay);
-
- if (Objects.equals(mPendingSuggestion, suggestion)) {
- // Do not reset the timer.
- debugLog("suggestDelayed: Suggestion=" + suggestion + " is equal to existing."
- + " Not scheduled.");
- return;
+ void handleProviderUncertainty(
+ @NonNull LocationTimeZoneProvider provider, @NonNull String reason) {
+ Objects.requireNonNull(provider);
+
+ // Start the uncertainty timeout if needed to ensure the controller will eventually make an
+ // uncertain suggestion if no success event arrives in time to counteract it.
+ if (!mUncertaintyTimeoutQueue.hasQueued()) {
+ debugLog("Starting uncertainty timeout: reason=" + reason);
+
+ Duration delay = mEnvironment.getUncertaintyDelay();
+ mUncertaintyTimeoutQueue.runDelayed(() -> onProviderUncertaintyTimeout(provider),
+ delay.toMillis());
+ }
+
+ if (provider == mPrimaryProvider) {
+ // (Try to) enable the secondary. It could already be enabled, or enabling might not
+ // succeed if the provider has previously reported it is perm failed. The uncertainty
+ // timeout (set above) is used to ensure that an uncertain suggestion will be made if
+ // the secondary cannot generate a success event in time.
+ tryEnableProvider(mSecondaryProvider, mCurrentUserConfiguration);
}
+ }
- debugLog("suggestDelayed: Scheduling suggestion=" + suggestion);
- mPendingSuggestion = suggestion;
+ private void onProviderUncertaintyTimeout(@NonNull LocationTimeZoneProvider provider) {
+ mThreadingDomain.assertCurrentThread();
- mDelayedSuggestionQueue.runDelayed(() -> {
- debugLog("suggestDelayed: Executing suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mPendingSuggestion = null;
- mLastSuggestion = suggestion;
- }, delay.toMillis());
+ synchronized (mSharedLock) {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
+ "Uncertainty timeout triggered for " + provider.getName() + ":"
+ + " primary=" + mPrimaryProvider
+ + ", secondary=" + mSecondaryProvider);
+ makeSuggestion(suggestion);
+ }
}
- private static GeolocationTimeZoneSuggestion createEmptySuggestion(String reason) {
+ private static GeolocationTimeZoneSuggestion createUncertainSuggestion(@NonNull String reason) {
GeolocationTimeZoneSuggestion suggestion = new GeolocationTimeZoneSuggestion(null);
suggestion.addDebugInfo(reason);
return suggestion;
@@ -403,18 +570,25 @@ class ControllerImpl extends LocationTimeZoneProviderController {
* Asynchronously passes a {@link SimulatedBinderProviderEvent] to the appropriate provider.
* If the provider name does not match a known provider, then the event is logged and discarded.
*/
- void simulateBinderProviderEvent(SimulatedBinderProviderEvent event) {
- if (!Objects.equals(mProvider.getName(), event.getProviderName())) {
+ void simulateBinderProviderEvent(@NonNull SimulatedBinderProviderEvent event) {
+ String targetProviderName = event.getProviderName();
+ LocationTimeZoneProvider targetProvider;
+ if (Objects.equals(mPrimaryProvider.getName(), targetProviderName)) {
+ targetProvider = mPrimaryProvider;
+ } else if (Objects.equals(mSecondaryProvider.getName(), targetProviderName)) {
+ targetProvider = mSecondaryProvider;
+ } else {
warnLog("Unable to process simulated binder provider event,"
+ " unknown providerName in event=" + event);
return;
}
- if (!(mProvider instanceof BinderLocationTimeZoneProvider)) {
+ if (!(targetProvider instanceof BinderLocationTimeZoneProvider)) {
warnLog("Unable to process simulated binder provider event,"
- + " provider is not a " + BinderLocationTimeZoneProvider.class
+ + " provider=" + targetProvider
+ + " is not a " + BinderLocationTimeZoneProvider.class
+ ", event=" + event);
return;
}
- ((BinderLocationTimeZoneProvider) mProvider).simulateBinderProviderEvent(event);
+ ((BinderLocationTimeZoneProvider) targetProvider).simulateBinderProviderEvent(event);
}
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index 238f999ff8a6..a817759c9783 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -44,8 +44,8 @@ import java.util.Objects;
* are made to the {@link TimeZoneDetectorInternal}, and the {@link LocationTimeZoneProvider}s that
* offer {@link android.location.timezone.LocationTimeZoneEvent}s.
*
- * TODO(b/152744911): This implementation currently only supports a primary provider. Support for a
- * secondary provider must be added in a later commit.
+ * <p>For details of the time zone suggestion behavior, see {@link
+ * LocationTimeZoneProviderController}.
*
* <p>Implementation details:
*
@@ -109,6 +109,7 @@ public class LocationTimeZoneManagerService extends Binder {
static final String TAG = "LocationTZDetector";
static final String PRIMARY_PROVIDER_NAME = "primary";
+ static final String SECONDARY_PROVIDER_NAME = "secondary";
private static final String SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX =
"persist.sys.location_tz_simulation_mode.";
@@ -117,6 +118,8 @@ public class LocationTimeZoneManagerService extends Binder {
private static final String PRIMARY_LOCATION_TIME_ZONE_SERVICE_ACTION =
"com.android.location.timezone.service.v1.PrimaryLocationTimeZoneProvider";
+ private static final String SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION =
+ "com.android.location.timezone.service.v1.SecondaryLocationTimeZoneProvider";
@NonNull private final Context mContext;
@@ -160,7 +163,9 @@ public class LocationTimeZoneManagerService extends Binder {
// Called on an arbitrary thread during initialization.
synchronized (mSharedLock) {
LocationTimeZoneProvider primary = createPrimaryProvider();
- mLocationTimeZoneDetectorController = new ControllerImpl(mThreadingDomain, primary);
+ LocationTimeZoneProvider secondary = createSecondaryProvider();
+ mLocationTimeZoneDetectorController =
+ new ControllerImpl(mThreadingDomain, primary, secondary);
ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
mThreadingDomain, mLocationTimeZoneDetectorController);
@@ -177,9 +182,6 @@ public class LocationTimeZoneManagerService extends Binder {
if (isInSimulationMode(PRIMARY_PROVIDER_NAME)) {
proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
} else {
- // TODO Uncomment this code in a later commit.
- throw new UnsupportedOperationException("Not implemented");
- /*
proxy = RealLocationTimeZoneProviderProxy.createAndRegister(
mContext,
mThreadingDomain,
@@ -187,11 +189,27 @@ public class LocationTimeZoneManagerService extends Binder {
com.android.internal.R.bool.config_enablePrimaryLocationTimeZoneOverlay,
com.android.internal.R.string.config_primaryLocationTimeZoneProviderPackageName
);
- */
}
return createLocationTimeZoneProvider(PRIMARY_PROVIDER_NAME, proxy);
}
+ private LocationTimeZoneProvider createSecondaryProvider() {
+ LocationTimeZoneProviderProxy proxy;
+ if (isInSimulationMode(SECONDARY_PROVIDER_NAME)) {
+ proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
+ } else {
+ proxy = RealLocationTimeZoneProviderProxy.createAndRegister(
+ mContext,
+ mThreadingDomain,
+ SECONDARY_LOCATION_TIME_ZONE_SERVICE_ACTION,
+ com.android.internal.R.bool.config_enableSecondaryLocationTimeZoneOverlay,
+ com.android.internal.R.string
+ .config_secondaryLocationTimeZoneProviderPackageName
+ );
+ }
+ return createLocationTimeZoneProvider(SECONDARY_PROVIDER_NAME, proxy);
+ }
+
private boolean isInSimulationMode(String providerName) {
return SystemProperties.getBoolean(
SIMULATION_MODE_SYSTEM_PROPERTY_PREFIX + providerName, false);
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 3743779b20c9..4bbda43d0e60 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -22,7 +22,9 @@ import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTA
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import android.annotation.IntDef;
@@ -33,10 +35,14 @@ import android.os.Handler;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.ReferenceWithHistory;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -72,8 +78,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
*/
static class ProviderState {
- @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED, PROVIDER_STATE_DISABLED,
- PROVIDER_STATE_PERM_FAILED })
+ @IntDef({ PROVIDER_STATE_UNKNOWN, PROVIDER_STATE_ENABLED_INITIALIZING,
+ PROVIDER_STATE_ENABLED_CERTAIN, PROVIDER_STATE_ENABLED_UNCERTAIN,
+ PROVIDER_STATE_DISABLED, PROVIDER_STATE_PERM_FAILED })
@interface ProviderStateEnum {}
/**
@@ -82,22 +89,33 @@ abstract class LocationTimeZoneProvider implements Dumpable {
static final int PROVIDER_STATE_UNKNOWN = 0;
/**
- * The provider is currently enabled.
+ * The provider is enabled and has not reported its first event.
*/
- static final int PROVIDER_STATE_ENABLED = 1;
+ static final int PROVIDER_STATE_ENABLED_INITIALIZING = 1;
/**
- * The provider is currently disabled.
+ * The provider is enabled and most recently reported a "success" event.
+ */
+ static final int PROVIDER_STATE_ENABLED_CERTAIN = 2;
+
+ /**
+ * The provider is enabled and most recently reported an "uncertain" event.
+ */
+ static final int PROVIDER_STATE_ENABLED_UNCERTAIN = 3;
+
+ /**
+ * The provider is disabled.
+ *
* This is the state after {@link #initialize} is called.
*/
- static final int PROVIDER_STATE_DISABLED = 2;
+ static final int PROVIDER_STATE_DISABLED = 4;
/**
* The provider has failed and cannot be re-enabled.
*
* Providers may enter this state after a provider is enabled.
*/
- static final int PROVIDER_STATE_PERM_FAILED = 3;
+ static final int PROVIDER_STATE_PERM_FAILED = 5;
/** The {@link LocationTimeZoneProvider} the state is for. */
public final @NonNull LocationTimeZoneProvider provider;
@@ -107,14 +125,15 @@ abstract class LocationTimeZoneProvider implements Dumpable {
/**
* The last {@link LocationTimeZoneEvent} received. Only populated when {@link #stateEnum}
- * is {@link #PROVIDER_STATE_ENABLED}, but it can be {@code null} then too if no event has
+ * is either {@link #PROVIDER_STATE_ENABLED_CERTAIN} or {@link
+ * #PROVIDER_STATE_ENABLED_UNCERTAIN}, but it can be {@code null} then too if no event has
* yet been received.
*/
@Nullable public final LocationTimeZoneEvent event;
/**
* The user configuration associated with the current state. Only and always present when
- * {@link #stateEnum} is {@link #PROVIDER_STATE_ENABLED}.
+ * {@link #stateEnum} is one of the enabled states.
*/
@Nullable public final ConfigurationInternal currentUserConfiguration;
@@ -132,7 +151,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
private ProviderState(@NonNull LocationTimeZoneProvider provider,
- @ProviderStateEnum int stateEnum, @Nullable LocationTimeZoneEvent event,
+ @ProviderStateEnum int stateEnum,
+ @Nullable LocationTimeZoneEvent event,
@Nullable ConfigurationInternal currentUserConfiguration,
@Nullable String debugInfo) {
this.provider = Objects.requireNonNull(provider);
@@ -171,7 +191,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
break;
}
case PROVIDER_STATE_DISABLED:
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
// These can go to each other or PROVIDER_STATE_PERM_FAILED.
break;
}
@@ -199,7 +221,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
}
break;
}
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
if (currentUserConfig == null) {
throw new IllegalArgumentException(
"Enabled state: currentUserConfig must not be null");
@@ -222,9 +246,18 @@ abstract class LocationTimeZoneProvider implements Dumpable {
return new ProviderState(provider, newStateEnum, event, currentUserConfig, debugInfo);
}
+ /** Returns {@code true} if {@link #stateEnum} is one of the enabled states. */
+ boolean isEnabled() {
+ return stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING
+ || stateEnum == PROVIDER_STATE_ENABLED_CERTAIN
+ || stateEnum == PROVIDER_STATE_ENABLED_UNCERTAIN;
+ }
+
@Override
public String toString() {
- return "State{"
+ // this.provider is omitted deliberately to avoid recursion, since the provider holds
+ // a reference to its state.
+ return "ProviderState{"
+ "stateEnum=" + prettyPrintStateEnum(stateEnum)
+ ", event=" + event
+ ", currentUserConfiguration=" + currentUserConfiguration
@@ -256,8 +289,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
switch (state) {
case PROVIDER_STATE_DISABLED:
return "Disabled (" + PROVIDER_STATE_DISABLED + ")";
- case PROVIDER_STATE_ENABLED:
- return "Enabled (" + PROVIDER_STATE_ENABLED + ")";
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ return "Enabled initializing (" + PROVIDER_STATE_ENABLED_INITIALIZING + ")";
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ return "Enabled certain (" + PROVIDER_STATE_ENABLED_CERTAIN + ")";
+ case PROVIDER_STATE_ENABLED_UNCERTAIN:
+ return "Enabled uncertain (" + PROVIDER_STATE_ENABLED_UNCERTAIN + ")";
case PROVIDER_STATE_PERM_FAILED:
return "Perm failure (" + PROVIDER_STATE_PERM_FAILED + ")";
case PROVIDER_STATE_UNKNOWN:
@@ -278,6 +315,11 @@ abstract class LocationTimeZoneProvider implements Dumpable {
final ReferenceWithHistory<ProviderState> mCurrentState =
new ReferenceWithHistory<>(10);
+ /**
+ * Used for scheduling initialization timeouts, i.e. for providers that have just been enabled.
+ */
+ @NonNull private final SingleRunnableQueue mInitializationTimeoutQueue;
+
// Non-null and effectively final after initialize() is called.
ProviderListener mProviderListener;
@@ -285,6 +327,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
LocationTimeZoneProvider(@NonNull ThreadingDomain threadingDomain,
@NonNull String providerName) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
+ mInitializationTimeoutQueue = threadingDomain.createSingleRunnableQueue();
mSharedLock = threadingDomain.getLockObject();
mProviderName = Objects.requireNonNull(providerName);
}
@@ -302,7 +345,8 @@ abstract class LocationTimeZoneProvider implements Dumpable {
mProviderListener = Objects.requireNonNull(providerListener);
ProviderState currentState = ProviderState.createStartingState(this);
ProviderState newState = currentState.newState(
- PROVIDER_STATE_DISABLED, null, null, "initialize() called");
+ PROVIDER_STATE_DISABLED, null, null,
+ "initialize() called");
setCurrentState(newState, false);
onInitialize();
@@ -368,41 +412,67 @@ abstract class LocationTimeZoneProvider implements Dumpable {
* #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_DISABLED}. This method must be
* called using the handler thread from the {@link ThreadingDomain}.
*/
- final void enable(@NonNull ConfigurationInternal currentUserConfiguration) {
+ final void enable(@NonNull ConfigurationInternal currentUserConfiguration,
+ @NonNull Duration initializationTimeout, @NonNull Duration initializationTimeoutFuzz) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
assertCurrentState(PROVIDER_STATE_DISABLED);
- ProviderState currentState = getCurrentState();
+ ProviderState currentState = mCurrentState.get();
ProviderState newState = currentState.newState(
- PROVIDER_STATE_ENABLED, null, currentUserConfiguration, "enable() called");
+ PROVIDER_STATE_ENABLED_INITIALIZING, null /* event */,
+ currentUserConfiguration, "enable() called");
setCurrentState(newState, false);
- onEnable();
+
+ Duration delay = initializationTimeout.plus(initializationTimeoutFuzz);
+ mInitializationTimeoutQueue.runDelayed(
+ this::handleInitializationTimeout, delay.toMillis());
+
+ onEnable(initializationTimeout);
+ }
+ }
+
+ private void handleInitializationTimeout() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ ProviderState currentState = mCurrentState.get();
+ if (currentState.stateEnum == PROVIDER_STATE_ENABLED_INITIALIZING) {
+ // On initialization timeout the provider becomes uncertain.
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, null /* event */,
+ currentState.currentUserConfiguration, "initialization timeout");
+ setCurrentState(newState, true);
+ }
}
}
/**
* Implemented by subclasses to do work during {@link #enable}.
*/
- abstract void onEnable();
+ abstract void onEnable(@NonNull Duration initializationTimeout);
/**
* Disables the provider. It is an error* to call this method except when the {@link
- * #getCurrentState()} is at {@link ProviderState#PROVIDER_STATE_ENABLED}. This method must be
+ * #getCurrentState()} is one of the enabled states. This method must be
* called using the handler thread from the {@link ThreadingDomain}.
*/
final void disable() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
- assertCurrentState(PROVIDER_STATE_ENABLED);
+ assertIsEnabled();
- ProviderState currentState = getCurrentState();
- ProviderState newState =
- currentState.newState(PROVIDER_STATE_DISABLED, null, null, "disable() called");
+ ProviderState currentState = mCurrentState.get();
+ ProviderState newState = currentState.newState(
+ PROVIDER_STATE_DISABLED, null, null, "disable() called");
setCurrentState(newState, false);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
+
onDisable();
}
}
@@ -422,7 +492,7 @@ abstract class LocationTimeZoneProvider implements Dumpable {
debugLog("handleLocationTimeZoneEvent: mProviderName=" + mProviderName
+ ", locationTimeZoneEvent=" + locationTimeZoneEvent);
- ProviderState currentState = getCurrentState();
+ ProviderState currentState = mCurrentState.get();
int eventType = locationTimeZoneEvent.getEventType();
switch (currentState.stateEnum) {
case PROVIDER_STATE_PERM_FAILED: {
@@ -443,6 +513,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
return;
}
case EVENT_TYPE_SUCCESS:
@@ -462,7 +535,9 @@ abstract class LocationTimeZoneProvider implements Dumpable {
}
}
}
- case PROVIDER_STATE_ENABLED: {
+ case PROVIDER_STATE_ENABLED_INITIALIZING:
+ case PROVIDER_STATE_ENABLED_CERTAIN:
+ case PROVIDER_STATE_ENABLED_UNCERTAIN: {
switch (eventType) {
case EVENT_TYPE_PERMANENT_FAILURE: {
String msg = "handleLocationTimeZoneEvent:"
@@ -473,14 +548,27 @@ abstract class LocationTimeZoneProvider implements Dumpable {
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
+
return;
}
case EVENT_TYPE_UNCERTAIN:
case EVENT_TYPE_SUCCESS: {
- ProviderState newState = currentState.newState(PROVIDER_STATE_ENABLED,
+ @ProviderStateEnum int providerStateEnum;
+ if (eventType == EVENT_TYPE_UNCERTAIN) {
+ providerStateEnum = PROVIDER_STATE_ENABLED_UNCERTAIN;
+ } else {
+ providerStateEnum = PROVIDER_STATE_ENABLED_CERTAIN;
+ }
+ ProviderState newState = currentState.newState(providerStateEnum,
locationTimeZoneEvent, currentState.currentUserConfiguration,
"handleLocationTimeZoneEvent() when enabled");
setCurrentState(newState, true);
+ if (mInitializationTimeoutQueue.hasQueued()) {
+ mInitializationTimeoutQueue.cancel();
+ }
return;
}
default: {
@@ -501,11 +589,34 @@ abstract class LocationTimeZoneProvider implements Dumpable {
*/
abstract void logWarn(String msg);
- private void assertCurrentState(@ProviderState.ProviderStateEnum int requiredState) {
- ProviderState currentState = getCurrentState();
+ @GuardedBy("mSharedLock")
+ private void assertIsEnabled() {
+ ProviderState currentState = mCurrentState.get();
+ if (!currentState.isEnabled()) {
+ throw new IllegalStateException("Required an enabled state, but was " + currentState);
+ }
+ }
+
+ @GuardedBy("mSharedLock")
+ private void assertCurrentState(@ProviderStateEnum int requiredState) {
+ ProviderState currentState = mCurrentState.get();
if (currentState.stateEnum != requiredState) {
throw new IllegalStateException(
"Required stateEnum=" + requiredState + ", but was " + currentState);
}
}
+
+ @VisibleForTesting
+ boolean isInitializationTimeoutSet() {
+ synchronized (mSharedLock) {
+ return mInitializationTimeoutQueue.hasQueued();
+ }
+ }
+
+ @VisibleForTesting
+ Duration getInitializationTimeoutDelay() {
+ synchronized (mSharedLock) {
+ return Duration.ofMillis(mInitializationTimeoutQueue.getQueuedDelayMillis());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
index 2f75c43594df..ace066e12d3f 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
@@ -19,11 +19,13 @@ package com.android.server.location.timezone;
import android.annotation.NonNull;
import android.os.Handler;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import java.time.Duration;
import java.util.Objects;
/**
@@ -84,6 +86,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
*/
abstract void onConfigChanged();
+ @VisibleForTesting
+ abstract boolean isUncertaintyTimeoutSet();
+
+ @VisibleForTesting
+ abstract long getUncertaintyTimeoutDelayMillis();
+
/**
* Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding
* service. It can easily be faked for tests.
@@ -100,6 +108,24 @@ abstract class LocationTimeZoneProviderController implements Dumpable {
/** Returns the {@link ConfigurationInternal} for the current user of the device. */
abstract ConfigurationInternal getCurrentUserConfigurationInternal();
+
+ /**
+ * Returns the value passed to LocationTimeZoneProviders informing them of how long they
+ * have to return their first time zone suggestion.
+ */
+ abstract Duration getProviderInitializationTimeout();
+
+ /**
+ * Returns the extra time granted on top of {@link #getProviderInitializationTimeout()} to
+ * allow for slop like communication delays.
+ */
+ abstract Duration getProviderInitializationTimeoutFuzz();
+
+ /**
+ * Returns the delay allowed after receiving uncertainty from a provider before it should be
+ * passed on.
+ */
+ abstract Duration getUncertaintyDelay();
}
/**
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
index 79e2b975089d..2bbae56e40c8 100644
--- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
@@ -23,6 +23,8 @@ import android.annotation.Nullable;
import android.util.IndentingPrintWriter;
import android.util.Slog;
+import java.time.Duration;
+
/**
* A {@link LocationTimeZoneProvider} that provides minimal responses needed for the {@link
* LocationTimeZoneProviderController} to operate correctly when there is no "real" provider
@@ -55,7 +57,7 @@ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
- void onEnable() {
+ void onEnable(@NonNull Duration initializationTimeout) {
// Report a failure (asynchronously using the mThreadingDomain thread to avoid recursion).
mThreadingDomain.post(()-> {
// Enter the perm-failed state.
@@ -90,7 +92,7 @@ class NullLocationTimeZoneProvider extends LocationTimeZoneProvider {
synchronized (mSharedLock) {
return "NullLocationTimeZoneProvider{"
+ "mProviderName='" + mProviderName + '\''
- + "mCurrentState='" + mCurrentState + '\''
+ + ", mCurrentState='" + mCurrentState + '\''
+ '}';
}
}
diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
new file mode 100644
index 000000000000..38018779aed1
--- /dev/null
+++ b/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
@@ -0,0 +1,174 @@
+/*
+ * 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.location.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.location.timezone.LocationTimeZoneEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.timezone.ILocationTimeZoneProvider;
+import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
+import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
+import com.android.server.ServiceWatcher;
+
+import java.util.Objects;
+
+/**
+ * System server-side proxy for ILocationTimeZoneProvider implementations, i.e. this provides the
+ * system server object used to communicate with a remote LocationTimeZoneProvider over Binder,
+ * which could be running in a different process. As "remote" LocationTimeZoneProviders are bound /
+ * unbound this proxy will rebind to the "best" available remote process.
+ */
+class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ static LocationTimeZoneProviderProxy createAndRegister(
+ @NonNull Context context, @NonNull ThreadingDomain threadingDomain,
+ @NonNull String action, int enableOverlayResId, int nonOverlayPackageResId) {
+ RealLocationTimeZoneProviderProxy proxy = new RealLocationTimeZoneProviderProxy(
+ context, threadingDomain, action, enableOverlayResId, nonOverlayPackageResId);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ @NonNull private final ServiceWatcher mServiceWatcher;
+
+ @GuardedBy("mProxyLock")
+ @Nullable private ManagerProxy mManagerProxy;
+
+ @GuardedBy("mProxyLock")
+ @NonNull private LocationTimeZoneProviderRequest mRequest;
+
+ private RealLocationTimeZoneProviderProxy(
+ @NonNull Context context, @NonNull ThreadingDomain threadingDomain,
+ @NonNull String action, int enableOverlayResId,
+ int nonOverlayPackageResId) {
+ super(context, threadingDomain);
+ mManagerProxy = null;
+ mRequest = LocationTimeZoneProviderRequest.EMPTY_REQUEST;
+ mServiceWatcher = new ServiceWatcher(context, action, this::onBind, this::onUnbind,
+ enableOverlayResId, nonOverlayPackageResId);
+ }
+
+ private boolean register() {
+ return mServiceWatcher.register();
+ }
+
+ private void onBind(IBinder binder, ComponentName componentName) throws RemoteException {
+ processServiceWatcherCallbackOnThreadingDomainThread(() -> onBindOnHandlerThread(binder));
+ }
+
+ private void onUnbind() {
+ processServiceWatcherCallbackOnThreadingDomainThread(this::onUnbindOnHandlerThread);
+ }
+
+ private void processServiceWatcherCallbackOnThreadingDomainThread(@NonNull Runnable runnable) {
+ // For simplicity, this code just post()s the runnable to the mThreadingDomain Thread in all
+ // cases. This adds a delay if ServiceWatcher and ThreadingDomain happen to be using the
+ // same thread, but nothing here should be performance critical.
+ mThreadingDomain.post(runnable);
+ }
+
+ private void onBindOnHandlerThread(@NonNull IBinder binder) {
+ mThreadingDomain.assertCurrentThread();
+
+ ILocationTimeZoneProvider provider = ILocationTimeZoneProvider.Stub.asInterface(binder);
+
+ synchronized (mSharedLock) {
+ try {
+ mManagerProxy = new ManagerProxy();
+ provider.setLocationTimeZoneProviderManager(mManagerProxy);
+ trySendCurrentRequest();
+ mListener.onProviderBound();
+ } catch (RemoteException e) {
+ // This is not expected to happen.
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private void onUnbindOnHandlerThread() {
+ mThreadingDomain.assertCurrentThread();
+
+ synchronized (mSharedLock) {
+ mManagerProxy = null;
+ mListener.onProviderUnbound();
+ }
+ }
+
+ @Override
+ final void setRequest(@NonNull LocationTimeZoneProviderRequest request) {
+ mThreadingDomain.assertCurrentThread();
+
+ Objects.requireNonNull(request);
+ synchronized (mSharedLock) {
+ mRequest = request;
+
+ trySendCurrentRequest();
+ }
+ }
+
+ @GuardedBy("mProxyLock")
+ private void trySendCurrentRequest() {
+ LocationTimeZoneProviderRequest request = mRequest;
+ mServiceWatcher.runOnBinder(binder -> {
+ ILocationTimeZoneProvider service =
+ ILocationTimeZoneProvider.Stub.asInterface(binder);
+ service.setRequest(request);
+ });
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("mRequest=" + mRequest);
+ mServiceWatcher.dump(null, ipw, args);
+ }
+ }
+
+ /**
+ * A system Server-side proxy for the ILocationTimeZoneProviderManager, i.e. this is a local
+ * binder stub. Each "remote" LocationTimeZoneProvider is passed a binder instance that it
+ * then uses to communicate back with the system server, invoking the logic here.
+ */
+ private class ManagerProxy extends ILocationTimeZoneProviderManager.Stub {
+
+ // executed on binder thread
+ @Override
+ public void onLocationTimeZoneEvent(LocationTimeZoneEvent locationTimeZoneEvent) {
+ synchronized (mSharedLock) {
+ if (mManagerProxy != this) {
+ return;
+ }
+ }
+ handleLocationTimeZoneEvent(locationTimeZoneEvent);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java
index ef2e349fceaa..f1d37237872b 100644
--- a/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java
+++ b/services/core/java/com/android/server/location/timezone/SimulatedBinderProviderEvent.java
@@ -21,6 +21,7 @@ import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneManagerService.PRIMARY_PROVIDER_NAME;
+import static com.android.server.location.timezone.LocationTimeZoneManagerService.SECONDARY_PROVIDER_NAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,7 +43,8 @@ import java.util.Objects;
*/
final class SimulatedBinderProviderEvent {
- private static final List<String> VALID_PROVIDER_NAMES = Arrays.asList(PRIMARY_PROVIDER_NAME);
+ private static final List<String> VALID_PROVIDER_NAMES =
+ Arrays.asList(PRIMARY_PROVIDER_NAME, SECONDARY_PROVIDER_NAME);
static final int INJECTED_EVENT_TYPE_ON_BIND = 1;
static final int INJECTED_EVENT_TYPE_ON_UNBIND = 2;
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
index 9b9c82358974..d55d3edf5e03 100644
--- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
+++ b/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
@@ -83,21 +83,14 @@ abstract class ThreadingDomain {
}
/**
- * A class that allows up to one {@link Runnable} to be queued on the handler, i.e. calling any
- * of the methods will cancel the execution of any previously queued / delayed runnable. All
+ * A class that allows up to one {@link Runnable} to be queued, i.e. calling {@link
+ * #runDelayed(Runnable, long)} will cancel the execution of any previously queued runnable. All
* methods must be called from the {@link ThreadingDomain}'s thread.
*/
final class SingleRunnableQueue {
- /**
- * Runs the supplied {@link Runnable} synchronously on the threading domain's thread,
- * cancelling any queued but not-yet-executed {@link Runnable} previously added by this.
- * This method must be called from the threading domain's thread.
- */
- void runSynchronously(Runnable r) {
- cancel();
- r.run();
- }
+ private boolean mIsQueued;
+ private long mDelayMillis;
/**
* Posts the supplied {@link Runnable} asynchronously and delayed on the threading domain
@@ -106,15 +99,48 @@ abstract class ThreadingDomain {
*/
void runDelayed(Runnable r, long delayMillis) {
cancel();
- ThreadingDomain.this.postDelayed(r, this, delayMillis);
+ mIsQueued = true;
+ mDelayMillis = delayMillis;
+ ThreadingDomain.this.postDelayed(() -> {
+ mIsQueued = false;
+ mDelayMillis = -2;
+ r.run();
+ }, this, delayMillis);
+ }
+
+ /**
+ * Returns {@code true} if there is an item current queued. This method must be called from
+ * the threading domain's thread.
+ */
+ boolean hasQueued() {
+ assertCurrentThread();
+ return mIsQueued;
+ }
+
+ /**
+ * Returns the delay in milliseconds for the currently queued item. Throws {@link
+ * IllegalStateException} if nothing is currently queued, see {@link #hasQueued()}.
+ * This method must be called from the threading domain's thread.
+ */
+ long getQueuedDelayMillis() {
+ assertCurrentThread();
+ if (!mIsQueued) {
+ throw new IllegalStateException("No item queued");
+ }
+ return mDelayMillis;
}
/**
* Cancels any queued but not-yet-executed {@link Runnable} previously added by this.
+ * This method must be called from the threading domain's thread.
*/
public void cancel() {
assertCurrentThread();
- removeQueuedRunnables(this);
+ if (mIsQueued) {
+ removeQueuedRunnables(this);
+ }
+ mIsQueued = false;
+ mDelayMillis = -1;
}
}
}
diff --git a/services/core/java/com/android/server/location/util/AlarmHelper.java b/services/core/java/com/android/server/location/util/AlarmHelper.java
new file mode 100644
index 000000000000..46dbdbffd87e
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/AlarmHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helps manage alarms.
+ */
+public abstract class AlarmHelper {
+
+ /**
+ * Sets a wakeup alarm that will fire after the given delay.
+ */
+ public final void setDelayedAlarm(long delayMs, OnAlarmListener listener,
+ WorkSource workSource) {
+ // helps ensure that we're not wasting system resources by setting alarms in the past/now
+ Preconditions.checkArgument(delayMs > 0);
+ Preconditions.checkArgument(workSource != null);
+ setDelayedAlarmInternal(delayMs, listener, workSource);
+ }
+
+ protected abstract void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+ WorkSource workSource);
+
+ /**
+ * Cancels an alarm.
+ */
+ public abstract void cancel(OnAlarmListener listener);
+}
diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java
index 379b303bbfc3..6ebce91fe963 100644
--- a/services/core/java/com/android/server/location/util/Injector.java
+++ b/services/core/java/com/android/server/location/util/Injector.java
@@ -17,7 +17,6 @@
package com.android.server.location.util;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.location.LocationRequestStatistics;
/**
* Injects various location dependencies so that they may be controlled by tests.
@@ -28,6 +27,9 @@ public interface Injector {
/** Returns a UserInfoHelper. */
UserInfoHelper getUserInfoHelper();
+ /** Returns an AlarmHelper. */
+ AlarmHelper getAlarmHelper();
+
/** Returns an AppOpsHelper. */
AppOpsHelper getAppOpsHelper();
@@ -52,6 +54,6 @@ public interface Injector {
/** Returns a LocationUsageLogger. */
LocationUsageLogger getLocationUsageLogger();
- /** Returns a LocationRequestStatistics. */
- LocationRequestStatistics getLocationRequestStatistics();
+ /** Returns a LocationEventLog. */
+ LocationEventLog getLocationEventLog();
}
diff --git a/services/core/java/com/android/server/location/util/LocationEventLog.java b/services/core/java/com/android/server/location/util/LocationEventLog.java
new file mode 100644
index 000000000000..e4c354b1c070
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/LocationEventLog.java
@@ -0,0 +1,313 @@
+/*
+ * 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.location.util;
+
+import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
+import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
+import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE;
+import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+
+import android.annotation.Nullable;
+import android.location.LocationRequest;
+import android.location.util.identity.CallerIdentity;
+import android.os.Build;
+import android.os.PowerManager.LocationPowerSaveMode;
+
+import com.android.internal.location.ProviderRequest;
+import com.android.server.location.LocationManagerService;
+import com.android.server.utils.eventlog.LocalEventLog;
+
+/** In memory event log for location events. */
+public class LocationEventLog extends LocalEventLog {
+
+ private static int getLogSize() {
+ if (Build.IS_DEBUGGABLE || LocationManagerService.D) {
+ return 500;
+ } else {
+ return 200;
+ }
+ }
+
+ private static final int EVENT_LOCATION_ENABLED = 1;
+ private static final int EVENT_PROVIDER_ENABLED = 2;
+ private static final int EVENT_PROVIDER_MOCKED = 3;
+ private static final int EVENT_PROVIDER_REGISTER_CLIENT = 4;
+ private static final int EVENT_PROVIDER_UNREGISTER_CLIENT = 5;
+ private static final int EVENT_PROVIDER_UPDATE_REQUEST = 6;
+ private static final int EVENT_PROVIDER_RECEIVE_LOCATION = 7;
+ private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8;
+ private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9;
+
+ public LocationEventLog() {
+ super(getLogSize());
+ }
+
+ /** Logs a location enabled/disabled event. */
+ public synchronized void logLocationEnabled(int userId, boolean enabled) {
+ addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, userId, enabled);
+ }
+
+ /** Logs a location provider enabled/disabled event. */
+ public synchronized void logProviderEnabled(String provider, int userId, boolean enabled) {
+ addLogEvent(EVENT_PROVIDER_ENABLED, provider, userId, enabled);
+ }
+
+ /** Logs a location provider being replaced/unreplaced by a mock provider. */
+ public synchronized void logProviderMocked(String provider, boolean mocked) {
+ addLogEvent(EVENT_PROVIDER_MOCKED, provider, mocked);
+ }
+
+ /** Logs a new client registration for a location provider. */
+ public synchronized void logProviderClientRegistered(String provider, CallerIdentity identity,
+ LocationRequest request) {
+ addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request);
+ }
+
+ /** Logs a client unregistration for a location provider. */
+ public synchronized void logProviderClientUnregistered(String provider,
+ CallerIdentity identity) {
+ addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity);
+ }
+
+ /** Logs a change to the provider request for a location provider. */
+ public synchronized void logProviderUpdateRequest(String provider, ProviderRequest request) {
+ addLogEvent(EVENT_PROVIDER_UPDATE_REQUEST, provider, request);
+ }
+
+ /** Logs a new incoming location for a location provider. */
+ public synchronized void logProviderReceivedLocation(String provider) {
+ addLogEvent(EVENT_PROVIDER_RECEIVE_LOCATION, provider);
+ }
+
+ /** Logs a location deliver for a client of a location provider. */
+ public synchronized void logProviderDeliveredLocation(String provider,
+ CallerIdentity identity) {
+ addLogEvent(EVENT_PROVIDER_DELIVER_LOCATION, provider, identity);
+ }
+
+ /** Logs that the location power save mode has changed. */
+ public synchronized void logLocationPowerSaveMode(
+ @LocationPowerSaveMode int locationPowerSaveMode) {
+ addLogEvent(EVENT_LOCATION_POWER_SAVE_MODE_CHANGE, locationPowerSaveMode);
+ }
+
+ @Override
+ protected LogEvent createLogEvent(long timeDelta, int event, Object... args) {
+ switch (event) {
+ case EVENT_LOCATION_ENABLED:
+ return new LocationEnabledEvent(timeDelta, (Integer) args[1], (Boolean) args[2]);
+ case EVENT_PROVIDER_ENABLED:
+ return new ProviderEnabledEvent(timeDelta, (String) args[0], (Integer) args[1],
+ (Boolean) args[2]);
+ case EVENT_PROVIDER_MOCKED:
+ return new ProviderMockedEvent(timeDelta, (String) args[0], (Boolean) args[1]);
+ case EVENT_PROVIDER_REGISTER_CLIENT:
+ return new ProviderRegisterEvent(timeDelta, (String) args[0], true,
+ (CallerIdentity) args[1], (LocationRequest) args[2]);
+ case EVENT_PROVIDER_UNREGISTER_CLIENT:
+ return new ProviderRegisterEvent(timeDelta, (String) args[0], false,
+ (CallerIdentity) args[1], null);
+ case EVENT_PROVIDER_UPDATE_REQUEST:
+ return new ProviderUpdateEvent(timeDelta, (String) args[0],
+ (ProviderRequest) args[1]);
+ case EVENT_PROVIDER_RECEIVE_LOCATION:
+ return new ProviderReceiveLocationEvent(timeDelta, (String) args[0]);
+ case EVENT_PROVIDER_DELIVER_LOCATION:
+ return new ProviderDeliverLocationEvent(timeDelta, (String) args[0],
+ (CallerIdentity) args[1]);
+ case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
+ return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private static class ProviderEnabledEvent extends LogEvent {
+
+ private final String mProvider;
+ private final int mUserId;
+ private final boolean mEnabled;
+
+ protected ProviderEnabledEvent(long timeDelta, String provider, int userId,
+ boolean enabled) {
+ super(timeDelta);
+ mProvider = provider;
+ mUserId = userId;
+ mEnabled = enabled;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider [u" + mUserId + "] " + (mEnabled ? "enabled"
+ : "disabled");
+ }
+ }
+
+ private static class ProviderMockedEvent extends LogEvent {
+
+ private final String mProvider;
+ private final boolean mMocked;
+
+ protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) {
+ super(timeDelta);
+ mProvider = provider;
+ mMocked = mocked;
+ }
+
+ @Override
+ public String getLogString() {
+ if (mMocked) {
+ return mProvider + " provider added mock provider override";
+ } else {
+ return mProvider + " provider removed mock provider override";
+ }
+ }
+ }
+
+ private static class ProviderRegisterEvent extends LogEvent {
+
+ private final String mProvider;
+ private final boolean mRegistered;
+ private final CallerIdentity mIdentity;
+ @Nullable private final LocationRequest mLocationRequest;
+
+ private ProviderRegisterEvent(long timeDelta, String provider, boolean registered,
+ CallerIdentity identity, @Nullable LocationRequest locationRequest) {
+ super(timeDelta);
+ mProvider = provider;
+ mRegistered = registered;
+ mIdentity = identity;
+ mLocationRequest = locationRequest;
+ }
+
+ @Override
+ public String getLogString() {
+ if (mRegistered) {
+ return mProvider + " provider " + "+registration " + mIdentity + " -> "
+ + mLocationRequest;
+ } else {
+ return mProvider + " provider " + "-registration " + mIdentity;
+ }
+ }
+ }
+
+ private static class ProviderUpdateEvent extends LogEvent {
+
+ private final String mProvider;
+ private final ProviderRequest mRequest;
+
+ private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) {
+ super(timeDelta);
+ mProvider = provider;
+ mRequest = request;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider request = " + mRequest;
+ }
+ }
+
+ private static class ProviderReceiveLocationEvent extends LogEvent {
+
+ private final String mProvider;
+
+ private ProviderReceiveLocationEvent(long timeDelta, String provider) {
+ super(timeDelta);
+ mProvider = provider;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider received location";
+ }
+ }
+
+ private static class ProviderDeliverLocationEvent extends LogEvent {
+
+ private final String mProvider;
+ @Nullable private final CallerIdentity mIdentity;
+
+ private ProviderDeliverLocationEvent(long timeDelta, String provider,
+ @Nullable CallerIdentity identity) {
+ super(timeDelta);
+ mProvider = provider;
+ mIdentity = identity;
+ }
+
+ @Override
+ public String getLogString() {
+ return mProvider + " provider delivered location to " + mIdentity;
+ }
+ }
+
+ private static class LocationPowerSaveModeEvent extends LogEvent {
+
+ @LocationPowerSaveMode
+ private final int mLocationPowerSaveMode;
+
+ private LocationPowerSaveModeEvent(long timeDelta,
+ @LocationPowerSaveMode int locationPowerSaveMode) {
+ super(timeDelta);
+ mLocationPowerSaveMode = locationPowerSaveMode;
+ }
+
+ @Override
+ public String getLogString() {
+ String mode;
+ switch (mLocationPowerSaveMode) {
+ case LOCATION_MODE_NO_CHANGE:
+ mode = "NO_CHANGE";
+ break;
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ mode = "GPS_DISABLED_WHEN_SCREEN_OFF";
+ break;
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ mode = "ALL_DISABLED_WHEN_SCREEN_OFF";
+ break;
+ case LOCATION_MODE_FOREGROUND_ONLY:
+ mode = "FOREGROUND_ONLY";
+ break;
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ mode = "THROTTLE_REQUESTS_WHEN_SCREEN_OFF";
+ break;
+ default:
+ mode = "UNKNOWN";
+ break;
+ }
+ return "location power save mode changed to " + mode;
+ }
+ }
+
+ private static class LocationEnabledEvent extends LogEvent {
+
+ private final int mUserId;
+ private final boolean mEnabled;
+
+ private LocationEnabledEvent(long timeDelta, int userId, boolean enabled) {
+ super(timeDelta);
+ mUserId = userId;
+ mEnabled = enabled;
+ }
+
+ @Override
+ public String getLogString() {
+ return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java
index a9a8c50f11dc..6e4cf064b440 100644
--- a/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java
@@ -16,7 +16,13 @@
package com.android.server.location.util;
+import static android.os.PowerManager.locationPowerSaveModeToString;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
import android.os.PowerManager.LocationPowerSaveMode;
+import android.util.Log;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -35,9 +41,11 @@ public abstract class LocationPowerSaveModeHelper {
void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode);
}
+ private final LocationEventLog mLocationEventLog;
private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners;
- public LocationPowerSaveModeHelper() {
+ public LocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
+ mLocationEventLog = locationEventLog;
mListeners = new CopyOnWriteArrayList<>();
}
@@ -58,6 +66,12 @@ public abstract class LocationPowerSaveModeHelper {
protected final void notifyLocationPowerSaveModeChanged(
@LocationPowerSaveMode int locationPowerSaveMode) {
+ if (D) {
+ Log.d(TAG, "location power save mode is now " + locationPowerSaveModeToString(
+ locationPowerSaveMode));
+ }
+ mLocationEventLog.logLocationPowerSaveMode(locationPowerSaveMode);
+
for (LocationPowerSaveModeChangedListener listener : mListeners) {
listener.onLocationPowerSaveModeChanged(locationPowerSaveMode);
}
diff --git a/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java
index d47bce31ed23..ecd6966c1493 100644
--- a/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java
+++ b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java
@@ -16,6 +16,11 @@
package com.android.server.location.util;
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.util.Log;
+
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -55,6 +60,10 @@ public abstract class ScreenInteractiveHelper {
}
protected final void notifyScreenInteractiveChanged(boolean interactive) {
+ if (D) {
+ Log.d(TAG, "screen interactive is now " + interactive);
+ }
+
for (ScreenInteractiveChangedListener listener : mListeners) {
listener.onScreenInteractiveChanged(interactive);
}
diff --git a/services/core/java/com/android/server/location/util/SystemAlarmHelper.java b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
new file mode 100644
index 000000000000..81849794c472
--- /dev/null
+++ b/services/core/java/com/android/server/location/util/SystemAlarmHelper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.location.util;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.WINDOW_EXACT;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.SystemClock;
+import android.os.WorkSource;
+
+import com.android.server.FgThread;
+
+import java.util.Objects;
+
+/**
+ * Provides helpers for alarms.
+ */
+public class SystemAlarmHelper extends AlarmHelper {
+
+ private final Context mContext;
+
+ public SystemAlarmHelper(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void setDelayedAlarmInternal(long delayMs, AlarmManager.OnAlarmListener listener,
+ WorkSource workSource) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayMs,
+ WINDOW_EXACT, 0, listener, FgThread.getHandler(), workSource);
+ }
+
+ @Override
+ public void cancel(AlarmManager.OnAlarmListener listener) {
+ AlarmManager alarmManager = Objects.requireNonNull(
+ mContext.getSystemService(AlarmManager.class));
+ alarmManager.cancel(listener);
+ }
+}
diff --git a/services/core/java/com/android/server/location/util/SystemAppForegroundHelper.java b/services/core/java/com/android/server/location/util/SystemAppForegroundHelper.java
index d8b1d5b88260..3ff572b0a8e6 100644
--- a/services/core/java/com/android/server/location/util/SystemAppForegroundHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemAppForegroundHelper.java
@@ -63,7 +63,7 @@ public class SystemAppForegroundHelper extends AppForegroundHelper {
public boolean isAppForeground(int uid) {
Preconditions.checkState(mActivityManager != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return isForeground(mActivityManager.getUidImportance(uid));
} finally {
diff --git a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
index cfb7697a8dfc..8742d65df97c 100644
--- a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java
@@ -60,7 +60,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return mAppOps.startOpNoThrow(
appOp,
@@ -78,7 +78,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
public void finishOp(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mAppOps.finishOp(
appOp,
@@ -94,7 +94,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return mAppOps.checkOpNoThrow(
appOp,
@@ -109,7 +109,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
public boolean noteOp(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return mAppOps.noteOp(
appOp,
@@ -126,7 +126,7 @@ public class SystemAppOpsHelper extends AppOpsHelper {
public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) {
Preconditions.checkState(mAppOps != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return mAppOps.noteOpNoThrow(
appOp,
diff --git a/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java
index b9c0ddef04ab..4f1f7a0038d2 100644
--- a/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java
@@ -54,7 +54,7 @@ public class SystemLocationPermissionsHelper extends LocationPermissionsHelper {
@Override
protected boolean hasPermission(String permission, CallerIdentity callerIdentity) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return mContext.checkPermission(permission, callerIdentity.getPid(),
callerIdentity.getUid()) == PERMISSION_GRANTED;
diff --git a/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java
index c8d8202157f0..3129eba33870 100644
--- a/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java
@@ -41,7 +41,8 @@ public class SystemLocationPowerSaveModeHelper extends LocationPowerSaveModeHelp
@LocationPowerSaveMode
private volatile int mLocationPowerSaveMode;
- public SystemLocationPowerSaveModeHelper(Context context) {
+ public SystemLocationPowerSaveModeHelper(Context context, LocationEventLog locationEventLog) {
+ super(locationEventLog);
mContext = context;
}
diff --git a/services/core/java/com/android/server/location/util/SystemSettingsHelper.java b/services/core/java/com/android/server/location/util/SystemSettingsHelper.java
index 39aeaba16579..560cd3c719d7 100644
--- a/services/core/java/com/android/server/location/util/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemSettingsHelper.java
@@ -123,7 +123,7 @@ public class SystemSettingsHelper extends SettingsHelper {
*/
@Override
public void setLocationEnabled(boolean enabled, int userId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(
mContext.getContentResolver(),
@@ -314,7 +314,7 @@ public class SystemSettingsHelper extends SettingsHelper {
*/
@Override
public long getBackgroundThrottleProximityAlertIntervalMs() {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getLong(mContext.getContentResolver(),
LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
@@ -330,7 +330,7 @@ public class SystemSettingsHelper extends SettingsHelper {
*/
@Override
public float getCoarseLocationAccuracyM() {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
final ContentResolver cr = mContext.getContentResolver();
try {
return Settings.Secure.getFloatForUser(
@@ -458,7 +458,7 @@ public class SystemSettingsHelper extends SettingsHelper {
}
public int getValueForUser(int defaultValue, int userId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSettingName,
defaultValue, userId);
@@ -496,7 +496,7 @@ public class SystemSettingsHelper extends SettingsHelper {
List<String> value = mCachedValue;
if (userId != mCachedUserId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
mSettingName, userId);
@@ -548,7 +548,7 @@ public class SystemSettingsHelper extends SettingsHelper {
}
public boolean getValue(boolean defaultValue) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getInt(mContext.getContentResolver(), mSettingName,
defaultValue ? 1 : 0) != 0;
@@ -574,7 +574,7 @@ public class SystemSettingsHelper extends SettingsHelper {
}
public long getValue(long defaultValue) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return Settings.Global.getLong(mContext.getContentResolver(), mSettingName,
defaultValue);
@@ -612,7 +612,7 @@ public class SystemSettingsHelper extends SettingsHelper {
public synchronized Set<String> getValue() {
ArraySet<String> value = mCachedValue;
if (!mValid) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
value = new ArraySet<>(mBaseValuesSupplier.get());
String setting = Settings.Global.getString(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/location/util/SystemUserInfoHelper.java b/services/core/java/com/android/server/location/util/SystemUserInfoHelper.java
index e9836aad0472..141afa7eef4c 100644
--- a/services/core/java/com/android/server/location/util/SystemUserInfoHelper.java
+++ b/services/core/java/com/android/server/location/util/SystemUserInfoHelper.java
@@ -97,7 +97,7 @@ public class SystemUserInfoHelper extends UserInfoHelper {
public int[] getRunningUserIds() {
IActivityManager activityManager = getActivityManager();
if (activityManager != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return activityManager.getRunningUserIds();
} catch (RemoteException e) {
@@ -118,7 +118,7 @@ public class SystemUserInfoHelper extends UserInfoHelper {
public boolean isCurrentUserId(@UserIdInt int userId) {
ActivityManagerInternal activityManagerInternal = getActivityManagerInternal();
if (activityManagerInternal != null) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return activityManagerInternal.isCurrentProfile(userId);
} finally {
@@ -136,7 +136,7 @@ public class SystemUserInfoHelper extends UserInfoHelper {
// if you're hitting this precondition then you are invoking this before the system is ready
Preconditions.checkState(userManager != null);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return userManager.getEnabledProfileIds(userId);
} finally {
diff --git a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
index ddd56c890c2f..82b0f9c05b6b 100644
--- a/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
+++ b/services/core/java/com/android/server/locksettings/BiometricDeferredQueue.java
@@ -20,9 +20,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.face.FaceManager;
-import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.ServiceManager;
@@ -34,7 +34,6 @@ import com.android.internal.widget.VerifyCredentialResponse;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
@@ -203,9 +202,9 @@ public class BiometricDeferredQueue {
private void processPendingLockoutsForFingerprint(List<UserAuthInfo> pendingResetLockouts) {
if (mFingerprintManager != null) {
- final List<FingerprintSensorProperties> fingerprintSensorProperties =
- mFingerprintManager.getSensorProperties();
- for (FingerprintSensorProperties prop : fingerprintSensorProperties) {
+ final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
+ mFingerprintManager.getSensorPropertiesInternal();
+ for (FingerprintSensorPropertiesInternal prop : fingerprintSensorProperties) {
if (!prop.resetLockoutRequiresHardwareAuthToken) {
for (UserAuthInfo user : pendingResetLockouts) {
mFingerprintManager.resetLockout(prop.sensorId, user.userId,
@@ -238,16 +237,16 @@ public class BiometricDeferredQueue {
Slog.w(TAG, "mFaceGenerateChallengeCallback not null, previous operation may be"
+ " stuck");
}
- final List<FaceSensorProperties> faceSensorProperties =
- mFaceManager.getSensorProperties();
+ final List<FaceSensorPropertiesInternal> faceSensorProperties =
+ mFaceManager.getSensorPropertiesInternal();
final Set<Integer> sensorIds = new ArraySet<>();
- for (FaceSensorProperties prop : faceSensorProperties) {
+ for (FaceSensorPropertiesInternal prop : faceSensorProperties) {
sensorIds.add(prop.sensorId);
}
mFaceResetLockoutTask = new FaceResetLockoutTask(mFaceFinishCallback, mFaceManager,
mSpManager, sensorIds, pendingResetLockouts);
- for (final FaceSensorProperties prop : faceSensorProperties) {
+ for (final FaceSensorPropertiesInternal prop : faceSensorProperties) {
// Generate a challenge for each sensor. The challenge does not need to be
// per-user, since the HAT returned by gatekeeper contains userId.
mFaceManager.generateChallenge(prop.sensorId, mFaceResetLockoutTask);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 7e64dc6fd0d7..e836d8d99cd2 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -114,6 +114,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
@@ -1428,7 +1429,7 @@ public class LockSettingsService extends ILockSettings.Stub {
// Now we have unlocked the parent user and attempted to unlock the profile we should
// show notifications if the profile is still locked.
if (!alreadyUnlocked) {
- long ident = clearCallingIdentity();
+ final long ident = clearCallingIdentity();
try {
maybeShowEncryptionNotificationForUser(profile.id);
} finally {
@@ -2275,7 +2276,7 @@ public class LockSettingsService extends ILockSettings.Stub {
final IStorageManager service = mInjector.getStorageManager();
// TODO(b/120484642): Update vold to return a password as a byte array
String password;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
password = service.getPassword();
service.clearPassword();
@@ -2658,9 +2659,12 @@ public class LockSettingsService extends ILockSettings.Stub {
protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
LockscreenCredential credential, int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
+ Preconditions.checkState(
+ getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE,
+ "Cannot reinitialize SP");
+
final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
getGateKeeperService(), credentialHash, credential, userId);
- onAuthTokenKnownForUser(userId, auth);
if (auth == null) {
Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
return null;
@@ -2683,6 +2687,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
fixateNewestUserKeyAuth(userId);
setSyntheticPasswordHandleLocked(handle, userId);
+ onAuthTokenKnownForUser(userId, auth);
return auth;
}
@@ -2718,7 +2723,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@VisibleForTesting
protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) {
- return true;
+ return getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE;
}
private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
@@ -3491,7 +3496,7 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public PasswordMetrics getUserPasswordMetrics(int userHandle) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
if (isManagedProfileWithUnifiedLock(userHandle)) {
// A managed profile with unified challenge is supposed to be protected by the
@@ -3550,6 +3555,9 @@ public class LockSettingsService extends ILockSettings.Stub {
SyntheticPasswordManager.AuthenticationToken
authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
authToken.recreateDirectly(syntheticPassword);
+ synchronized (mSpManager) {
+ mSpManager.verifyChallenge(getGateKeeperService(), authToken, 0L, userId);
+ }
onCredentialVerified(authToken, loadPasswordMetrics(authToken, userId), userId);
}
}
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index b9997e0c2ec8..0a4d17f20aec 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -259,7 +259,7 @@ final class MediaButtonReceiverHolder {
return "";
}
return String.join(COMPONENT_NAME_USER_ID_DELIM,
- mComponentName.toString(),
+ mComponentName.flattenToString(),
String.valueOf(mUserId),
String.valueOf(mComponentType));
}
diff --git a/services/core/java/com/android/server/media/MediaResourceMonitorService.java b/services/core/java/com/android/server/media/MediaResourceMonitorService.java
index e5da9b1c178c..fc7dd30eec49 100644
--- a/services/core/java/com/android/server/media/MediaResourceMonitorService.java
+++ b/services/core/java/com/android/server/media/MediaResourceMonitorService.java
@@ -28,6 +28,8 @@ import android.util.Log;
import com.android.server.SystemService;
+import java.util.List;
+
/** This class provides a system service that monitors media resource usage. */
public class MediaResourceMonitorService extends SystemService {
private static final String TAG = "MediaResourceMonitor";
@@ -60,16 +62,18 @@ public class MediaResourceMonitorService extends SystemService {
if (pkgNames == null) {
return;
}
- UserManager manager = getContext().getSystemService(UserManager.class);
- int[] userIds = manager.getEnabledProfileIds(ActivityManager.getCurrentUser());
- if (userIds == null || userIds.length == 0) {
+ UserManager manager = getContext().createContextAsUser(
+ UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
+ .getSystemService(UserManager.class);
+ List<UserHandle> enabledProfiles = manager.getEnabledProfiles();
+ if (enabledProfiles.isEmpty()) {
return;
}
Intent intent = new Intent(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
intent.putExtra(Intent.EXTRA_PACKAGES, pkgNames);
intent.putExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE, type);
- for (int userId : userIds) {
- getContext().sendBroadcastAsUser(intent, UserHandle.of(userId),
+ for (UserHandle userHandle : enabledProfiles) {
+ getContext().sendBroadcastAsUser(intent, userHandle,
android.Manifest.permission.RECEIVE_MEDIA_RESOURCE_USAGE);
}
} finally {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 8e932155d8a7..828a0ac94c0c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1397,7 +1397,7 @@ public class MediaSessionService extends SystemService implements Monitor {
*/
@Override
public boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
- MediaSession.Token sessionToken, KeyEvent keyEvent) {
+ KeyEvent keyEvent, MediaSession.Token sessionToken) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
@@ -1772,7 +1772,7 @@ public class MediaSessionService extends SystemService implements Monitor {
*/
@Override
public void dispatchVolumeKeyEventToSessionAsSystemService(String packageName,
- String opPackageName, MediaSession.Token sessionToken, KeyEvent keyEvent) {
+ String opPackageName, KeyEvent keyEvent, MediaSession.Token sessionToken) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index 20df271a1de2..69c57a9a5d74 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -38,6 +38,7 @@ import android.view.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.List;
@@ -53,11 +54,13 @@ public class MediaShellCommand extends ShellCommand {
private ISessionManager mSessionService;
private PrintWriter mWriter;
private PrintWriter mErrorWriter;
+ private InputStream mInput;
@Override
public int onCommand(String cmd) {
mWriter = getOutPrintWriter();
mErrorWriter = getErrPrintWriter();
+ mInput = getRawInputStream();
if (TextUtils.isEmpty(cmd)) {
return handleDefaultCommands(cmd);
@@ -189,6 +192,10 @@ public class MediaShellCommand extends ShellCommand {
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD));
}
+ void log(String code, String msg) {
+ mWriter.println(code + " " + msg);
+ }
+
void showError(String errMsg) {
onHelp();
mErrorWriter.println(errMsg);
@@ -273,11 +280,14 @@ public class MediaShellCommand extends ShellCommand {
cbThread.start();
try {
- InputStreamReader converter = new InputStreamReader(System.in);
+ InputStreamReader converter = new InputStreamReader(mInput);
BufferedReader in = new BufferedReader(converter);
String line;
- while ((line = in.readLine()) != null) {
+ while (true) {
+ mWriter.flush();
+ mErrorWriter.flush();
+ if ((line = in.readLine()) == null) break;
boolean addNewline = true;
if (line.length() <= 0) {
addNewline = false;
@@ -297,7 +307,7 @@ public class MediaShellCommand extends ShellCommand {
synchronized (this) {
if (addNewline) {
- System.out.println("");
+ mWriter.println("");
}
printUsageMessage();
}
diff --git a/services/core/java/com/android/server/media/VolumeCtrl.java b/services/core/java/com/android/server/media/VolumeCtrl.java
index 7a2666566ea2..d516d963e866 100644
--- a/services/core/java/com/android/server/media/VolumeCtrl.java
+++ b/services/core/java/com/android/server/media/VolumeCtrl.java
@@ -32,6 +32,8 @@ import com.android.internal.os.BaseCommand;
public class VolumeCtrl {
private static final String TAG = "VolumeCtrl";
+ private static final String LOG_V = "[V]";
+ private static final String LOG_E = "[E]";
// --stream affects --set, --adj or --get options.
// --show affects --set and --adj options.
@@ -80,21 +82,22 @@ public class VolumeCtrl {
break;
case "--get":
doGet = true;
- log(LOG_V, "will get volume");
+ cmd.log(LOG_V, "will get volume");
break;
case "--stream":
stream = Integer.decode(cmd.getNextArgRequired()).intValue();
- log(LOG_V, "will control stream=" + stream + " (" + streamName(stream) + ")");
+ cmd.log(LOG_V,
+ "will control stream=" + stream + " (" + streamName(stream) + ")");
break;
case "--set":
volIndex = Integer.decode(cmd.getNextArgRequired()).intValue();
mode = VOLUME_CONTROL_MODE_SET;
- log(LOG_V, "will set volume to index=" + volIndex);
+ cmd.log(LOG_V, "will set volume to index=" + volIndex);
break;
case "--adj":
mode = VOLUME_CONTROL_MODE_ADJUST;
adjustment = cmd.getNextArgRequired();
- log(LOG_V, "will adjust volume");
+ cmd.log(LOG_V, "will adjust volume");
break;
default:
throw new IllegalArgumentException("Unknown argument " + option);
@@ -122,11 +125,11 @@ public class VolumeCtrl {
//----------------------------------------
// Test initialization
- log(LOG_V, "Connecting to AudioService");
+ cmd.log(LOG_V, "Connecting to AudioService");
IAudioService audioService = IAudioService.Stub.asInterface(ServiceManager.checkService(
Context.AUDIO_SERVICE));
if (audioService == null) {
- System.err.println(BaseCommand.NO_SYSTEM_ERROR_CODE);
+ cmd.log(LOG_E, BaseCommand.NO_SYSTEM_ERROR_CODE);
throw new AndroidException(
"Can't connect to audio service; is the system running?");
}
@@ -152,23 +155,12 @@ public class VolumeCtrl {
audioService.adjustStreamVolume(stream, adjDir, flag, pack);
}
if (doGet) {
- log(LOG_V, "volume is " + audioService.getStreamVolume(stream)
+ cmd.log(LOG_V, "volume is " + audioService.getStreamVolume(stream)
+ " in range [" + audioService.getStreamMinVolume(stream)
+ ".." + audioService.getStreamMaxVolume(stream) + "]");
}
}
- //--------------------------------------------
- // Utilities
-
- static final String LOG_V = "[v]";
- static final String LOG_W = "[w]";
- static final String LOG_OK = "[ok]";
-
- static void log(String code, String msg) {
- System.out.println(code + " " + msg);
- }
-
static String streamName(int stream) {
try {
return AudioSystem.STREAM_NAMES[stream];
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 94776f8c7cb4..3ce8e4659737 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -261,7 +261,7 @@ public final class MediaProjectionManagerService extends SystemService
@Override // Binder call
public boolean hasProjectionPermission(int uid, String packageName) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
boolean hasPermission = false;
try {
hasPermission |= checkPermission(packageName,
@@ -288,7 +288,7 @@ public final class MediaProjectionManagerService extends SystemService
}
final UserHandle callingUser = Binder.getCallingUserHandle();
- long callingToken = Binder.clearCallingIdentity();
+ final long callingToken = Binder.clearCallingIdentity();
MediaProjection projection;
try {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2acc60db52e3..35d36216e63e 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -96,7 +96,9 @@ import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.readThisIntArrayXml;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntArrayXml;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -229,6 +231,7 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.StatLogger;
+import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -239,6 +242,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
@@ -313,7 +317,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int VERSION_ADDED_NETWORK_ID = 9;
private static final int VERSION_SWITCH_UID = 10;
private static final int VERSION_ADDED_CYCLE = 11;
- private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
+ private static final int VERSION_ADDED_NETWORK_TYPES = 12;
+ private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_TYPES;
@VisibleForTesting
public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@@ -332,6 +337,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String TAG_WHITELIST = "whitelist";
private static final String TAG_RESTRICT_BACKGROUND = "restrict-background";
private static final String TAG_REVOKED_RESTRICT_BACKGROUND = "revoked-restrict-background";
+ private static final String TAG_XML_UTILS_INT_ARRAY = "int-array";
private static final String ATTR_VERSION = "version";
private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
@@ -360,6 +366,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final String ATTR_USAGE_BYTES = "usageBytes";
private static final String ATTR_USAGE_TIME = "usageTime";
private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
+ private static final String ATTR_NETWORK_TYPES = "networkTypes";
+ private static final String ATTR_XML_UTILS_NAME = "name";
private static final String ACTION_ALLOW_BACKGROUND =
"com.android.server.net.action.ALLOW_BACKGROUND";
@@ -579,7 +587,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
- /** List of apps indexed by appId and whether they have the internet permission */
+ /** List of apps indexed by uid and whether they have the internet permission */
@GuardedBy("mUidRulesFirstLock")
private final SparseBooleanArray mInternetPermissionMap = new SparseBooleanArray();
@@ -965,7 +973,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid);
// Clear the cache for the app
synchronized (mUidRulesFirstLock) {
- mInternetPermissionMap.delete(UserHandle.getAppId(uid));
+ mInternetPermissionMap.delete(uid);
updateRestrictionRulesForUidUL(uid);
}
}
@@ -2317,13 +2325,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
final int subId = readIntAttribute(in, ATTR_SUB_ID);
+ final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE);
+
+ if (version >= VERSION_ADDED_NETWORK_TYPES) {
+ final int depth = in.getDepth();
+ while (XmlUtils.nextElementWithin(in, depth)) {
+ if (TAG_XML_UTILS_INT_ARRAY.equals(in.getName())
+ && ATTR_NETWORK_TYPES.equals(
+ readStringAttribute(in, ATTR_XML_UTILS_NAME))) {
+ final int[] networkTypes =
+ readThisIntArrayXml(in, TAG_XML_UTILS_INT_ARRAY, null);
+ builder.setNetworkTypes(networkTypes);
+ }
+ }
+ }
+
final SubscriptionPlan plan = builder.build();
mSubscriptionPlans.put(subId, ArrayUtils.appendElement(
SubscriptionPlan.class, mSubscriptionPlans.get(subId), plan));
-
- final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE);
mSubscriptionPlansOwner.put(subId, ownerPackage);
-
} else if (TAG_UID_POLICY.equals(tag)) {
final int uid = readIntAttribute(in, ATTR_UID);
final int policy = readIntAttribute(in, ATTR_POLICY);
@@ -2519,6 +2539,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
writeIntAttribute(out, ATTR_LIMIT_BEHAVIOR, plan.getDataLimitBehavior());
writeLongAttribute(out, ATTR_USAGE_BYTES, plan.getDataUsageBytes());
writeLongAttribute(out, ATTR_USAGE_TIME, plan.getDataUsageTime());
+ try {
+ writeIntArrayXml(plan.getNetworkTypes(), ATTR_NETWORK_TYPES, out);
+ } catch (XmlPullParserException ignored) { }
out.endTag(null, TAG_SUBSCRIPTION_PLAN);
}
}
@@ -3316,7 +3339,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// let in core system components (like the Settings app).
final String ownerPackage = mSubscriptionPlansOwner.get(subId);
if (Objects.equals(ownerPackage, callingPackage)
- || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)) {
+ || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)
+ || (UserHandle.getCallingAppId() == android.os.Process.PHONE_UID)) {
return mSubscriptionPlans.get(subId);
} else {
Log.w(TAG, "Not returning plans because caller " + callingPackage
@@ -3448,10 +3472,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
fout.println();
- fout.print("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
- fout.print("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
- fout.print("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
- fout.print("mRestrictBackgroundChangedInBsm: " + mRestrictBackgroundChangedInBsm);
+ fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode);
+ fout.println("mRestrictBackgroundBeforeBsm: " + mRestrictBackgroundBeforeBsm);
+ fout.println("mLoadedRestrictBackground: " + mLoadedRestrictBackground);
+ fout.println("mRestrictBackgroundChangedInBsm: " + mRestrictBackgroundChangedInBsm);
fout.println();
fout.println("Network policies:");
@@ -3876,7 +3900,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// quick check: if this uid doesn't have INTERNET permission, it
// doesn't have network access anyway, so it is a waste to mess
// with it here.
- if (hasInternetPermissionUL(uid)) {
+ if (hasInternetPermissionUL(uid) && !isUidForegroundOnRestrictPowerUL(uid)) {
uidRules.put(uid, FIREWALL_RULE_DENY);
}
}
@@ -4179,16 +4203,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
@GuardedBy("mUidRulesFirstLock")
private boolean hasInternetPermissionUL(int uid) {
try {
- final int appId = UserHandle.getAppId(uid);
- final boolean hasPermission;
- if (mInternetPermissionMap.indexOfKey(appId) < 0) {
- hasPermission =
- mIPm.checkUidPermission(Manifest.permission.INTERNET, uid)
- == PackageManager.PERMISSION_GRANTED;
- mInternetPermissionMap.put(appId, hasPermission);
- } else {
- hasPermission = mInternetPermissionMap.get(appId);
+ if (mInternetPermissionMap.get(uid)) {
+ return true;
}
+ // If the cache shows that uid doesn't have internet permission,
+ // then always re-check with PackageManager just to be safe.
+ final boolean hasPermission = mIPm.checkUidPermission(Manifest.permission.INTERNET,
+ uid) == PackageManager.PERMISSION_GRANTED;
+ mInternetPermissionMap.put(uid, hasPermission);
return hasPermission;
} catch (RemoteException e) {
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index 7c1c1c7ce403..5a8fbf3d26b5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -24,7 +24,6 @@ import static android.net.TrafficStats.UID_TETHERING;
import android.Manifest;
import android.annotation.IntDef;
import android.app.AppOpsManager;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -111,8 +110,7 @@ public final class NetworkStatsAccess {
boolean hasCarrierPrivileges = tm != null &&
tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) ==
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
- boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
- DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ boolean isDeviceOwner = dpmi != null && dpmi.isActiveDeviceOwner(callingUid);
if (hasCarrierPrivileges || isDeviceOwner
|| UserHandle.getAppId(callingUid) == android.os.Process.SYSTEM_UID) {
// Carrier-privileged apps and device owners, and the system can access data usage for
@@ -126,8 +124,9 @@ public final class NetworkStatsAccess {
return NetworkStatsAccess.Level.DEVICESUMMARY;
}
- boolean isProfileOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
+ boolean isProfileOwner = dpmi != null && (dpmi.isActiveProfileOwner(callingUid)
+ || dpmi.isActiveDeviceOwner(callingUid));
if (isProfileOwner) {
// Apps with the AppOps permission, profile owners, and apps with the privileged
// permission can access data usage for all apps in this user/profile.
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
index d202a2a60738..5646c752fc90 100644
--- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -30,6 +30,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
@@ -94,39 +95,41 @@ public class NetworkStatsSubscriptionsMonitor extends
// also needed to track CBRS.
final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
- for (final int subId : newSubs) {
- final RatTypeListener match = CollectionUtils.find(mRatListeners,
- it -> it.mSubId == subId);
- if (match != null) continue;
-
- // Create listener for every newly added sub. Also store subscriberId into it to
- // prevent binder call to telephony when querying RAT. If the subscriberId is empty
- // for any reason, such as SIM PIN locked, skip registration.
- // SubscriberId will be unavailable again if 1. modem crashed 2. reboot
- // 3. re-insert SIM. If that happens, the listeners will be eventually synchronized
- // with active sub list once all subscriberIds are ready.
- final String subscriberId = mTeleManager.getSubscriberId(subId);
- if (TextUtils.isEmpty(subscriberId)) {
- Log.d(NetworkStatsService.TAG, "Empty subscriberId for newly added sub "
- + subId + ", skip listener registration");
+ // IMSI is needed for every newly added sub. Listener stores subscriberId into it to
+ // prevent binder call to telephony when querying RAT. Keep listener registration with empty
+ // IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
+ // with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
+ final List<Pair<Integer, String>> filteredNewSubs =
+ CollectionUtils.mapNotNull(newSubs, subId -> {
+ final String subscriberId = mTeleManager.getSubscriberId(subId);
+ return TextUtils.isEmpty(subscriberId) ? null : new Pair(subId, subscriberId);
+ });
+
+ for (final Pair<Integer, String> sub : filteredNewSubs) {
+ // Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
+ // suddenly change regardless of subId, such as switch IMSI feature in modem side.
+ // If that happens, register new listener with new IMSI and remove old one later.
+ if (CollectionUtils.find(mRatListeners,
+ it -> it.equalsKey(sub.first, sub.second)) != null) {
continue;
}
+
final RatTypeListener listener =
- new RatTypeListener(mExecutor, this, subId, subscriberId);
+ new RatTypeListener(mExecutor, this, sub.first, sub.second);
mRatListeners.add(listener);
// Register listener to the telephony manager that associated with specific sub.
- mTeleManager.createForSubscriptionId(subId)
+ mTeleManager.createForSubscriptionId(sub.first)
.listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
- Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + subId);
+ Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + sub.first);
}
for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
- // If the new list contains the subId of the listener, keeps it.
- final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId);
- if (match != null) continue;
-
- handleRemoveRatTypeListener(listener);
+ // If there is no subId and IMSI matched the listener, removes it.
+ if (CollectionUtils.find(filteredNewSubs,
+ it -> listener.equalsKey(it.first, it.second)) == null) {
+ handleRemoveRatTypeListener(listener);
+ }
}
}
@@ -232,5 +235,9 @@ public class NetworkStatsSubscriptionsMonitor extends
public int getSubId() {
return mSubId;
}
+
+ boolean equalsKey(int subId, @NonNull String subscriberId) {
+ return mSubId == subId && TextUtils.equals(mSubscriberId, subscriberId);
+ }
}
}
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 9f04260242d9..f70759761304 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -9,6 +9,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
},
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index a4a94c29abfa..25ad9280fa99 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -271,7 +271,7 @@ public class EventConditionProvider extends SystemConditionProviderService {
new Intent(ACTION_EVALUATE)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
alarms.cancel(pendingIntent);
if (time == 0 || time < now) {
if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 74b7bd76b047..4040f4189698 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -833,6 +833,7 @@ abstract public class ManagedServices {
public void onUserSwitched(int user) {
if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
+ unbindOtherUserServices(user);
rebindServices(true, user);
}
@@ -1219,6 +1220,27 @@ abstract public class ManagedServices {
bindToServices(componentsToBind);
}
+ /**
+ * Called when user switched to unbind all services from other users.
+ */
+ @VisibleForTesting
+ void unbindOtherUserServices(int currentUser) {
+ final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
+
+ synchronized (mMutex) {
+ final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
+ for (ManagedServiceInfo info : removableBoundServices) {
+ if (info.userid != currentUser) {
+ Set<ComponentName> toUnbind =
+ componentsToUnbind.get(info.userid, new ArraySet<>());
+ toUnbind.add(info.component);
+ componentsToUnbind.put(info.userid, toUnbind);
+ }
+ }
+ }
+ unbindFromServices(componentsToUnbind);
+ }
+
protected void unbindFromServices(SparseArray<Set<ComponentName>> componentsToUnbind) {
for (int i = 0; i < componentsToUnbind.size(); i++) {
final int userId = componentsToUnbind.keyAt(i);
@@ -1264,7 +1286,8 @@ abstract public class ManagedServices {
/**
* Version of registerService that takes the name of a service component to bind to.
*/
- private void registerService(final ComponentName name, final int userid) {
+ @VisibleForTesting
+ void registerService(final ComponentName name, final int userid) {
synchronized (mMutex) {
registerServiceLocked(name, userid);
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 18da33ce19e0..7257f522221f 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -40,15 +40,12 @@ import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.LinkedList;
-import java.util.concurrent.TimeUnit;
+import java.util.Set;
/**
* Provides an interface to write and query for notification history data for a user from a Protocol
@@ -173,8 +170,8 @@ public class NotificationHistoryDatabase {
mFileWriteHandler.post(rnr);
}
- public void deleteConversation(String pkg, String conversationId) {
- RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationId);
+ public void deleteConversations(String pkg, Set<String> conversationIds) {
+ RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationIds);
mFileWriteHandler.post(rcr);
}
@@ -467,12 +464,12 @@ public class NotificationHistoryDatabase {
final class RemoveConversationRunnable implements Runnable {
private String mPkg;
- private String mConversationId;
+ private Set<String> mConversationIds;
private NotificationHistory mNotificationHistory;
- public RemoveConversationRunnable(String pkg, String conversationId) {
+ public RemoveConversationRunnable(String pkg, Set<String> conversationIds) {
mPkg = pkg;
- mConversationId = conversationId;
+ mConversationIds = conversationIds;
}
@VisibleForTesting
@@ -482,10 +479,10 @@ public class NotificationHistoryDatabase {
@Override
public void run() {
- if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " " + mConversationId);
+ if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable " + mPkg + " " + mConversationIds);
synchronized (mLock) {
// Remove from pending history
- mBuffer.removeConversationFromWrite(mPkg, mConversationId);
+ mBuffer.removeConversationsFromWrite(mPkg, mConversationIds);
Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
while (historyFileItr.hasNext()) {
@@ -496,7 +493,8 @@ public class NotificationHistoryDatabase {
: new NotificationHistory();
readLocked(af, notificationHistory,
new NotificationHistoryFilter.Builder().build());
- if(notificationHistory.removeConversationFromWrite(mPkg, mConversationId)) {
+ if (notificationHistory.removeConversationsFromWrite(
+ mPkg, mConversationIds)) {
writeLocked(af, notificationHistory);
}
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 69a7ce90f1c6..cf3530bfe7fc 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -38,12 +38,12 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FunctionalUtils;
import com.android.server.IoThread;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* Keeps track of per-user notification histories.
@@ -167,7 +167,7 @@ public class NotificationHistoryManager {
}
}
- public void deleteConversation(String pkg, int uid, String conversationId) {
+ public void deleteConversations(String pkg, int uid, Set<String> conversationIds) {
synchronized (mLock) {
int userId = UserHandle.getUserId(uid);
final NotificationHistoryDatabase userHistory =
@@ -179,7 +179,7 @@ public class NotificationHistoryManager {
+ userId);
return;
}
- userHistory.deleteConversation(pkg, conversationId);
+ userHistory.deleteConversations(pkg, conversationIds);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index c301cd2339ec..affdcea1960b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -19,6 +19,8 @@ package com.android.server.notification;
import android.app.Notification;
import android.app.NotificationChannel;
+import java.util.Set;
+
public interface NotificationManagerInternal {
NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
@@ -28,5 +30,5 @@ public interface NotificationManagerInternal {
void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
- void onConversationRemoved(String pkg, int uid, String conversationId);
+ void onConversationRemoved(String pkg, int uid, Set<String> shortcuts);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7401403de127..03bf74f40205 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -60,6 +60,7 @@ import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
@@ -137,9 +138,9 @@ import android.app.PendingIntent;
import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UriGrantsManager;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
+import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.app.usage.UsageEvents;
@@ -309,6 +310,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
/** {@hide} */
public class NotificationManagerService extends SystemService {
@@ -407,9 +409,19 @@ public class NotificationManagerService extends SystemService {
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L;
+ /**
+ * Activity starts coming from broadcast receivers or services in response to notification and
+ * notification action clicks will be blocked for UX and performance reasons. Instead start the
+ * activity directly from the PendingIntent.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ private static final long NOTIFICATION_TRAMPOLINE_BLOCK = 167676448L;
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
+ private ActivityManagerInternal mAmi;
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
AudioManager mAudioManager;
@@ -868,7 +880,7 @@ public class NotificationManagerService extends SystemService {
(status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
if (disableNotificationEffects(null) != null) {
// cancel whatever's going on
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
@@ -879,11 +891,11 @@ public class NotificationManagerService extends SystemService {
Binder.restoreCallingIdentity(identity);
}
- identity = Binder.clearCallingIdentity();
+ final long identity2 = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
} finally {
- Binder.restoreCallingIdentity(identity);
+ Binder.restoreCallingIdentity(identity2);
}
}
}
@@ -1339,7 +1351,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void clearSoundLocked() {
mSoundNotificationKey = null;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
if (player != null) {
@@ -1354,7 +1366,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void clearVibrateLocked() {
mVibrateNotificationKey = null;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
} finally {
@@ -1528,10 +1540,10 @@ public class NotificationManagerService extends SystemService {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
!queryRestart, changeUserId, reason, null);
}
- } else if (hideNotifications) {
- hideNotificationsForPackages(pkgList);
- } else if (unhideNotifications) {
- unhideNotificationsForPackages(pkgList);
+ } else if (hideNotifications && uidList != null && (uidList.length > 0)) {
+ hideNotificationsForPackages(pkgList, uidList);
+ } else if (unhideNotifications && uidList != null && (uidList.length > 0)) {
+ unhideNotificationsForPackages(pkgList, uidList);
}
}
@@ -1886,7 +1898,7 @@ public class NotificationManagerService extends SystemService {
DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
NotificationHistoryManager historyManager, StatsManager statsManager,
- TelephonyManager telephonyManager) {
+ TelephonyManager telephonyManager, ActivityManagerInternal ami) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
@@ -1908,6 +1920,7 @@ public class NotificationManagerService extends SystemService {
mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mCompanionManager = companionManager;
mActivityManager = activityManager;
+ mAmi = ami;
mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class);
mDpm = dpm;
mUm = userManager;
@@ -2076,6 +2089,68 @@ public class NotificationManagerService extends SystemService {
mMsgPkgsAllowedAsConvos = Set.of(getStringArrayResource(
com.android.internal.R.array.config_notificationMsgPkgsAllowedAsConvos));
mStatsManager = statsManager;
+
+ // register for various Intents.
+ // If this is called within a test, make sure to unregister the intent receivers by
+ // calling onDestroy()
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_STOPPED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
+
+ IntentFilter pkgFilter = new IntentFilter();
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ pkgFilter.addDataScheme("package");
+ getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
+ null);
+
+ IntentFilter suspendedPkgFilter = new IntentFilter();
+ suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+ getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
+ suspendedPkgFilter, null, null);
+
+ IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
+ null);
+
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
+
+ IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+ getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
+
+ IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+ }
+
+ /**
+ * Cleanup broadcast receivers change listeners.
+ */
+ public void onDestroy() {
+ getContext().unregisterReceiver(mIntentReceiver);
+ getContext().unregisterReceiver(mPackageIntentReceiver);
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ getContext().unregisterReceiver(mRestoreReceiver);
+ getContext().unregisterReceiver(mLocaleChangeReceiver);
+
+ if (mDeviceConfigChangedListener != null) {
+ DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
+ }
}
protected String[] getStringArrayResource(int key) {
@@ -2124,52 +2199,8 @@ public class NotificationManagerService extends SystemService {
new NotificationHistoryManager(getContext(), handler),
mStatsManager = (StatsManager) getContext().getSystemService(
Context.STATS_MANAGER),
- getContext().getSystemService(TelephonyManager.class));
-
- // register for various Intents
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- filter.addAction(Intent.ACTION_USER_STOPPED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
-
- IntentFilter pkgFilter = new IntentFilter();
- pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
- pkgFilter.addDataScheme("package");
- getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
- null);
-
- IntentFilter suspendedPkgFilter = new IntentFilter();
- suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- suspendedPkgFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
- suspendedPkgFilter.addAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
- getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL,
- suspendedPkgFilter, null, null);
-
- IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
- getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
- null);
-
- IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
- timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
- getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
-
- IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
- getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
-
- IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
- getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
+ getContext().getSystemService(TelephonyManager.class),
+ LocalServices.getService(ActivityManagerInternal.class));
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
@@ -2193,12 +2224,6 @@ public class NotificationManagerService extends SystemService {
mDeviceConfigChangedListener);
}
- void unregisterDeviceConfigChange() {
- if (mDeviceConfigChangedListener != null) {
- DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
- }
- }
-
private void registerNotificationPreferencesPullers() {
mPullAtomCallback = new StatsPullAtomCallbackImpl();
mStatsManager.setPullAtomCallback(
@@ -2854,7 +2879,7 @@ public class NotificationManagerService extends SystemService {
callingUid);
final boolean appIsForeground;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
appIsForeground = mActivityManager.getUidImportance(callingUid)
== IMPORTANCE_FOREGROUND;
@@ -2874,7 +2899,7 @@ public class NotificationManagerService extends SystemService {
if (isAppRenderedToast && !isSystemToast && !isPackageInForegroundForToast(pkg,
callingUid)) {
boolean block;
- long id = Binder.clearCallingIdentity();
+ final long id = Binder.clearCallingIdentity();
try {
// CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK is gated on targetSdk, so block will be
// false for apps with targetSdk < R. For apps with targetSdk R+, text toasts
@@ -2900,7 +2925,7 @@ public class NotificationManagerService extends SystemService {
synchronized (mToastQueue) {
int callingPid = Binder.getCallingPid();
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
ToastRecord record;
int index = indexOfToastLocked(pkg, token);
@@ -2979,7 +3004,7 @@ public class NotificationManagerService extends SystemService {
}
synchronized (mToastQueue) {
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
int index = indexOfToastLocked(pkg, token);
if (index >= 0) {
@@ -2997,7 +3022,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void finishToken(String pkg, IBinder token) {
synchronized (mToastQueue) {
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
int index = indexOfToastLocked(pkg, token);
if (index >= 0) {
@@ -3072,6 +3097,8 @@ public class NotificationManagerService extends SystemService {
UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
+ mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+ enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
try {
getContext().sendBroadcastAsUser(
new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
@@ -3937,7 +3964,7 @@ public class NotificationManagerService extends SystemService {
public void cancelNotificationsFromListener(INotificationListener token, String[] keys) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
@@ -3975,7 +4002,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestBindListener(ComponentName component) {
checkCallerIsSystemOrSameApp(component.getPackageName());
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
ManagedServices manager =
mAssistants.isComponentEnabledForCurrentProfiles(component)
@@ -3989,7 +4016,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestUnbindListener(INotificationListener token) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// allow bound services to disable themselves
synchronized (mNotificationLock) {
@@ -4003,7 +4030,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
@@ -4062,7 +4089,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void snoozeNotificationUntilContextFromListener(INotificationListener token,
String key, String snoozeCriterionId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
@@ -4081,7 +4108,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
long duration) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
@@ -4099,7 +4126,7 @@ public class NotificationManagerService extends SystemService {
*/
@Override
public void unsnoozeNotificationFromAssistant(INotificationListener token, String key) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info =
@@ -4119,7 +4146,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void unsnoozeNotificationFromSystemListener(INotificationListener token,
String key) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info =
@@ -4146,7 +4173,7 @@ public class NotificationManagerService extends SystemService {
String tag, int id) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
@@ -4443,7 +4470,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestUnbindProvider(IConditionProvider provider) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// allow bound services to disable themselves
final ManagedServiceInfo info = mConditionProviders.checkServiceToken(provider);
@@ -4456,7 +4483,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void requestBindProvider(ComponentName component) {
checkCallerIsSystemOrSameApp(component.getPackageName());
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mConditionProviders.setComponentState(component, true);
} finally {
@@ -4529,11 +4556,11 @@ public class NotificationManagerService extends SystemService {
} catch (NameNotFoundException e) {
return false;
}
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
return checkPackagePolicyAccess(pkg)
|| mListeners.isComponentEnabledForPackage(pkg)
- || (mDpm != null &&
- mDpm.isActiveAdminWithPolicy(Binder.getCallingUid(),
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER));
+ || (mDpm != null && (mDpm.isActiveProfileOwner(Binder.getCallingUid())
+ || mDpm.isActiveDeviceOwner(Binder.getCallingUid())));
}
@Override
@@ -4742,7 +4769,7 @@ public class NotificationManagerService extends SystemService {
@Override
public List<ComponentName> getEnabledNotificationListeners(int userId) {
- checkCallerIsSystem();
+ checkNotificationListenerAccess();
return mListeners.getAllowedComponents(userId);
}
@@ -4811,7 +4838,7 @@ public class NotificationManagerService extends SystemService {
public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
boolean granted) {
Objects.requireNonNull(listener);
- checkCallerIsSystemOrShell();
+ checkNotificationListenerAccess();
final long identity = Binder.clearCallingIdentity();
try {
if (mAllowedManagedServicePackages.test(
@@ -5023,7 +5050,7 @@ public class NotificationManagerService extends SystemService {
private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException {
int uid = 0;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
uid = mPackageManager.getPackageUid(pkg, 0, user.getIdentifier());
} finally {
@@ -5084,6 +5111,14 @@ public class NotificationManagerService extends SystemService {
}
};
+ protected void checkNotificationListenerAccess() {
+ if (!isCallerSystemOrPhone()) {
+ getContext().enforceCallingPermission(
+ permission.MANAGE_NOTIFICATION_LISTENERS,
+ "Caller must hold " + permission.MANAGE_NOTIFICATION_LISTENERS);
+ }
+ }
+
@VisibleForTesting
protected void setNotificationAssistantAccessGrantedForUserInternal(
ComponentName assistant, int baseUserId, boolean granted) {
@@ -5215,8 +5250,8 @@ public class NotificationManagerService extends SystemService {
if (!summaries.containsKey(pkg)) {
// Add summary
final ApplicationInfo appInfo =
- adjustedSbn.getNotification().extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO);
+ adjustedSbn.getNotification().extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO);
final Bundle extras = new Bundle();
extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
@@ -5235,7 +5270,8 @@ public class NotificationManagerService extends SystemService {
Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
if (appIntent != null) {
summaryNotification.contentIntent = PendingIntent.getActivityAsUser(
- getContext(), 0, appIntent, 0, null, UserHandle.of(userId));
+ getContext(), 0, appIntent, PendingIntent.FLAG_IMMUTABLE, null,
+ UserHandle.of(userId));
}
final StatusBarNotification summarySbn =
new StatusBarNotification(adjustedSbn.getPackageName(),
@@ -5251,11 +5287,11 @@ public class NotificationManagerService extends SystemService {
notificationRecord.getIsAppImportanceLocked());
summaries.put(pkg, summarySbn.getKey());
}
- }
- if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
- summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
- true)) {
- mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
+ if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
+ summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
+ true)) {
+ mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
+ }
}
}
@@ -5593,8 +5629,8 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onConversationRemoved(String pkg, int uid, String conversationId) {
- onConversationRemovedInternal(pkg, uid, conversationId);
+ public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) {
+ onConversationRemovedInternal(pkg, uid, shortcuts);
}
@GuardedBy("mNotificationLock")
@@ -5823,14 +5859,13 @@ public class NotificationManagerService extends SystemService {
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}
- private void onConversationRemovedInternal(String pkg, int uid, String conversationId) {
+ private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
checkCallerIsSystem();
Preconditions.checkStringNotEmpty(pkg);
- Preconditions.checkStringNotEmpty(conversationId);
- mHistoryManager.deleteConversation(pkg, uid, conversationId);
+ mHistoryManager.deleteConversations(pkg, uid, shortcuts);
List<String> deletedChannelIds =
- mPreferencesHelper.deleteConversation(pkg, uid, conversationId);
+ mPreferencesHelper.deleteConversations(pkg, uid, shortcuts);
for (String channelId : deletedChannelIds) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
@@ -5995,13 +6030,17 @@ public class NotificationManagerService extends SystemService {
+ " cannot post for pkg " + targetPkg + " in user " + userId);
}
+ public boolean hasFlag(final int flags, final int flag) {
+ return (flags & flag) != 0;
+ }
/**
* Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking.
*
* Has side effects.
*/
- private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
+ boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag,
NotificationRecord r, boolean isAutogroup) {
+ Notification n = r.getNotification();
final String pkg = r.getSbn().getPackageName();
final boolean isSystemNotification =
isUidSystemOrPhone(uid) || ("android".equals(pkg));
@@ -6010,71 +6049,101 @@ public class NotificationManagerService extends SystemService {
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
- synchronized (mNotificationLock) {
- final int callingUid = Binder.getCallingUid();
- if (mNotificationsByKey.get(r.getSbn().getKey()) == null
- && isCallerInstantApp(callingUid, userId)) {
- // Ephemeral apps have some special constraints for notifications.
- // They are not allowed to create new notifications however they are allowed to
- // update notifications created by the system (e.g. a foreground service
- // notification).
- throw new SecurityException("Instant app " + pkg
- + " cannot create notifications");
- }
-
- // rate limit updates that aren't completed progress notifications
- if (mNotificationsByKey.get(r.getSbn().getKey()) != null
- && !r.getNotification().hasCompletedProgress()
- && !isAutogroup) {
-
- final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
- if (appEnqueueRate > mMaxPackageEnqueueRate) {
- mUsageStats.registerOverRateQuota(pkg);
- final long now = SystemClock.elapsedRealtime();
- if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
- Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
- + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
- mLastOverRateLogTime = now;
- }
- return false;
+ final int callingUid = Binder.getCallingUid();
+ if (mNotificationsByKey.get(r.getSbn().getKey()) == null
+ && isCallerInstantApp(callingUid, userId)) {
+ // Ephemeral apps have some special constraints for notifications.
+ // They are not allowed to create new notifications however they are allowed to
+ // update notifications created by the system (e.g. a foreground service
+ // notification).
+ throw new SecurityException("Instant app " + pkg
+ + " cannot create notifications");
+ }
+
+ // rate limit updates that aren't completed progress notifications
+ if (mNotificationsByKey.get(r.getSbn().getKey()) != null
+ && !r.getNotification().hasCompletedProgress()
+ && !isAutogroup) {
+
+ final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
+ if (appEnqueueRate > mMaxPackageEnqueueRate) {
+ mUsageStats.registerOverRateQuota(pkg);
+ final long now = SystemClock.elapsedRealtime();
+ if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
+ Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
+ + ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
+ mLastOverRateLogTime = now;
}
+ return false;
}
+ }
- // limit the number of non-fgs outstanding notificationrecords an app can have
- if (!r.getNotification().isForegroundService()) {
- int count = getNotificationCountLocked(pkg, userId, id, tag);
- if (count >= MAX_PACKAGE_NOTIFICATIONS) {
- mUsageStats.registerOverCountQuota(pkg);
- Slog.e(TAG, "Package has already posted or enqueued " + count
- + " notifications. Not showing more. package=" + pkg);
- return false;
- }
+ // limit the number of non-fgs outstanding notificationrecords an app can have
+ if (!n.isForegroundService()) {
+ int count = getNotificationCountLocked(pkg, userId, id, tag);
+ if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+ mUsageStats.registerOverCountQuota(pkg);
+ Slog.e(TAG, "Package has already posted or enqueued " + count
+ + " notifications. Not showing more. package=" + pkg);
+ return false;
}
}
}
- synchronized (mNotificationLock) {
- // snoozed apps
- if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
- MetricsLogger.action(r.getLogMaker()
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
- mNotificationRecordLogger.log(
- NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
- r);
- if (DBG) {
- Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
+ // bubble or inline reply that's immutable?
+ if (n.getBubbleMetadata() != null
+ && n.getBubbleMetadata().getIntent() != null
+ && hasFlag(mAmi.getPendingIntentFlags(
+ n.getBubbleMetadata().getIntent().getTarget()),
+ PendingIntent.FLAG_IMMUTABLE)) {
+ throw new IllegalArgumentException(r.getKey() + " Not posted."
+ + " PendingIntents attached to bubbles must be mutable");
+ }
+
+ if (n.actions != null) {
+ for (Notification.Action action : n.actions) {
+ if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
+ && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
+ PendingIntent.FLAG_IMMUTABLE)) {
+ throw new IllegalArgumentException(r.getKey() + " Not posted."
+ + " PendingIntents attached to actions with remote"
+ + " inputs must be mutable");
}
- mSnoozeHelper.update(userId, r);
- handleSavePolicyFile();
- return false;
}
+ }
+ if (r.getSystemGeneratedSmartActions() != null) {
+ for (Notification.Action action : r.getSystemGeneratedSmartActions()) {
+ if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null)
+ && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()),
+ PendingIntent.FLAG_IMMUTABLE)) {
+ throw new IllegalArgumentException(r.getKey() + " Not posted."
+ + " PendingIntents attached to contextual actions with remote inputs"
+ + " must be mutable");
+ }
+ }
+ }
- // blocked apps
- if (isBlocked(r, mUsageStats)) {
- return false;
+ // snoozed apps
+ if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
+ MetricsLogger.action(r.getLogMaker()
+ .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
+ mNotificationRecordLogger.log(
+ NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
+ r);
+ if (DBG) {
+ Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
}
+ mSnoozeHelper.update(userId, r);
+ handleSavePolicyFile();
+ return false;
+ }
+
+
+ // blocked apps
+ if (isBlocked(r, mUsageStats)) {
+ return false;
}
return true;
@@ -6819,7 +6888,7 @@ public class NotificationManagerService extends SystemService {
.appendPath(record.getKey()).build())
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_KEY, record.getKey()),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + record.getNotification().getTimeoutAfter(), pi);
}
@@ -7098,7 +7167,7 @@ public class NotificationManagerService extends SystemService {
boolean delayVibForSound) {
// Escalate privileges so we can use the vibrator even if the
// notifying app does not have the VIBRATE permission.
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
final VibrationEffect effect;
try {
@@ -7125,8 +7194,15 @@ public class NotificationManagerService extends SystemService {
// so need to check the notification still valide for vibrate.
synchronized (mNotificationLock) {
if (mNotificationsByKey.get(record.getKey()) != null) {
+ // Vibrator checks the appops for the op package, not the caller,
+ // so we need to add the bypass dnd flag to be heard. it's ok to
+ // always add this flag here because we've already checked that we can
+ // bypass dnd
+ AudioAttributes.Builder aab =
+ new AudioAttributes.Builder(record.getAudioAttributes())
+ .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY);
mVibrator.vibrate(record.getSbn().getUid(), record.getSbn().getOpPkg(),
- effect, "Notification (delayed)", record.getAudioAttributes());
+ effect, "Notification (delayed)", aab.build());
} else {
Slog.e(TAG, "No vibration for canceled notification : "
+ record.getKey());
@@ -7706,7 +7782,7 @@ public class NotificationManagerService extends SystemService {
// vibrate
if (canceledKey.equals(mVibrateNotificationKey)) {
mVibrateNotificationKey = null;
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mVibrator.cancel();
}
@@ -8007,7 +8083,7 @@ public class NotificationManagerService extends SystemService {
int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
boolean sendDelete, int reason, String listenerName, boolean wasPosted) {
- ArrayList<NotificationRecord> canceledNotifications = null;
+ Set<String> childNotifications = null;
for (int i = notificationList.size() - 1; i >= 0; --i) {
NotificationRecord r = notificationList.get(i);
if (includeCurrentProfiles) {
@@ -8030,20 +8106,30 @@ public class NotificationManagerService extends SystemService {
if (channelId != null && !channelId.equals(r.getChannel().getId())) {
continue;
}
- if (canceledNotifications == null) {
- canceledNotifications = new ArrayList<>();
+ if (r.getSbn().isGroup() && r.getNotification().isGroupChild()) {
+ if (childNotifications == null) {
+ childNotifications = new HashSet<>();
+ }
+ childNotifications.add(r.getKey());
+ continue;
}
notificationList.remove(i);
mNotificationsByKey.remove(r.getKey());
r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
- canceledNotifications.add(r);
cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
}
- if (canceledNotifications != null) {
- final int M = canceledNotifications.size();
- for (int i = 0; i < M; i++) {
- cancelGroupChildrenLocked(canceledNotifications.get(i), callingUid, callingPid,
- listenerName, false /* sendDelete */, flagChecker, reason);
+ if (childNotifications != null) {
+ final int M = notificationList.size();
+ for (int i = M - 1; i >= 0; i--) {
+ NotificationRecord r = notificationList.get(i);
+ if (childNotifications.contains(r.getKey())) {
+ // dismiss conditions were checked in the first loop and so don't need to be
+ // checked again
+ notificationList.remove(i);
+ mNotificationsByKey.remove(r.getKey());
+ r.recordDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_NEUTRAL);
+ cancelNotificationLocked(r, sendDelete, reason, wasPosted, listenerName);
+ }
}
updateLightsLocked();
}
@@ -8339,15 +8425,16 @@ public class NotificationManagerService extends SystemService {
return -1;
}
- @VisibleForTesting
- protected void hideNotificationsForPackages(String[] pkgs) {
+ private void hideNotificationsForPackages(@NonNull String[] pkgs, @NonNull int[] uidList) {
synchronized (mNotificationLock) {
+ Set<Integer> uidSet = Arrays.stream(uidList).boxed().collect(Collectors.toSet());
List<String> pkgList = Arrays.asList(pkgs);
List<NotificationRecord> changedNotifications = new ArrayList<>();
int numNotifications = mNotificationList.size();
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
- if (pkgList.contains(rec.getSbn().getPackageName())) {
+ if (pkgList.contains(rec.getSbn().getPackageName())
+ && uidSet.contains(rec.getUid())) {
rec.setHidden(true);
changedNotifications.add(rec);
}
@@ -8357,15 +8444,17 @@ public class NotificationManagerService extends SystemService {
}
}
- @VisibleForTesting
- protected void unhideNotificationsForPackages(String[] pkgs) {
+ private void unhideNotificationsForPackages(@NonNull String[] pkgs,
+ @NonNull int[] uidList) {
synchronized (mNotificationLock) {
+ Set<Integer> uidSet = Arrays.stream(uidList).boxed().collect(Collectors.toSet());
List<String> pkgList = Arrays.asList(pkgs);
List<NotificationRecord> changedNotifications = new ArrayList<>();
int numNotifications = mNotificationList.size();
for (int i = 0; i < numNotifications; i++) {
NotificationRecord rec = mNotificationList.get(i);
- if (pkgList.contains(rec.getSbn().getPackageName())) {
+ if (pkgList.contains(rec.getSbn().getPackageName())
+ && uidSet.contains(rec.getUid())) {
rec.setHidden(false);
changedNotifications.add(rec);
}
@@ -8618,7 +8707,7 @@ public class NotificationManagerService extends SystemService {
if (mCompanionManager == null) {
return false;
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
List<String> associations = mCompanionManager.getAssociations(
info.component.getPackageName(), info.userid);
@@ -9939,38 +10028,6 @@ public class NotificationManagerService extends SystemService {
return CollectionUtils.firstOrNull(allowedComponents);
}
- @VisibleForTesting
- protected void simulatePackageSuspendBroadcast(boolean suspend, String pkg) {
- checkCallerIsSystemOrShell();
- // only use for testing: mimic receive broadcast that package is (un)suspended
- // but does not actually (un)suspend the package
- final Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- new String[]{pkg});
-
- final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED;
- final Intent intent = new Intent(action);
- intent.putExtras(extras);
-
- mPackageIntentReceiver.onReceive(getContext(), intent);
- }
-
- @VisibleForTesting
- protected void simulatePackageDistractionBroadcast(int flag, String[] pkgs) {
- checkCallerIsSystemOrShell();
- // only use for testing: mimic receive broadcast that package is (un)distracting
- // but does not actually register that info with packagemanager
- final Bundle extras = new Bundle();
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
- extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
-
- final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
- intent.putExtras(extras);
-
- mPackageIntentReceiver.onReceive(getContext(), intent);
- }
-
/**
* Wrapper for a StatusBarNotification object that allows transfer across a oneway
* binder without sending large amounts of data over a oneway transaction.
@@ -10011,7 +10068,7 @@ public class NotificationManagerService extends SystemService {
* TODO(b/161957908): Remove dogfooder toast.
*/
private class NotificationTrampolineCallback implements BackgroundActivityStartCallback {
- private Set<String> mPackagesShown = new ArraySet<>();
+ private final Set<String> mPackagesShown = new ArraySet<>();
@Override
public IBinder getToken() {
@@ -10019,20 +10076,25 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onExclusiveTokenActivityStart(String packageName) {
- Slog.w(TAG, "Indirect notification activity start from " + packageName);
- boolean isFirstOccurrence = mPackagesShown.add(packageName);
- if (!isFirstOccurrence) {
- return;
+ public boolean isActivityStartAllowed(int uid, String packageName) {
+ boolean block = CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
+ if (block || mPackagesShown.add(packageName)) {
+ mUiHandler.post(() ->
+ Toast.makeText(getUiContext(),
+ "Indirect activity start from "
+ + packageName + ". "
+ + "This will be blocked in S.\n"
+ + "See go/s-trampolines.",
+ Toast.LENGTH_LONG).show());
}
-
- mUiHandler.post(() ->
- Toast.makeText(getUiContext(),
- "Indirect activity start from "
- + packageName + ". "
- + "This will be blocked in S.\n"
- + "See go/s-trampolines.",
- Toast.LENGTH_LONG).show());
+ String message =
+ "Indirect notification activity start (trampoline) from " + packageName;
+ if (block) {
+ Slog.e(TAG, message + " blocked");
+ return false;
+ }
+ Slog.w(TAG, message + ", this should be avoided for performance reasons");
+ return true;
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index da7864ba32c4..73272a012671 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -66,8 +66,6 @@ public class NotificationShellCmd extends ShellCommand {
+ " set_dnd [on|none (same as on)|priority|alarms|all|off (same as all)]"
+ " allow_dnd PACKAGE [user_id (current user if not specified)]\n"
+ " disallow_dnd PACKAGE [user_id (current user if not specified)]\n"
- + " suspend_package PACKAGE\n"
- + " unsuspend_package PACKAGE\n"
+ " reset_assistant_user_set [user_id (current user if not specified)]\n"
+ " get_approved_assistant [user_id (current user if not specified)]\n"
+ " post [--help | flags] TAG TEXT\n"
@@ -135,7 +133,7 @@ public class NotificationShellCmd extends ShellCommand {
}
String callingPackage = null;
final int callingUid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
if (callingUid == Process.ROOT_UID) {
callingPackage = NotificationManagerService.ROOT_PKG;
@@ -258,25 +256,6 @@ public class NotificationShellCmd extends ShellCommand {
mBinderService.setNotificationAssistantAccessGrantedForUser(cn, userId, false);
}
break;
- case "suspend_package": {
- // only use for testing
- mDirectService.simulatePackageSuspendBroadcast(true, getNextArgRequired());
- }
- break;
- case "unsuspend_package": {
- // only use for testing
- mDirectService.simulatePackageSuspendBroadcast(false, getNextArgRequired());
- }
- break;
- case "distract_package": {
- // only use for testing
- // Flag values are in
- // {@link android.content.pm.PackageManager.DistractionRestriction}.
- mDirectService.simulatePackageDistractionBroadcast(
- Integer.parseInt(getNextArgRequired()),
- getNextArgRequired().split(","));
- break;
- }
case "reset_assistant_user_set": {
int userId = ActivityManager.getCurrentUser();
if (peekNextArg() != null) {
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index b42fe929549a..9e91875bfd86 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -77,7 +77,6 @@ public class NotificationUsageStats {
private final Map<String, AggregatedStats> mStats = new HashMap<>();
private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
private ArraySet<String> mStatExpiredkeys = new ArraySet<>();
- private final SQLiteLog mSQLiteLog;
private final Context mContext;
private final Handler mHandler;
private long mLastEmitTime;
@@ -85,7 +84,6 @@ public class NotificationUsageStats {
public NotificationUsageStats(Context context) {
mContext = context;
mLastEmitTime = SystemClock.elapsedRealtime();
- mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
mHandler = new Handler(mContext.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
@@ -152,9 +150,6 @@ public class NotificationUsageStats {
stats.numUndecoratedRemoteViews += (notification.hasUndecoratedRemoteView() ? 1 : 0);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.logPosted(notification);
- }
}
/**
@@ -170,9 +165,6 @@ public class NotificationUsageStats {
stats.countApiUse(notification);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.logPosted(notification);
- }
}
/**
@@ -185,9 +177,6 @@ public class NotificationUsageStats {
stats.numRemovedByApp++;
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.logRemoved(notification);
- }
}
/**
@@ -197,9 +186,6 @@ public class NotificationUsageStats {
MetricsLogger.histogram(mContext, "note_dismiss_longevity",
(int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onDismiss();
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.logDismissed(notification);
- }
}
/**
@@ -209,9 +195,6 @@ public class NotificationUsageStats {
MetricsLogger.histogram(mContext, "note_click_longevity",
(int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onClick();
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.logClicked(notification);
- }
}
public synchronized void registerPeopleAffinity(NotificationRecord notification, boolean valid,
@@ -328,21 +311,18 @@ public class NotificationUsageStats {
// pass
}
}
- if (ENABLE_SQLITE_LOG) {
- try {
- dump.put("historical", mSQLiteLog.dumpJson(filter));
- } catch (JSONException e) {
- // pass
- }
- }
return dump;
}
public PulledStats remoteViewStats(long startMs, boolean aggregate) {
- if (ENABLE_SQLITE_LOG) {
- if (aggregate) {
- return mSQLiteLog.remoteViewAggStats(startMs);
+ if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
+ PulledStats stats = new PulledStats(startMs);
+ for (AggregatedStats as : mStats.values()) {
+ if (as.numUndecoratedRemoteViews > 0) {
+ stats.addUndecoratedPackage(as.key, as.mCreated);
+ }
}
+ return stats;
}
return null;
}
@@ -357,9 +337,6 @@ public class NotificationUsageStats {
pw.println(indent + "mStatsArrays.size(): " + mStatsArrays.size());
pw.println(indent + "mStats.size(): " + mStats.size());
}
- if (ENABLE_SQLITE_LOG) {
- mSQLiteLog.dump(pw, indent, filter);
- }
}
public synchronized void emit() {
@@ -1046,353 +1023,4 @@ public class NotificationUsageStats {
'}';
}
}
-
- private static class SQLiteLog {
- private static final String TAG = "NotificationSQLiteLog";
-
- // Message types passed to the background handler.
- private static final int MSG_POST = 1;
- private static final int MSG_CLICK = 2;
- private static final int MSG_REMOVE = 3;
- private static final int MSG_DISMISS = 4;
-
- private static final String DB_NAME = "notification_log.db";
- private static final int DB_VERSION = 7;
-
- /** Age in ms after which events are pruned from the DB. */
- private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week
- /** Delay between pruning the DB. Used to throttle pruning. */
- private static final long PRUNE_MIN_DELAY_MS = 6 * 60 * 60 * 1000L; // 6 hours
- /** Mininum number of writes between pruning the DB. Used to throttle pruning. */
- private static final long PRUNE_MIN_WRITES = 1024;
-
- // Table 'log'
- private static final String TAB_LOG = "log";
- private static final String COL_EVENT_USER_ID = "event_user_id";
- private static final String COL_EVENT_TYPE = "event_type";
- private static final String COL_EVENT_TIME = "event_time_ms";
- private static final String COL_KEY = "key";
- private static final String COL_PKG = "pkg";
- private static final String COL_NOTIFICATION_ID = "nid";
- private static final String COL_TAG = "tag";
- private static final String COL_WHEN_MS = "when_ms";
- private static final String COL_DEFAULTS = "defaults";
- private static final String COL_FLAGS = "flags";
- private static final String COL_IMPORTANCE_REQ = "importance_request";
- private static final String COL_IMPORTANCE_FINAL = "importance_final";
- private static final String COL_NOISY = "noisy";
- private static final String COL_MUTED = "muted";
- private static final String COL_DEMOTED = "demoted";
- private static final String COL_CATEGORY = "category";
- private static final String COL_ACTION_COUNT = "action_count";
- private static final String COL_POSTTIME_MS = "posttime_ms";
- private static final String COL_AIRTIME_MS = "airtime_ms";
- private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
- private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
- private static final String COL_EXPAND_COUNT = "expansion_count";
- private static final String COL_UNDECORATED = "undecorated";
-
-
- private static final int EVENT_TYPE_POST = 1;
- private static final int EVENT_TYPE_CLICK = 2;
- private static final int EVENT_TYPE_REMOVE = 3;
- private static final int EVENT_TYPE_DISMISS = 4;
-
- private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
-
- private static long sLastPruneMs;
-
- private static long sNumWrites;
- private final SQLiteOpenHelper mHelper;
-
- private final Handler mWriteHandler;
- private static final long DAY_MS = 24 * 60 * 60 * 1000;
- private static final String STATS_QUERY = "SELECT " +
- COL_EVENT_USER_ID + ", " +
- COL_PKG + ", " +
- // Bucket by day by looking at 'floor((midnight - eventTimeMs) / dayMs)'
- "CAST(((%d - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
- "AS day, " +
- "COUNT(*) AS cnt, " +
- "SUM(" + COL_MUTED + ") as muted, " +
- "SUM(" + COL_NOISY + ") as noisy, " +
- "SUM(" + COL_DEMOTED + ") as demoted, " +
- "SUM(" + COL_UNDECORATED + ") as undecorated " +
- "FROM " + TAB_LOG + " " +
- "WHERE " +
- COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
- " AND " + COL_EVENT_TIME + " > %d " +
- " GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
- private static final String UNDECORATED_QUERY = "SELECT " +
- COL_PKG + ", " +
- "MAX(" + COL_EVENT_TIME + ") as max_time " +
- "FROM " + TAB_LOG + " " +
- "WHERE " + COL_UNDECORATED + "> 0 " +
- " AND " + COL_EVENT_TIME + " > %d " +
- "GROUP BY " + COL_PKG;
-
- public SQLiteLog(Context context) {
- HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- backgroundThread.start();
- mWriteHandler = new Handler(backgroundThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- NotificationRecord r = (NotificationRecord) msg.obj;
- long nowMs = System.currentTimeMillis();
- switch (msg.what) {
- case MSG_POST:
- writeEvent(r.getSbn().getPostTime(), EVENT_TYPE_POST, r);
- break;
- case MSG_CLICK:
- writeEvent(nowMs, EVENT_TYPE_CLICK, r);
- break;
- case MSG_REMOVE:
- writeEvent(nowMs, EVENT_TYPE_REMOVE, r);
- break;
- case MSG_DISMISS:
- writeEvent(nowMs, EVENT_TYPE_DISMISS, r);
- break;
- default:
- Log.wtf(TAG, "Unknown message type: " + msg.what);
- break;
- }
- }
- };
- mHelper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TAB_LOG + " (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- COL_EVENT_USER_ID + " INT," +
- COL_EVENT_TYPE + " INT," +
- COL_EVENT_TIME + " INT," +
- COL_KEY + " TEXT," +
- COL_PKG + " TEXT," +
- COL_NOTIFICATION_ID + " INT," +
- COL_TAG + " TEXT," +
- COL_WHEN_MS + " INT," +
- COL_DEFAULTS + " INT," +
- COL_FLAGS + " INT," +
- COL_IMPORTANCE_REQ + " INT," +
- COL_IMPORTANCE_FINAL + " INT," +
- COL_NOISY + " INT," +
- COL_MUTED + " INT," +
- COL_DEMOTED + " INT," +
- COL_CATEGORY + " TEXT," +
- COL_ACTION_COUNT + " INT," +
- COL_POSTTIME_MS + " INT," +
- COL_AIRTIME_MS + " INT," +
- COL_FIRST_EXPANSIONTIME_MS + " INT," +
- COL_AIRTIME_EXPANDED_MS + " INT," +
- COL_EXPAND_COUNT + " INT," +
- COL_UNDECORATED + " INT" +
- ")");
- }
-
- @Override
- public void onConfigure(SQLiteDatabase db) {
- // Memory optimization - close idle connections after 30s of inactivity
- setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion != newVersion) {
- db.execSQL("DROP TABLE IF EXISTS " + TAB_LOG);
- onCreate(db);
- }
- }
- };
- }
-
- public void logPosted(NotificationRecord notification) {
- mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_POST, notification));
- }
-
- public void logClicked(NotificationRecord notification) {
- mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_CLICK, notification));
- }
-
- public void logRemoved(NotificationRecord notification) {
- mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_REMOVE, notification));
- }
-
- public void logDismissed(NotificationRecord notification) {
- mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
- }
-
- private JSONArray jsonPostFrequencies(DumpFilter filter) throws JSONException {
- JSONArray frequencies = new JSONArray();
- SQLiteDatabase db = mHelper.getReadableDatabase();
- long midnight = getMidnightMs();
- String q = String.format(STATS_QUERY, midnight, filter.since);
- Cursor cursor = db.rawQuery(q, null);
- try {
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- int userId = cursor.getInt(0);
- String pkg = cursor.getString(1);
- if (filter != null && !filter.matches(pkg)) continue;
- int day = cursor.getInt(2);
- int count = cursor.getInt(3);
- int muted = cursor.getInt(4);
- int noisy = cursor.getInt(5);
- int demoted = cursor.getInt(6);
- JSONObject row = new JSONObject();
- row.put("user_id", userId);
- row.put("package", pkg);
- row.put("day", day);
- row.put("count", count);
- row.put("noisy", noisy);
- row.put("muted", muted);
- row.put("demoted", demoted);
- frequencies.put(row);
- }
- } finally {
- cursor.close();
- }
- return frequencies;
- }
-
- public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
- SQLiteDatabase db = mHelper.getReadableDatabase();
- long midnight = getMidnightMs();
- String q = String.format(STATS_QUERY, midnight, filter.since);
- Cursor cursor = db.rawQuery(q, null);
- try {
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- int userId = cursor.getInt(0);
- String pkg = cursor.getString(1);
- if (filter != null && !filter.matches(pkg)) continue;
- int day = cursor.getInt(2);
- int count = cursor.getInt(3);
- int muted = cursor.getInt(4);
- int noisy = cursor.getInt(5);
- int demoted = cursor.getInt(6);
- pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg +
- ",day=" + day + ",count=" + count + ",muted=" + muted + "/" + noisy +
- ",demoted=" + demoted + "}");
- }
- } finally {
- cursor.close();
- }
- }
-
- private long getMidnightMs() {
- GregorianCalendar midnight = new GregorianCalendar();
- midnight.set(midnight.get(Calendar.YEAR), midnight.get(Calendar.MONTH),
- midnight.get(Calendar.DATE), 23, 59, 59);
- return midnight.getTimeInMillis();
- }
-
- private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
- ContentValues cv = new ContentValues();
- cv.put(COL_EVENT_USER_ID, r.getSbn().getUser().getIdentifier());
- cv.put(COL_EVENT_TIME, eventTimeMs);
- cv.put(COL_EVENT_TYPE, eventType);
- putNotificationIdentifiers(r, cv);
- if (eventType == EVENT_TYPE_POST) {
- putNotificationDetails(r, cv);
- } else {
- putPosttimeVisibility(r, cv);
- }
- cv.put(COL_UNDECORATED, (r.hasUndecoratedRemoteView() ? 1 : 0));
- SQLiteDatabase db = mHelper.getWritableDatabase();
- if (db.insert(TAB_LOG, null, cv) < 0) {
- Log.wtf(TAG, "Error while trying to insert values: " + cv);
- }
- sNumWrites++;
- pruneIfNecessary(db);
- }
-
- private void pruneIfNecessary(SQLiteDatabase db) {
- // Prune if we haven't in a while.
- long nowMs = System.currentTimeMillis();
- if (sNumWrites > PRUNE_MIN_WRITES ||
- nowMs - sLastPruneMs > PRUNE_MIN_DELAY_MS) {
- sNumWrites = 0;
- sLastPruneMs = nowMs;
- long horizonStartMs = nowMs - HORIZON_MS;
- try {
- int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?",
- new String[]{String.valueOf(horizonStartMs)});
- Log.d(TAG, "Pruned event entries: " + deletedRows);
- } catch (SQLiteFullException e) {
- Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
- }
- }
- }
-
- private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) {
- outCv.put(COL_KEY, r.getSbn().getKey());
- outCv.put(COL_PKG, r.getSbn().getPackageName());
- }
-
- private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) {
- outCv.put(COL_NOTIFICATION_ID, r.getSbn().getId());
- if (r.getSbn().getTag() != null) {
- outCv.put(COL_TAG, r.getSbn().getTag());
- }
- outCv.put(COL_WHEN_MS, r.getSbn().getPostTime());
- outCv.put(COL_FLAGS, r.getNotification().flags);
- final int before = r.stats.requestedImportance;
- final int after = r.getImportance();
- final boolean noisy = r.stats.isNoisy;
- outCv.put(COL_IMPORTANCE_REQ, before);
- outCv.put(COL_IMPORTANCE_FINAL, after);
- outCv.put(COL_DEMOTED, after < before ? 1 : 0);
- outCv.put(COL_NOISY, noisy);
- if (noisy && after < IMPORTANCE_HIGH) {
- outCv.put(COL_MUTED, 1);
- } else {
- outCv.put(COL_MUTED, 0);
- }
- if (r.getNotification().category != null) {
- outCv.put(COL_CATEGORY, r.getNotification().category);
- }
- outCv.put(COL_ACTION_COUNT, r.getNotification().actions != null ?
- r.getNotification().actions.length : 0);
- }
-
- private static void putPosttimeVisibility(NotificationRecord r, ContentValues outCv) {
- outCv.put(COL_POSTTIME_MS, r.stats.getCurrentPosttimeMs());
- outCv.put(COL_AIRTIME_MS, r.stats.getCurrentAirtimeMs());
- outCv.put(COL_EXPAND_COUNT, r.stats.userExpansionCount);
- outCv.put(COL_AIRTIME_EXPANDED_MS, r.stats.getCurrentAirtimeExpandedMs());
- outCv.put(COL_FIRST_EXPANSIONTIME_MS, r.stats.posttimeToFirstVisibleExpansionMs);
- }
-
- public void dump(PrintWriter pw, String indent, DumpFilter filter) {
- printPostFrequencies(pw, indent, filter);
- }
-
- public JSONObject dumpJson(DumpFilter filter) {
- JSONObject dump = new JSONObject();
- try {
- dump.put("post_frequency", jsonPostFrequencies(filter));
- dump.put("since", filter.since);
- dump.put("now", System.currentTimeMillis());
- } catch (JSONException e) {
- // pass
- }
- return dump;
- }
-
- public PulledStats remoteViewAggStats(long startMs) {
- PulledStats stats = new PulledStats(startMs);
- SQLiteDatabase db = mHelper.getReadableDatabase();
- String q = String.format(UNDECORATED_QUERY, startMs);
- Cursor cursor = db.rawQuery(q, null);
- try {
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- String pkg = cursor.getString(0);
- long maxTimeMs = cursor.getLong(1);
- stats.addUndecoratedPackage(pkg, maxTimeMs);
- }
- } finally {
- cursor.close();
- }
- return stats;
- }
- }
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 9cf9545d889a..bdf98f41cbbc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -79,6 +79,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class PreferencesHelper implements RankingConfig {
@@ -1428,7 +1429,8 @@ public class PreferencesHelper implements RankingConfig {
}
}
- public @NonNull List<String> deleteConversation(String pkg, int uid, String conversationId) {
+ public @NonNull List<String> deleteConversations(String pkg, int uid,
+ Set<String> conversationIds) {
synchronized (mPackagePreferences) {
List<String> deletedChannelIds = new ArrayList<>();
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
@@ -1438,7 +1440,8 @@ public class PreferencesHelper implements RankingConfig {
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
- if (conversationId.equals(nc.getConversationId())) {
+ if (nc.getConversationId() != null
+ && conversationIds.contains(nc.getConversationId())) {
nc.setDeleted(true);
LogMaker lm = getChannelLog(nc, pkg);
lm.setType(
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 961d3db867e9..14affe7b4dd5 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -223,7 +223,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
new Intent(ACTION_EVALUATE)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
alarms.cancel(pendingIntent);
if (time > now) {
if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
@@ -302,7 +302,7 @@ public class ScheduleConditionProvider extends SystemConditionProviderService {
private void readSnoozed() {
synchronized (mSnoozedForAlarm) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
final String setting = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 9a9e733cb390..f7d69fdc09d2 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -496,7 +496,7 @@ public class SnoozeHelper {
private void scheduleRepostAtTime(String pkg, String key, int userId, long time) {
Runnable runnable = () -> {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
final PendingIntent pi = createPendingIntent(pkg, key, userId);
mAm.cancel(pi);
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 5b5ec4221093..7992fea4a675 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -207,13 +207,13 @@ public abstract class ApexManager {
/**
* Returns the active apex package's name that contains the (apk) package.
*
- * @param containedPackage The (apk) package that might be in a apex
+ * @param containedPackageName The (apk) package that might be in a apex
* @return the apex package's name of {@code null} if the {@code containedPackage} is not inside
* any apex.
*/
@Nullable
public abstract String getActiveApexPackageNameContainingPackage(
- @NonNull AndroidPackage containedPackage);
+ @NonNull String containedPackageName);
/**
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
@@ -650,15 +650,14 @@ public abstract class ApexManager {
@Override
@Nullable
- public String getActiveApexPackageNameContainingPackage(AndroidPackage containedPackage) {
- Objects.requireNonNull(containedPackage);
+ public String getActiveApexPackageNameContainingPackage(String containedPackageName) {
+ Objects.requireNonNull(containedPackageName);
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
int numApksInApex = mApksInApex.size();
for (int apkInApexNum = 0; apkInApexNum < numApksInApex; apkInApexNum++) {
- if (mApksInApex.valueAt(apkInApexNum).contains(
- containedPackage.getPackageName())) {
+ if (mApksInApex.valueAt(apkInApexNum).contains(containedPackageName)) {
String apexModuleName = mApksInApex.keyAt(apkInApexNum);
int numApexPkgs = mPackageNameToApexModuleName.size();
@@ -778,12 +777,15 @@ public abstract class ApexManager {
void registerApkInApex(AndroidPackage pkg) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
- if (pkg.getBaseApkPath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+ if (pkg.getBaseApkPath().startsWith(
+ aai.apexDirectory.getAbsolutePath() + File.separator)) {
List<String> apks = mApksInApex.get(aai.apexModuleName);
if (apks == null) {
apks = Lists.newArrayList();
mApksInApex.put(aai.apexModuleName, apks);
}
+ Slog.i(TAG, "Registering " + pkg.getPackageName() + " as apk-in-apex of "
+ + aai.apexModuleName);
apks.add(pkg.getPackageName());
}
}
@@ -1080,8 +1082,8 @@ public abstract class ApexManager {
@Override
@Nullable
public String getActiveApexPackageNameContainingPackage(
- @NonNull AndroidPackage containedPackage) {
- Objects.requireNonNull(containedPackage);
+ @NonNull String containedPackageName) {
+ Objects.requireNonNull(containedPackageName);
return null;
}
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index c6c80aef4432..7e8ff9499ff8 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -16,13 +16,13 @@
package com.android.server.pm;
-import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA256;
-import static android.content.pm.Checksum.PARTIAL_MERKLE_ROOT_1M_SHA512;
-import static android.content.pm.Checksum.WHOLE_MD5;
-import static android.content.pm.Checksum.WHOLE_MERKLE_ROOT_4K_SHA256;
-import static android.content.pm.Checksum.WHOLE_SHA1;
-import static android.content.pm.Checksum.WHOLE_SHA256;
-import static android.content.pm.Checksum.WHOLE_SHA512;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
+import static android.content.pm.Checksum.TYPE_WHOLE_MD5;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
@@ -64,7 +64,6 @@ import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -168,12 +167,11 @@ public class ApkChecksums {
}
/**
- * Serialize checksums to file in binary format.
+ * Serialize checksums to the stream in binary format.
*/
- public static void writeChecksums(File file, ApkChecksum[] checksums)
+ public static void writeChecksums(OutputStream os, ApkChecksum[] checksums)
throws IOException, CertificateException {
- try (OutputStream os = new FileOutputStream(file);
- DataOutputStream dos = new DataOutputStream(os)) {
+ try (DataOutputStream dos = new DataOutputStream(os)) {
dos.writeInt(checksums.length);
for (ApkChecksum checksum : checksums) {
final String splitName = checksum.getSplitName();
@@ -184,13 +182,21 @@ public class ApkChecksums {
dos.writeUTF(splitName);
}
- dos.writeInt(checksum.getKind());
+ dos.writeInt(checksum.getType());
final byte[] valueBytes = checksum.getValue();
dos.writeInt(valueBytes.length);
dos.write(valueBytes);
- final Certificate cert = checksum.getSourceCertificate();
+ final String packageName = checksum.getInstallerPackageName();
+ if (packageName == null) {
+ dos.writeInt(-1);
+ } else {
+ dos.writeInt(packageName.length());
+ dos.writeUTF(packageName);
+ }
+
+ final Certificate cert = checksum.getInstallerCertificate();
final byte[] certBytes = (cert == null) ? null : cert.getEncoded();
if (certBytes == null) {
dos.writeInt(-1);
@@ -218,9 +224,19 @@ public class ApkChecksums {
} else {
splitName = dis.readUTF();
}
- final int kind = dis.readInt();
+
+ final int type = dis.readInt();
+
final byte[] valueBytes = new byte[dis.readInt()];
dis.read(valueBytes);
+
+ final String packageName;
+ if (dis.readInt() < 0) {
+ packageName = null;
+ } else {
+ packageName = dis.readUTF();
+ }
+
final byte[] certBytes;
final int certBytesLength = dis.readInt();
if (certBytesLength < 0) {
@@ -229,8 +245,8 @@ public class ApkChecksums {
certBytes = new byte[certBytesLength];
dis.read(certBytes);
}
- checksums[i] = new ApkChecksum(splitName, new Checksum(kind, valueBytes),
- certBytes);
+ checksums[i] = new ApkChecksum(splitName, new Checksum(type, valueBytes),
+ packageName, certBytes);
}
return checksums;
}
@@ -249,8 +265,8 @@ public class ApkChecksums {
* @param statusReceiver to receive the resulting checksums
*/
public static void getChecksums(List<Pair<String, File>> filesToChecksum,
- @Checksum.Kind int optional,
- @Checksum.Kind int required,
+ @Checksum.Type int optional,
+ @Checksum.Type int required,
@Nullable Certificate[] trustedInstallers,
@NonNull IntentSender statusReceiver,
@NonNull Injector injector) {
@@ -276,7 +292,7 @@ public class ApkChecksums {
private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
List<Map<Integer, ApkChecksum>> result,
- @Checksum.Kind int required,
+ @Checksum.Type int required,
@NonNull IntentSender statusReceiver,
@NonNull Injector injector,
long startTime) {
@@ -323,32 +339,32 @@ public class ApkChecksums {
*
* @param split split name, null for base
* @param file to fetch checksums for
- * @param kinds mask to fetch checksums
+ * @param types mask to fetch checksums
* @param trustedInstallers array of certificate to trust, two specific cases:
* null - trust anybody,
* [] - trust nobody.
* @param checksums resulting checksums
*/
private static void getAvailableApkChecksums(String split, File file,
- @Checksum.Kind int kinds,
+ @Checksum.Type int types,
@Nullable Certificate[] trustedInstallers,
Map<Integer, ApkChecksum> checksums) {
final String filePath = file.getAbsolutePath();
// Always available: FSI or IncFs.
- if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
+ if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
// Hashes in fs-verity and IncFS are always verified.
ApkChecksum checksum = extractHashFromFS(split, filePath);
if (checksum != null) {
- checksums.put(checksum.getKind(), checksum);
+ checksums.put(checksum.getType(), checksum);
}
}
// System enforced: v2/v3.
- if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired(
- PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
+ if (isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums) || isRequired(
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
Map<Integer, ApkChecksum> v2v3checksums = extractHashFromV2V3Signature(
- split, filePath, kinds);
+ split, filePath, types);
if (v2v3checksums != null) {
checksums.putAll(v2v3checksums);
}
@@ -361,9 +377,9 @@ public class ApkChecksums {
final ApkChecksum[] digests = readChecksums(digestsFile);
final Set<Signature> trusted = convertToSet(trustedInstallers);
for (ApkChecksum digest : digests) {
- if (isRequired(digest.getKind(), kinds, checksums) && isTrusted(digest,
+ if (isRequired(digest.getType(), types, checksums) && isTrusted(digest,
trusted)) {
- checksums.put(digest.getKind(), digest);
+ checksums.put(digest.getType(), digest);
}
}
} catch (IOException e) {
@@ -379,16 +395,16 @@ public class ApkChecksums {
* Whether the file is available for checksumming or we need to wait.
*/
private static boolean needToWait(File file,
- @Checksum.Kind int kinds,
+ @Checksum.Type int types,
Map<Integer, ApkChecksum> checksums,
@NonNull Injector injector) throws IOException {
- if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)
- && !isRequired(WHOLE_MD5, kinds, checksums)
- && !isRequired(WHOLE_SHA1, kinds, checksums)
- && !isRequired(WHOLE_SHA256, kinds, checksums)
- && !isRequired(WHOLE_SHA512, kinds, checksums)
- && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums)
- && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
+ if (!isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)
+ && !isRequired(TYPE_WHOLE_MD5, types, checksums)
+ && !isRequired(TYPE_WHOLE_SHA1, types, checksums)
+ && !isRequired(TYPE_WHOLE_SHA256, types, checksums)
+ && !isRequired(TYPE_WHOLE_SHA512, types, checksums)
+ && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, types, checksums)
+ && !isRequired(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, types, checksums)) {
return false;
}
@@ -416,16 +432,16 @@ public class ApkChecksums {
*
* @param split split name, null for base
* @param file to fetch checksums for
- * @param kinds mask to forcefully calculate if not available
+ * @param types mask to forcefully calculate if not available
* @param checksums resulting checksums
*/
private static void getRequiredApkChecksums(String split, File file,
- @Checksum.Kind int kinds,
+ @Checksum.Type int types,
Map<Integer, ApkChecksum> checksums) {
final String filePath = file.getAbsolutePath();
// Manually calculating required checksums if not readily available.
- if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
+ if (isRequired(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, types, checksums)) {
try {
byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
filePath, /*salt=*/null,
@@ -435,27 +451,28 @@ public class ApkChecksums {
return ByteBuffer.allocate(capacity);
}
});
- checksums.put(WHOLE_MERKLE_ROOT_4K_SHA256,
- new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, generatedRootHash));
+ checksums.put(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
+ new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256,
+ generatedRootHash));
} catch (IOException | NoSuchAlgorithmException | DigestException e) {
Slog.e(TAG, "Error calculating WHOLE_MERKLE_ROOT_4K_SHA256", e);
}
}
- calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_MD5);
- calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA1);
- calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA256);
- calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA512);
+ calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_MD5);
+ calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA1);
+ calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA256);
+ calculateChecksumIfRequested(checksums, split, file, types, TYPE_WHOLE_SHA512);
- calculatePartialChecksumsIfRequested(checksums, split, file, kinds);
+ calculatePartialChecksumsIfRequested(checksums, split, file, types);
}
- private static boolean isRequired(@Checksum.Kind int kind,
- @Checksum.Kind int kinds, Map<Integer, ApkChecksum> checksums) {
- if ((kinds & kind) == 0) {
+ private static boolean isRequired(@Checksum.Type int type,
+ @Checksum.Type int types, Map<Integer, ApkChecksum> checksums) {
+ if ((types & type) == 0) {
return false;
}
- if (checksums.containsKey(kind)) {
+ if (checksums.containsKey(type)) {
return false;
}
return true;
@@ -481,7 +498,7 @@ public class ApkChecksums {
if (trusted == null) {
return true;
}
- final Signature signature = new Signature(checksum.getSourceCertificateBytes());
+ final Signature signature = new Signature(checksum.getInstallerCertificateBytes());
return trusted.contains(signature);
}
@@ -490,7 +507,7 @@ public class ApkChecksums {
{
byte[] hash = VerityUtils.getFsverityRootHash(filePath);
if (hash != null) {
- return new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
+ return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, hash);
}
}
// v4 next
@@ -500,7 +517,7 @@ public class ApkChecksums {
byte[] hash = signer.contentDigests.getOrDefault(CONTENT_DIGEST_VERITY_CHUNKED_SHA256,
null);
if (hash != null) {
- return new ApkChecksum(split, WHOLE_MERKLE_ROOT_4K_SHA256, hash);
+ return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, hash);
}
} catch (SignatureNotFoundException e) {
// Nothing
@@ -511,7 +528,7 @@ public class ApkChecksums {
}
private static Map<Integer, ApkChecksum> extractHashFromV2V3Signature(
- String split, String filePath, int kinds) {
+ String split, String filePath, int types) {
Map<Integer, byte[]> contentDigests = null;
try {
contentDigests = ApkSignatureVerifier.verifySignaturesInternal(filePath,
@@ -528,56 +545,56 @@ public class ApkChecksums {
}
Map<Integer, ApkChecksum> checksums = new ArrayMap<>();
- if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0) {
+ if ((types & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256) != 0) {
byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA256, null);
if (hash != null) {
- checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA256,
- new ApkChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
+ checksums.put(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ new ApkChecksum(split, TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, hash));
}
}
- if ((kinds & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0) {
+ if ((types & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512) != 0) {
byte[] hash = contentDigests.getOrDefault(CONTENT_DIGEST_CHUNKED_SHA512, null);
if (hash != null) {
- checksums.put(PARTIAL_MERKLE_ROOT_1M_SHA512,
- new ApkChecksum(split, PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
+ checksums.put(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
+ new ApkChecksum(split, TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512, hash));
}
}
return checksums;
}
- private static String getMessageDigestAlgoForChecksumKind(int kind)
+ private static String getMessageDigestAlgoForChecksumKind(int type)
throws NoSuchAlgorithmException {
- switch (kind) {
- case WHOLE_MD5:
+ switch (type) {
+ case TYPE_WHOLE_MD5:
return ALGO_MD5;
- case WHOLE_SHA1:
+ case TYPE_WHOLE_SHA1:
return ALGO_SHA1;
- case WHOLE_SHA256:
+ case TYPE_WHOLE_SHA256:
return ALGO_SHA256;
- case WHOLE_SHA512:
+ case TYPE_WHOLE_SHA512:
return ALGO_SHA512;
default:
- throw new NoSuchAlgorithmException("Invalid checksum kind: " + kind);
+ throw new NoSuchAlgorithmException("Invalid checksum type: " + type);
}
}
private static void calculateChecksumIfRequested(Map<Integer, ApkChecksum> checksums,
- String split, File file, int required, int kind) {
- if ((required & kind) != 0 && !checksums.containsKey(kind)) {
- final byte[] checksum = getApkChecksum(file, kind);
+ String split, File file, int required, int type) {
+ if ((required & type) != 0 && !checksums.containsKey(type)) {
+ final byte[] checksum = getApkChecksum(file, type);
if (checksum != null) {
- checksums.put(kind, new ApkChecksum(split, kind, checksum));
+ checksums.put(type, new ApkChecksum(split, type, checksum));
}
}
}
- private static byte[] getApkChecksum(File file, int kind) {
+ private static byte[] getApkChecksum(File file, int type) {
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] dataBytes = new byte[512 * 1024];
int nread = 0;
- final String algo = getMessageDigestAlgoForChecksumKind(kind);
+ final String algo = getMessageDigestAlgoForChecksumKind(type);
MessageDigest md = MessageDigest.getInstance(algo);
while ((nread = bis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, nread);
@@ -608,9 +625,9 @@ public class ApkChecksums {
private static int getChecksumKindForContentDigestAlgo(int contentDigestAlgo) {
switch (contentDigestAlgo) {
case CONTENT_DIGEST_CHUNKED_SHA256:
- return PARTIAL_MERKLE_ROOT_1M_SHA256;
+ return TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
case CONTENT_DIGEST_CHUNKED_SHA512:
- return PARTIAL_MERKLE_ROOT_1M_SHA512;
+ return TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512;
default:
return -1;
}
@@ -619,11 +636,11 @@ public class ApkChecksums {
private static void calculatePartialChecksumsIfRequested(Map<Integer, ApkChecksum> checksums,
String split, File file, int required) {
boolean needSignatureSha256 =
- (required & PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
- PARTIAL_MERKLE_ROOT_1M_SHA256);
+ (required & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256) != 0 && !checksums.containsKey(
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
boolean needSignatureSha512 =
- (required & PARTIAL_MERKLE_ROOT_1M_SHA512) != 0 && !checksums.containsKey(
- PARTIAL_MERKLE_ROOT_1M_SHA512);
+ (required & TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512) != 0 && !checksums.containsKey(
+ TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512);
if (!needSignatureSha256 && !needSignatureSha512) {
return;
}
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 99a109367afc..520871ff40c8 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -43,6 +43,7 @@ public final class DumpState {
public static final int DUMP_SERVICE_PERMISSIONS = 1 << 24;
public static final int DUMP_APEX = 1 << 25;
public static final int DUMP_QUERIES = 1 << 26;
+ public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
new file mode 100644
index 000000000000..ababb8365c4f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -0,0 +1,480 @@
+/*
+ * 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.pm;
+
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.IncrementalStatesInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.incremental.IStorageHealthListener;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Manages state transitions of a package installed on Incremental File System. Currently manages:
+ * 1. startable state (whether a package is allowed to be launched), and
+ * 2. loading state (whether a package is still loading or has been fully loaded).
+ *
+ * The following events might change the states of a package:
+ * 1. Installation commit
+ * 2. Incremental storage health
+ * 3. Data loader stream health
+ * 4. Loading progress changes
+ *
+ * @hide
+ */
+public final class IncrementalStates {
+ private static final String TAG = "IncrementalStates";
+ private static final boolean DEBUG = false;
+ private final Handler mHandler = BackgroundThread.getHandler();
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private int mStreamStatus = IDataLoaderStatusListener.STREAM_HEALTHY;
+ @GuardedBy("mLock")
+ private int mStorageHealthStatus = IStorageHealthListener.HEALTH_STATUS_OK;
+ @GuardedBy("mLock")
+ private final LoadingState mLoadingState;
+ @GuardedBy("mLock")
+ private StartableState mStartableState;
+ @GuardedBy("mLock")
+ private Callback mCallback = null;
+ private final BiConsumer<Integer, Integer> mStatusConsumer;
+
+ public IncrementalStates() {
+ // By default the package is not startable and not fully loaded (i.e., is loading)
+ this(false, true);
+ }
+
+ public IncrementalStates(boolean isStartable, boolean isLoading) {
+ mStartableState = new StartableState(isStartable);
+ mLoadingState = new LoadingState(isLoading);
+ mStatusConsumer = new StatusConsumer();
+ }
+
+ /**
+ * Callback interface to report that the startable state of this package has changed.
+ */
+ public interface Callback {
+ /**
+ * Reports that the package is now unstartable and the unstartable reason.
+ */
+ void onPackageUnstartable(int reason);
+
+ /**
+ * Reports that the package is now startable.
+ */
+ void onPackageStartable();
+
+ /**
+ * Reports that package is fully loaded.
+ */
+ void onPackageFullyLoaded();
+ }
+
+ /**
+ * By calling this method, the caller indicates that package installation has just been
+ * committed. The package becomes startable. Set the initial loading state after the package
+ * is committed. Incremental packages are by-default loading; non-Incremental packages are not.
+ *
+ * @param isIncremental whether a package is installed on Incremental or not.
+ */
+ public void onCommit(boolean isIncremental) {
+ if (DEBUG) {
+ Slog.i(TAG, "received package commit event");
+ }
+ synchronized (mLock) {
+ if (!mStartableState.isStartable()) {
+ mStartableState.adoptNewStartableStateLocked(true);
+ }
+ if (mLoadingState.isLoading() != isIncremental) {
+ mLoadingState.adoptNewLoadingStateLocked(isIncremental);
+ }
+ }
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportStartableState,
+ IncrementalStates.this).recycleOnUse());
+ if (!isIncremental) {
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportFullyLoaded,
+ IncrementalStates.this).recycleOnUse());
+ }
+ }
+
+ private void reportStartableState() {
+ final Callback callback;
+ final boolean startable;
+ final int reason;
+ synchronized (mLock) {
+ callback = mCallback;
+ startable = mStartableState.isStartable();
+ reason = mStartableState.getUnstartableReason();
+ }
+ if (callback == null) {
+ return;
+ }
+ if (startable) {
+ callback.onPackageStartable();
+ } else {
+ callback.onPackageUnstartable(reason);
+ }
+ }
+
+ private void reportFullyLoaded() {
+ final Callback callback;
+ synchronized (mLock) {
+ callback = mCallback;
+ }
+ if (callback != null) {
+ callback.onPackageFullyLoaded();
+ }
+ }
+
+ private class StatusConsumer implements BiConsumer<Integer, Integer> {
+ @Override
+ public void accept(Integer streamStatus, Integer storageStatus) {
+ if (streamStatus == null && storageStatus == null) {
+ return;
+ }
+ final boolean oldState, newState;
+ synchronized (mLock) {
+ if (!mLoadingState.isLoading()) {
+ // Do nothing if the package is already fully loaded
+ return;
+ }
+ oldState = mStartableState.isStartable();
+ if (streamStatus != null) {
+ mStreamStatus = (Integer) streamStatus;
+ }
+ if (storageStatus != null) {
+ mStorageHealthStatus = (Integer) storageStatus;
+ }
+ updateStartableStateLocked();
+ newState = mStartableState.isStartable();
+ }
+ if (oldState != newState) {
+ mHandler.post(PooledLambda.obtainRunnable(IncrementalStates::reportStartableState,
+ IncrementalStates.this).recycleOnUse());
+ }
+ }
+ };
+
+ /**
+ * By calling this method, the caller indicates that there issues with the Incremental
+ * Storage,
+ * on which the package is installed. The state will change according to the status
+ * code defined in {@code IStorageHealthListener}.
+ */
+ public void onStorageHealthStatusChanged(int storageHealthStatus) {
+ if (DEBUG) {
+ Slog.i(TAG, "received storage health status changed event : storageHealthStatus="
+ + storageHealthStatus);
+ }
+ mStatusConsumer.accept(null, storageHealthStatus);
+ }
+
+ /**
+ * By calling this method, the caller indicates that the stream status of the package has
+ * been
+ * changed. This could indicate a streaming error. The state will change according to the
+ * status
+ * code defined in {@code IDataLoaderStatusListener}.
+ */
+ public void onStreamStatusChanged(int streamState) {
+ if (DEBUG) {
+ Slog.i(TAG, "received stream status changed event : streamState=" + streamState);
+ }
+ mStatusConsumer.accept(streamState, null);
+ }
+
+ /**
+ * Use the specified callback to report state changing events.
+ *
+ * @param callback Object to report new state.
+ */
+ public void setCallback(Callback callback) {
+ if (DEBUG) {
+ Slog.i(TAG, "registered callback");
+ }
+ synchronized (mLock) {
+ mCallback = callback;
+ }
+ }
+
+ /**
+ * Update the package loading progress to specified value. This might change startable state.
+ *
+ * @param progress Value between [0, 1].
+ */
+ public void setProgress(float progress) {
+ final boolean newLoadingState;
+ final boolean oldStartableState, newStartableState;
+ synchronized (mLock) {
+ oldStartableState = mStartableState.isStartable();
+ updateProgressLocked(progress);
+ newLoadingState = mLoadingState.isLoading();
+ newStartableState = mStartableState.isStartable();
+ }
+ if (!newLoadingState) {
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportFullyLoaded,
+ IncrementalStates.this).recycleOnUse());
+ }
+ if (newStartableState != oldStartableState) {
+ mHandler.post(PooledLambda.obtainRunnable(
+ IncrementalStates::reportStartableState,
+ IncrementalStates.this).recycleOnUse());
+ }
+ }
+
+ /**
+ * @return the current startable state.
+ */
+ public boolean isStartable() {
+ synchronized (mLock) {
+ return mStartableState.isStartable();
+ }
+ }
+
+ /**
+ * @return Whether the package is still being loaded or has been fully loaded.
+ */
+ public boolean isLoading() {
+ synchronized (mLock) {
+ return mLoadingState.isLoading();
+ }
+ }
+
+ /**
+ * @return all current states in a Parcelable.
+ */
+ public IncrementalStatesInfo getIncrementalStatesInfo() {
+ synchronized (mLock) {
+ return new IncrementalStatesInfo(mStartableState.isStartable(),
+ mLoadingState.isLoading(),
+ mLoadingState.getProgress());
+ }
+ }
+
+ /**
+ * Determine the next state based on the current state, current stream status and storage
+ * health
+ * status. If the next state is different from the current state, proceed with state
+ * change.
+ */
+ private void updateStartableStateLocked() {
+ final boolean currentState = mStartableState.isStartable();
+ boolean nextState = currentState;
+ if (!currentState) {
+ if (mStorageHealthStatus == IStorageHealthListener.HEALTH_STATUS_OK
+ && mStreamStatus == IDataLoaderStatusListener.STREAM_HEALTHY) {
+ // change from unstartable -> startable when both stream and storage are healthy
+ nextState = true;
+ }
+ } else {
+ if (mStorageHealthStatus == IStorageHealthListener.HEALTH_STATUS_UNHEALTHY) {
+ // unrecoverable if storage is unhealthy
+ nextState = false;
+ } else {
+ switch (mStreamStatus) {
+ case IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR:
+ // unrecoverable, fall through
+ case IDataLoaderStatusListener.STREAM_SOURCE_ERROR: {
+ // unrecoverable
+ nextState = false;
+ break;
+ }
+ case IDataLoaderStatusListener.STREAM_STORAGE_ERROR: {
+ if (mStorageHealthStatus != IStorageHealthListener.HEALTH_STATUS_OK) {
+ // unrecoverable if there is a pending read AND storage is limited
+ nextState = false;
+ }
+ break;
+ }
+ default:
+ // anything else, remain startable
+ break;
+ }
+ }
+ }
+ if (nextState == currentState) {
+ return;
+ }
+ mStartableState.adoptNewStartableStateLocked(nextState);
+ }
+
+ private void updateProgressLocked(float progress) {
+ if (DEBUG) {
+ Slog.i(TAG, "received progress update: " + progress);
+ }
+ mLoadingState.setProgress(progress);
+ if (1 - progress < 0.001) {
+ if (DEBUG) {
+ Slog.i(TAG, "package is fully loaded");
+ }
+ mLoadingState.setProgress(1);
+ if (mLoadingState.isLoading()) {
+ mLoadingState.adoptNewLoadingStateLocked(false);
+ }
+ // Also updates startable state if necessary
+ if (!mStartableState.isStartable()) {
+ mStartableState.adoptNewStartableStateLocked(true);
+ }
+ }
+ }
+
+ private class StartableState {
+ private boolean mIsStartable;
+ private int mUnstartableReason = PackageManager.UNSTARTABLE_REASON_UNKNOWN;
+
+ StartableState(boolean isStartable) {
+ mIsStartable = isStartable;
+ }
+
+ public boolean isStartable() {
+ return mIsStartable;
+ }
+
+ public int getUnstartableReason() {
+ return mUnstartableReason;
+ }
+
+ public void adoptNewStartableStateLocked(boolean nextState) {
+ if (DEBUG) {
+ Slog.i(TAG, "startable state changed from " + mIsStartable + " to " + nextState);
+ }
+ mIsStartable = nextState;
+ mUnstartableReason = getUnstartableReasonLocked();
+ }
+
+ private int getUnstartableReasonLocked() {
+ if (mIsStartable) {
+ return PackageManager.UNSTARTABLE_REASON_UNKNOWN;
+ }
+ // Translate stream status to reason for unstartable state
+ switch (mStreamStatus) {
+ case IDataLoaderStatusListener.STREAM_TRANSPORT_ERROR:
+ // fall through
+ case IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR:
+ // fall through
+ case IDataLoaderStatusListener.STREAM_SOURCE_ERROR: {
+ return PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR;
+ }
+ case IDataLoaderStatusListener.STREAM_STORAGE_ERROR: {
+ return PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE;
+ }
+ default:
+ return PackageManager.UNSTARTABLE_REASON_UNKNOWN;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof StartableState)) {
+ return false;
+ }
+ StartableState l = (StartableState) o;
+ return l.mIsStartable == mIsStartable;
+ }
+
+ @Override
+ public int hashCode() {
+ return Boolean.hashCode(mIsStartable);
+ }
+ }
+
+ private class LoadingState {
+ private boolean mIsLoading;
+ private float mProgress;
+
+ LoadingState(boolean isLoading) {
+ mIsLoading = isLoading;
+ mProgress = isLoading ? 0 : 1;
+ }
+
+ public boolean isLoading() {
+ return mIsLoading;
+ }
+
+ public float getProgress() {
+ return mProgress;
+ }
+
+ public void setProgress(float progress) {
+ mProgress = progress;
+ }
+
+ public void adoptNewLoadingStateLocked(boolean nextState) {
+ if (DEBUG) {
+ Slog.i(TAG, "Loading state changed from " + mIsLoading + " to " + nextState);
+ }
+ mIsLoading = nextState;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof LoadingState)) {
+ return false;
+ }
+ LoadingState l = (LoadingState) o;
+ return l.mIsLoading == mIsLoading && l.mProgress == mProgress;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = Boolean.hashCode(mIsLoading);
+ hashCode = 31 * hashCode + Float.hashCode(mProgress);
+ return hashCode;
+ }
+ }
+
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof IncrementalStates)) {
+ return false;
+ }
+ IncrementalStates l = (IncrementalStates) o;
+ return l.mStorageHealthStatus == mStorageHealthStatus
+ && l.mStreamStatus == mStreamStatus
+ && l.mStartableState.equals(mStartableState)
+ && l.mLoadingState.equals(mLoadingState);
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = mStartableState.hashCode();
+ hashCode = 31 * hashCode + mLoadingState.hashCode();
+ hashCode = 31 * hashCode + mStorageHealthStatus;
+ hashCode = 31 * hashCode + mStreamStatus;
+ return hashCode;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index 9646b9ce8edf..c0329b2b2fe6 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -771,8 +771,8 @@ class InstantAppRegistry {
for (int i = 0; i < packageCount; i++) {
final String packageToDelete = packagesToDelete.get(i);
if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
- UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
- == PackageManager.DELETE_SUCCEEDED) {
+ UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
if (file.getUsableSpace() >= neededSpace) {
return true;
}
diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
index a9a4589fdb94..4f75f04b11b6 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java
@@ -139,7 +139,7 @@ final class InstantAppResolverConnection implements DeathRecipient {
@WorkerThread
private IInstantAppResolver getRemoteInstanceLazy(String token)
throws ConnectionException, TimeoutException, InterruptedException {
- long binderToken = Binder.clearCallingIdentity();
+ final long binderToken = Binder.clearCallingIdentity();
try {
return bind(token);
} finally {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5bbe49088c02..9bb6f7892972 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -75,6 +75,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
@@ -143,6 +144,9 @@ public class LauncherAppsService extends SystemService {
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+ @GuardedBy("mListeners")
+ private boolean mIsWatchingPackageBroadcasts = false;
+
private final ShortcutChangeHandler mShortcutChangeHandler;
private final Handler mCallbackHandler;
@@ -257,7 +261,7 @@ public class LauncherAppsService extends SystemService {
verifyCallingPackage(callingPackage);
List<SessionInfo> sessionInfos = new ArrayList<>();
int[] userIds = mUm.getEnabledProfileIds(getCallingUserId());
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
for (int userId : userIds) {
sessionInfos.addAll(getPackageInstallerService().getAllSessions(userId)
@@ -281,7 +285,10 @@ public class LauncherAppsService extends SystemService {
* Register a receiver to watch for package broadcasts
*/
private void startWatchingPackageBroadcasts() {
- mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
+ if (!mIsWatchingPackageBroadcasts) {
+ mPackageMonitor.register(mContext, UserHandle.ALL, true, mCallbackHandler);
+ mIsWatchingPackageBroadcasts = true;
+ }
}
/**
@@ -291,7 +298,10 @@ public class LauncherAppsService extends SystemService {
if (DEBUG) {
Log.d(TAG, "Stopped watching for packages");
}
- mPackageMonitor.unregister();
+ if (mIsWatchingPackageBroadcasts) {
+ mPackageMonitor.unregister();
+ mIsWatchingPackageBroadcasts = false;
+ }
}
void checkCallbackCount() {
@@ -531,7 +541,7 @@ public class LauncherAppsService extends SystemService {
}
final int callingUid = injectBinderCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final PackageManagerInternal pmInt =
LocalServices.getService(PackageManagerInternal.class);
@@ -605,7 +615,7 @@ public class LauncherAppsService extends SystemService {
}
final int callingUid = injectBinderCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final PackageManagerInternal pmInt =
LocalServices.getService(PackageManagerInternal.class);
@@ -639,7 +649,7 @@ public class LauncherAppsService extends SystemService {
}
final int callingUid = injectBinderCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final PackageManagerInternal pmInt =
LocalServices.getService(PackageManagerInternal.class);
@@ -918,7 +928,7 @@ public class LauncherAppsService extends SystemService {
}
final int callingUid = injectBinderCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final int state = mIPM.getComponentEnabledSetting(component, user.getIdentifier());
switch (state) {
@@ -989,7 +999,7 @@ public class LauncherAppsService extends SystemService {
boolean canLaunch = false;
final int callingUid = injectBinderCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final PackageManagerInternal pmInt =
LocalServices.getService(PackageManagerInternal.class);
@@ -1040,7 +1050,7 @@ public class LauncherAppsService extends SystemService {
}
final Intent intent;
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
String packageName = component.getPackageName();
intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 8e3ea2e292c1..3215ee994e74 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -244,7 +244,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
mSessionsDir.mkdirs();
mApexManager = ApexManager.getInstance();
- mStagingManager = new StagingManager(this, context, apexParserSupplier);
+ mStagingManager = new StagingManager(context, apexParserSupplier);
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -619,6 +619,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
+ && !isCalledBySystemOrShell(callingUid)
+ && (mPm.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ throw new SecurityException(
+ "Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag.");
+ }
+
if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
if (mBypassNextStagedInstallerCheck) {
mBypassNextStagedInstallerCheck = false;
@@ -740,9 +747,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
- if (params.isStaged) {
- mStagingManager.createSession(session);
- }
mCallbacks.notifySessionCreated(session.sessionId, session.userId);
@@ -974,7 +978,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
} else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7474f4118f2c..4e8fe09c5aec 100755
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -28,6 +28,8 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING;
+import static android.content.pm.PackageManager.INSTALL_STAGED;
+import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
import static android.system.OsConstants.O_CREAT;
@@ -52,6 +54,7 @@ import static com.android.server.pm.PackageInstallerService.prepareStageDir;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyEventLogger;
@@ -141,6 +144,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -154,6 +158,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
@@ -168,6 +173,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String TAG = "PackageInstallerSession";
@@ -241,6 +247,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_SIGNATURE = "signature";
private static final String ATTR_CHECKSUM_KIND = "checksumKind";
private static final String ATTR_CHECKSUM_VALUE = "checksumValue";
+ private static final String ATTR_CHECKSUM_PACKAGE = "checksumPackage";
private static final String ATTR_CHECKSUM_CERTIFICATE = "checksumCertificate";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
@@ -402,10 +409,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
static class CertifiedChecksum {
final @NonNull Checksum mChecksum;
+ final @NonNull String mPackageName;
final @NonNull byte[] mCertificate;
- CertifiedChecksum(@NonNull Checksum checksum, @NonNull byte[] certificate) {
+ CertifiedChecksum(@NonNull Checksum checksum, @NonNull String packageName,
+ @NonNull byte[] certificate) {
mChecksum = checksum;
+ mPackageName = packageName;
mCertificate = certificate;
}
@@ -413,6 +423,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return mChecksum;
}
+ String getPackageName() {
+ return mPackageName;
+ }
+
byte[] getCertificate() {
return mCertificate;
}
@@ -687,7 +701,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
stagedSessionErrorMessage != null ? stagedSessionErrorMessage : "";
if (isDataLoaderInstallation()) {
- if (isApexInstallation()) {
+ if (isApexSession()) {
throw new IllegalArgumentException(
"DataLoader installation of APEX modules is not allowed.");
}
@@ -959,23 +973,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
- final PackageManagerInternal pmi = LocalServices.getService(
- PackageManagerInternal.class);
- final AndroidPackage callingInstaller = pmi.getPackage(Binder.getCallingUid());
+ final String initiatingPackageName = mInstallSource.initiatingPackageName;
+ final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ appOps.checkPackage(Binder.getCallingUid(), initiatingPackageName);
+
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ final AndroidPackage callingInstaller = pmi.getPackage(initiatingPackageName);
if (callingInstaller == null) {
throw new IllegalStateException("Can't obtain calling installer's package.");
}
// Obtaining array of certificates used for signing the installer package.
- // According to V2/V3 signing schema, the first certificate corresponds to public key
- // in the signing block.
- Signature[] certs = callingInstaller.getSigningDetails().signatures;
+ final Signature[] certs = callingInstaller.getSigningDetails().signatures;
if (certs == null || certs.length == 0 || certs[0] == null) {
throw new IllegalStateException(
"Can't obtain calling installer package's certificates.");
}
- byte[] mainCertificateBytes = certs[0].toByteArray();
+ // According to V2/V3 signing schema, the first certificate corresponds to the public key
+ // in the signing block.
+ final byte[] mainCertificateBytes = certs[0].toByteArray();
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
@@ -987,7 +1004,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
fileChecksums = new ArrayList<>();
mChecksums.put(name, fileChecksums);
}
- fileChecksums.add(new CertifiedChecksum(checksum, mainCertificateBytes));
+ fileChecksums.add(new CertifiedChecksum(checksum, initiatingPackageName,
+ mainCertificateBytes));
}
}
}
@@ -1087,6 +1105,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ private ParcelFileDescriptor openTargetInternal(String path, int flags, int mode)
+ throws IOException, ErrnoException {
+ // TODO: this should delegate to DCS so the system process avoids
+ // holding open FDs into containers.
+ final FileDescriptor fd = Os.open(path, flags, mode);
+ return new ParcelFileDescriptor(fd);
+ }
+
+ private ParcelFileDescriptor createRevocableFdInternal(RevocableFileDescriptor fd,
+ ParcelFileDescriptor pfd) throws IOException {
+ int releasedFdInt = pfd.detachFd();
+ FileDescriptor releasedFd = new FileDescriptor();
+ releasedFd.setInt$(releasedFdInt);
+ fd.init(mContext, releasedFd);
+ return fd.getRevocableFileDescriptor();
+ }
+
private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor incomingFd) throws IOException {
// Quick validity check of state, and allocate a pipe for ourselves. We
@@ -1119,21 +1154,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
Binder.restoreCallingIdentity(identity);
}
- // TODO: this should delegate to DCS so the system process avoids
- // holding open FDs into containers.
- final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
+ ParcelFileDescriptor targetPfd = openTargetInternal(target.getAbsolutePath(),
O_CREAT | O_WRONLY, 0644);
Os.chmod(target.getAbsolutePath(), 0644);
// If caller specified a total length, allocate it for them. Free up
// cache space to grow, if needed.
if (stageDir != null && lengthBytes > 0) {
- mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
+ mContext.getSystemService(StorageManager.class).allocateBytes(
+ targetPfd.getFileDescriptor(), lengthBytes,
PackageHelper.translateAllocateFlags(params.installFlags));
}
if (offsetBytes > 0) {
- Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+ Os.lseek(targetPfd.getFileDescriptor(), offsetBytes, OsConstants.SEEK_SET);
}
if (incomingFd != null) {
@@ -1143,8 +1177,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// inserted above to hold the session active.
try {
final Int64Ref last = new Int64Ref(0);
- FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
- Runnable::run, (long progress) -> {
+ FileUtils.copy(incomingFd.getFileDescriptor(), targetPfd.getFileDescriptor(),
+ lengthBytes, null, Runnable::run,
+ (long progress) -> {
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
@@ -1155,7 +1190,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
});
} finally {
- IoUtils.closeQuietly(targetFd);
+ IoUtils.closeQuietly(targetPfd);
IoUtils.closeQuietly(incomingFd);
// We're done here, so remove the "bridge" that was holding
@@ -1171,12 +1206,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
return null;
} else if (PackageInstaller.ENABLE_REVOCABLE_FD) {
- fd.init(mContext, targetFd);
- return fd.getRevocableFileDescriptor();
+ return createRevocableFdInternal(fd, targetPfd);
} else {
- bridge.setTargetFile(targetFd);
+ bridge.setTargetFile(targetPfd);
bridge.start();
- return new ParcelFileDescriptor(bridge.getClientSocket());
+ return bridge.getClientSocket();
}
} catch (ErrnoException e) {
@@ -1446,7 +1480,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mChildSessionsRemaining.removeAt(sessionIndex);
if (mChildSessionsRemaining.size() == 0) {
destroyInternal();
- dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED,
+ dispatchSessionFinished(INSTALL_SUCCEEDED,
"Session installed", null);
}
} else if (PackageInstaller.STATUS_PENDING_USER_ACTION == status) {
@@ -1521,7 +1555,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
- assertPreparedAndNotDestroyedLocked("commit");
+ assertPreparedAndNotDestroyedLocked("commit of session " + sessionId);
assertNoWriteFileTransfersOpenLocked();
final boolean isSecureFrpEnabled =
@@ -1582,19 +1616,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return false;
}
- if (isApexInstallation()) {
+ if (isApexSession()) {
validateApexInstallLocked();
} else {
validateApkInstallLocked();
}
}
- }
-
- if (params.isStaged) {
- mStagingManager.checkNonOverlappingWithStagedSessions(this);
- }
-
- synchronized (mLock) {
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session destroyed");
@@ -1651,7 +1678,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
throws PackageManagerException {
try {
assertNoWriteFileTransfersOpenLocked();
- assertPreparedAndNotDestroyedLocked("sealing of session");
+ assertPreparedAndNotDestroyedLocked("sealing of session " + sessionId);
mSealed = true;
} catch (Throwable e) {
// Convert all exceptions into package manager exceptions as only those are handled
@@ -1689,20 +1716,32 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- private void onStorageUnhealthy() {
+ private void onSessionInstallationFailure(int error, String detailedMessage) {
+ Slog.e(TAG, "Install of session " + sessionId + " failed: " + detailedMessage);
+ destroyInternal();
+ dispatchSessionFinished(error, detailedMessage, null);
+ }
+
+ private void onStorageHealthStatusChanged(int status) {
final String packageName = getPackageName();
if (TextUtils.isEmpty(packageName)) {
// The package has not been installed.
return;
}
- final PackageManagerService packageManagerService = mPm;
- mHandler.post(() -> {
- if (packageManagerService.deletePackageX(packageName,
- PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
- PackageManager.DELETE_ALL_USERS) != PackageManager.DELETE_SUCCEEDED) {
- Slog.e(TAG, "Failed to uninstall package with failed dataloader: " + packageName);
- }
- });
+ mHandler.post(PooledLambda.obtainRunnable(
+ PackageManagerService::onStorageHealthStatusChanged,
+ mPm, packageName, status, userId).recycleOnUse());
+ }
+
+ private void onStreamHealthStatusChanged(int status) {
+ final String packageName = getPackageName();
+ if (TextUtils.isEmpty(packageName)) {
+ // The package has not been installed.
+ return;
+ }
+ mHandler.post(PooledLambda.obtainRunnable(
+ PackageManagerService::onStreamStatusChanged,
+ mPm, packageName, status, userId).recycleOnUse());
}
/**
@@ -1739,15 +1778,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
try {
sealLocked();
- if (isApexInstallation()) {
- // APEX installations rely on certain fields to be populated after reboot.
- // E.g. mPackageName.
- validateApexInstallLocked();
- } else {
- // Populate mPackageName for this APK session which is required by the staging
- // manager to check duplicate apk-in-apex.
- PackageInstallerSession parent = allSessions.get(mParentSessionId);
- if (parent != null && parent.isStagedSessionReady()) {
+ // Session that are staged, ready and not multi package will be installed during
+ // this boot. As such, we need populate all the fields for successful installation.
+ if (isMultiPackage()) {
+ return;
+ }
+ final PackageInstallerSession root = hasParentSessionId()
+ ? allSessions.get(getParentSessionId())
+ : this;
+ if (root != null && root.isStagedSessionReady()) {
+ if (isApexSession()) {
+ validateApexInstallLocked();
+ } else {
validateApkInstallLocked();
}
}
@@ -1811,11 +1853,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mStagingManager.commitSession(this);
// TODO(b/136257624): CTS test fails if we don't send session finished broadcast, even
// though ideally, we just need to send session committed broadcast.
- dispatchSessionFinished(PackageManager.INSTALL_SUCCEEDED, "Session staged", null);
+ dispatchSessionFinished(INSTALL_SUCCEEDED, "Session staged", null);
return;
}
- if (isApexInstallation()) {
+ if (isApexSession()) {
destroyInternal();
dispatchSessionFinished(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"APEX packages can only be installed using staged sessions.", null);
@@ -1893,12 +1935,53 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ /**
+ * Installs apks of staged session while skipping the verification process for a committed and
+ * ready session.
+ */
+ void installStagedSession(IntentSender statusReceiver) {
+ assertCallerIsOwnerOrRootOrSystemLocked();
+ Preconditions.checkArgument(!hasParentSessionId()); // Don't allow installing child sessions
+ Preconditions.checkArgument(isCommitted() && isStagedSessionReady());
+
+ // Since staged sessions are installed during boot, the original reference to status
+ // receiver from the owner has already been lost. We can safely replace it with a
+ // status receiver from the system without effecting the flow.
+ updateRemoteStatusReceiver(statusReceiver);
+ install();
+ }
+
+ private void updateRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
+ synchronized (mLock) {
+ mRemoteStatusReceiver = remoteStatusReceiver;
+ if (isMultiPackage()) {
+ final IntentSender childIntentSender =
+ new ChildStatusIntentReceiver(mChildSessions.clone(), remoteStatusReceiver)
+ .getIntentSender();
+ for (int i = mChildSessions.size() - 1; i >= 0; --i) {
+ mChildSessions.valueAt(i).mRemoteStatusReceiver = childIntentSender;
+ }
+ }
+ }
+ }
+
+ private void install() {
+ try {
+ installNonStaged();
+ } catch (PackageManagerException e) {
+ final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+ onSessionInstallationFailure(e.error, completeMsg);
+ }
+ }
+
private void installNonStaged()
throws PackageManagerException {
- final PackageManagerService.InstallParams installingSession =
- makeInstallParams();
+ Preconditions.checkArgument(containsApkSession());
+
+ final PackageManagerService.InstallParams installingSession = makeInstallParams();
if (installingSession == null) {
- return;
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session should contain at least one apk session for installation");
}
if (isMultiPackage()) {
final List<PackageInstallerSession> childSessions;
@@ -1992,7 +2075,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// TODO(b/136257624): Some logic in this if block probably belongs in
// makeInstallParams().
- if (!params.isMultiPackage && !isApexInstallation()) {
+ if (!params.isMultiPackage && !isApexSession()) {
Objects.requireNonNull(mPackageName);
Objects.requireNonNull(mSigningDetails);
Objects.requireNonNull(mResolvedBaseFile);
@@ -2066,7 +2149,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
- if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ if (returnCode == INSTALL_SUCCEEDED) {
onVerificationComplete();
} else {
onSessionVerificationFailure(returnCode, msg);
@@ -2086,7 +2169,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mRelinquished = true;
- return mPm.new VerificationParams(user, stageDir, localObserver, params,
+ // TODO(b/169375643): Remove this workaround once b/161121612 is fixed.
+ PackageInstaller.SessionParams copiedParams = params.copy();
+ if (params.isStaged) {
+ // This is called by the pre-reboot verification. Don't enable rollback here since
+ // it has been enabled when pre-reboot verification starts.
+ copiedParams.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
+ }
+ return mPm.new VerificationParams(user, stageDir, localObserver, copiedParams,
mInstallSource, mInstallerUid, mSigningDetails, sessionId);
}
@@ -2101,20 +2191,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
- try {
- installNonStaged();
- } catch (PackageManagerException e) {
- final String completeMsg = ExceptionUtils.getCompleteMessage(e);
- Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
- destroyInternal();
- dispatchSessionFinished(e.error, completeMsg, null);
- }
+ install();
}
/**
* Stages this session for install and returns a
* {@link PackageManagerService.InstallParams} representing this new staged state.
*/
+ @Nullable
private PackageManagerService.InstallParams makeInstallParams()
throws PackageManagerException {
synchronized (mLock) {
@@ -2128,8 +2212,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
- // We've reached point of no return; call into PMS to install the stage.
- // Regardless of success or failure we always destroy session.
+ // Do not try to install apex session. Parent session will have at least one apk session.
+ if (!isMultiPackage() && isApexSession()) {
+ sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED,
+ "Apex package should have been installed by apexd", null);
+ return null;
+ }
+
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
@@ -2139,8 +2228,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
- destroyInternal();
- dispatchSessionFinished(returnCode, msg, extras);
+ if (isStaged()) {
+ sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
+ } else {
+ // We've reached point of no return; call into PMS to install the stage.
+ // Regardless of success or failure we always destroy session.
+ destroyInternal();
+ dispatchSessionFinished(returnCode, msg, extras);
+ }
}
};
@@ -2151,6 +2246,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
user = new UserHandle(userId);
}
+ if (params.isStaged) {
+ params.installFlags |= INSTALL_STAGED;
+ }
+
synchronized (mLock) {
return mPm.new InstallParams(stageDir, localObserver, params, mInstallSource, user,
mSigningDetails, mInstallerUid);
@@ -2221,10 +2320,34 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/**
* Returns true if the session is installing an APEX package.
*/
- private boolean isApexInstallation() {
+ boolean isApexSession() {
return (params.installFlags & PackageManager.INSTALL_APEX) != 0;
}
+ boolean sessionContains(Predicate<PackageInstallerSession> filter) {
+ if (!isMultiPackage()) {
+ return filter.test(this);
+ }
+ final List<PackageInstallerSession> childSessions;
+ synchronized (mLock) {
+ childSessions = getChildSessionsLocked();
+ }
+ for (PackageInstallerSession child: childSessions) {
+ if (filter.test(child)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean containsApexSession() {
+ return sessionContains((s) -> s.isApexSession());
+ }
+
+ boolean containsApkSession() {
+ return sessionContains((s) -> !s.isApexSession());
+ }
+
/**
* Validate apex install.
* <p>
@@ -2653,7 +2776,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
for (int i = 0, size = checksums.size(); i < size; ++i) {
CertifiedChecksum checksum = checksums.get(i);
result[i] = new ApkChecksum(splitName, checksum.getChecksum(),
- checksum.getCertificate());
+ checksum.getPackageName(), checksum.getCertificate());
}
return result;
}
@@ -2671,11 +2794,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
- final File targetDigestsFile = new File(stageDir,
- ApkChecksums.buildDigestsPathForApk(targetFile.getName()));
- try {
- ApkChecksums.writeChecksums(targetDigestsFile,
- createApkChecksums(splitName, checksums));
+ final String targetDigestsPath = ApkChecksums.buildDigestsPathForApk(targetFile.getName());
+ final File targetDigestsFile = new File(stageDir, targetDigestsPath);
+ try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+ ApkChecksums.writeChecksums(os, createApkChecksums(splitName, checksums));
+ final byte[] checksumsBytes = os.toByteArray();
+
+ if (!isIncrementalInstallation() || mIncrementalFileStorages == null) {
+ FileUtils.bytesToFile(targetDigestsFile.getAbsolutePath(), checksumsBytes);
+ } else {
+ mIncrementalFileStorages.makeFile(targetDigestsPath, checksumsBytes);
+ }
} catch (CertificateException e) {
throw new PackageManagerException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
"Failed to encode certificate for " + mPackageName, e);
@@ -2897,7 +3026,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
handle = NativeLibraryHelper.Handle.create(packageDir);
final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
abiOverride, isIncrementalInstallation());
- if (res != PackageManager.INSTALL_SUCCEEDED) {
+ if (res != INSTALL_SUCCEEDED) {
throw new PackageManagerException(res,
"Failed to extract native libraries, res=" + res);
}
@@ -3218,7 +3347,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (isDestroyedOrDataLoaderFinished) {
switch (status) {
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
- onStorageUnhealthy();
+ // treat as unhealthy storage
+ onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
return;
}
return;
@@ -3315,6 +3446,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
sendPendingStreaming(mContext, statusReceiver, sessionId, e.getMessage());
}
}
+ @Override
+ public void reportStreamHealth(int dataLoaderId, int streamStatus) {
+ synchronized (mLock) {
+ if (!mDestroyed && !mDataLoaderFinished) {
+ // ignore streaming status if package isn't installed
+ return;
+ }
+ }
+ onStreamHealthStatusChanged(streamStatus);
+ }
};
if (!manualStartAndDestroy) {
@@ -3334,11 +3475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (isDestroyedOrDataLoaderFinished) {
// App's installed.
- switch (status) {
- case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
- onStorageUnhealthy();
- return;
- }
+ onStorageHealthStatusChanged(status);
return;
}
@@ -3528,16 +3665,35 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
- final IntentSender statusReceiver;
- final String packageName;
+ sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
+
synchronized (mLock) {
mFinalStatus = returnCode;
mFinalMessage = msg;
+ }
+
+ final boolean success = (returnCode == INSTALL_SUCCEEDED);
+ // Send broadcast to default launcher only if it's a new install
+ // TODO(b/144270665): Secure the usage of this broadcast.
+ final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
+ if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
+ mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
+ }
+
+ mCallback.onSessionFinished(this, success);
+ if (isDataLoaderInstallation()) {
+ logDataLoaderInstallationSession(returnCode);
+ }
+ }
+
+ private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {
+ final IntentSender statusReceiver;
+ final String packageName;
+ synchronized (mLock) {
statusReceiver = mRemoteStatusReceiver;
packageName = mPackageName;
}
-
if (statusReceiver != null) {
// Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
@@ -3548,23 +3704,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
args.arg3 = extras;
args.arg4 = statusReceiver;
args.argi1 = returnCode;
-
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
}
-
- final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
-
- // Send broadcast to default launcher only if it's a new install
- // TODO(b/144270665): Secure the usage of this broadcast.
- final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING);
- if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) {
- mPm.sendSessionCommitBroadcast(generateInfoScrubbed(true /*icon*/), userId);
- }
-
- mCallback.onSessionFinished(this, success);
- if (isDataLoaderInstallation()) {
- logDataLoaderInstallationSession(returnCode);
- }
}
/** {@hide} */
@@ -3776,7 +3917,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId,
boolean showNotification, int userId, String basePackageName, int returnCode,
String msg, Bundle extras) {
- if (PackageManager.INSTALL_SUCCEEDED == returnCode && showNotification) {
+ if (INSTALL_SUCCEEDED == returnCode && showNotification) {
boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
Notification notification = PackageInstallerService.buildSuccessNotification(context,
context.getResources()
@@ -3997,9 +4138,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
CertifiedChecksum checksum = checksums.get(j);
out.startTag(null, TAG_SESSION_CHECKSUM);
writeStringAttribute(out, ATTR_NAME, fileName);
- writeIntAttribute(out, ATTR_CHECKSUM_KIND, checksum.getChecksum().getKind());
+ writeIntAttribute(out, ATTR_CHECKSUM_KIND, checksum.getChecksum().getType());
writeByteArrayAttribute(out, ATTR_CHECKSUM_VALUE,
checksum.getChecksum().getValue());
+ writeStringAttribute(out, ATTR_CHECKSUM_PACKAGE, checksum.getPackageName());
writeByteArrayAttribute(out, ATTR_CHECKSUM_CERTIFICATE,
checksum.getCertificate());
out.endTag(null, TAG_SESSION_CHECKSUM);
@@ -4152,6 +4294,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final CertifiedChecksum certifiedChecksum = new CertifiedChecksum(
new Checksum(readIntAttribute(in, ATTR_CHECKSUM_KIND, 0),
readByteArrayAttribute(in, ATTR_CHECKSUM_VALUE)),
+ readStringAttribute(in, ATTR_CHECKSUM_PACKAGE),
readByteArrayAttribute(in, ATTR_CHECKSUM_CERTIFICATE));
List<CertifiedChecksum> certifiedChecksums = checksums.get(fileName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 26bcb355b811..d3301274ed59 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -20,10 +20,8 @@ import static android.Manifest.permission.DELETE_PACKAGES;
import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.MANAGE_DEVICE_ADMINS;
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_IGNORED;
@@ -68,6 +66,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
+import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
@@ -92,10 +91,10 @@ import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.MOVE_FAILED_LOCKED_USER;
import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
-import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
+import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -282,6 +281,7 @@ import android.os.storage.StorageManagerInternal;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import android.permission.IPermissionManager;
+import android.provider.ContactsContract;
import android.provider.DeviceConfig;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -350,6 +350,7 @@ import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
+import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
@@ -687,6 +688,8 @@ public class PackageManagerService extends IPackageManager.Stub
private static final String PACKAGE_SCHEME = "package";
+ private static final String COMPANION_PACKAGE_NAME = "com.android.companiondevicemanager";
+
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -1739,15 +1742,14 @@ public class PackageManagerService extends IPackageManager.Stub
int i = 0; // filling out the above arrays
for (int n = 0; n < mPendingBroadcasts.userIdCount(); n++) {
- int packageUserId = mPendingBroadcasts.userIdAt(n);
- Iterator<Map.Entry<String, ArrayList<String>>> it
- = mPendingBroadcasts.packagesForUserId(packageUserId)
- .entrySet().iterator();
- while (it.hasNext() && i < size) {
- Map.Entry<String, ArrayList<String>> ent = it.next();
- packages[i] = ent.getKey();
- components[i] = ent.getValue();
- PackageSetting ps = mSettings.mPackages.get(ent.getKey());
+ final int packageUserId = mPendingBroadcasts.userIdAt(n);
+ final ArrayMap<String, ArrayList<String>> componentsToBroadcast =
+ mPendingBroadcasts.packagesForUserId(packageUserId);
+ final int numComponents = componentsToBroadcast.size();
+ for (int index = 0; i < size && index < numComponents; index++) {
+ packages[i] = componentsToBroadcast.keyAt(index);
+ components[i] = componentsToBroadcast.valueAt(index);
+ final PackageSetting ps = mSettings.mPackages.get(packages[i]);
uids[i] = (ps != null)
? UserHandle.getUid(packageUserId, ps.appId)
: -1;
@@ -1853,7 +1855,6 @@ public class PackageManagerService extends IPackageManager.Stub
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
removeMessages(WRITE_PACKAGE_LIST);
- mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.writePackageListLPr(msg.arg1);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
@@ -2131,11 +2132,21 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;
final String packageName = res.name;
final PackageSetting pkgSetting = succeeded ? getPackageSetting(packageName) : null;
- if (succeeded && pkgSetting == null) {
+ final boolean removedBeforeUpdate = (pkgSetting == null)
+ || (pkgSetting.isSystem() && !pkgSetting.getPathString().equals(res.pkg.getPath()));
+ if (succeeded && removedBeforeUpdate) {
Slog.e(TAG, packageName + " was removed before handlePackagePostInstall "
+ "could be executed");
res.returnCode = INSTALL_FAILED_PACKAGE_CHANGED;
res.returnMsg = "Package was removed before install could complete.";
+
+ // Remove the update failed package's older resources safely now
+ InstallArgs args = res.removedInfo != null ? res.removedInfo.args : null;
+ if (args != null) {
+ synchronized (mInstallLock) {
+ args.doPostDeleteLI(true);
+ }
+ }
notifyInstallObserver(res, installObserver);
return;
}
@@ -2143,7 +2154,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (succeeded) {
// Send the removed broadcasts
if (res.removedInfo != null) {
- res.removedInfo.sendPackageRemovedBroadcasts(killApp);
+ res.removedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
}
// Allowlist any restricted permissions first as some may be runtime
@@ -2224,8 +2235,7 @@ public class PackageManagerService extends IPackageManager.Stub
// Send installed broadcasts if the package is not a static shared lib.
if (res.pkg.getStaticSharedLibName() == null) {
- mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(
- res.pkg.getBaseApkPath());
+ mProcessLoggingHandler.invalidateBaseApkHash(res.pkg.getBaseApkPath());
// Send added for users that see the package for the first time
// sendPackageAddedForNewUsers also deals with system apps
@@ -2463,9 +2473,9 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
- public void getChecksums(@NonNull String packageName, boolean includeSplits,
- @Checksum.Kind int optional,
- @Checksum.Kind int required, @Nullable List trustedInstallers,
+ public void requestChecksums(@NonNull String packageName, boolean includeSplits,
+ @Checksum.Type int optional,
+ @Checksum.Type int required, @Nullable List trustedInstallers,
@NonNull IntentSender statusReceiver, int userId) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(statusReceiver);
@@ -2489,13 +2499,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
- final File file = filesToChecksum.get(i).second;
- if (!file.exists()) {
- throw new IllegalStateException("File not found: " + file.getPath());
- }
- }
-
final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
trustedInstallers) : null;
@@ -2690,7 +2693,8 @@ public class PackageManagerService extends IPackageManager.Stub
(i, pm) ->
new Settings(Environment.getDataDirectory(),
i.getPermissionManagerServiceInternal().getPermissionSettings(),
- RuntimePermissionsPersistence.createInstance(), lock),
+ RuntimePermissionsPersistence.createInstance(),
+ i.getPermissionManagerServiceInternal(), lock),
new Injector.LocalServicesProducer<>(ActivityTaskManagerInternal.class),
new Injector.LocalServicesProducer<>(ActivityManagerInternal.class),
new Injector.LocalServicesProducer<>(DeviceIdleInternal.class),
@@ -2707,39 +2711,43 @@ public class PackageManagerService extends IPackageManager.Stub
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest);
t.traceEnd(); // "create package manager"
- injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_LATEST_CHANGES,
- packageName -> {
- synchronized (m.mInstallLock) {
- final AndroidPackage pkg;
- final PackageSetting ps;
- final SharedUserSetting sharedUser;
- final String oldSeInfo;
- synchronized (m.mLock) {
- ps = m.mSettings.getPackageLPr(packageName);
- if (ps == null) {
- Slog.e(TAG, "Failed to find package setting " + packageName);
- return;
- }
- pkg = ps.pkg;
- sharedUser = ps.getSharedUser();
- oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
- }
-
- if (pkg == null) {
- Slog.e(TAG, "Failed to find package " + packageName);
- return;
- }
- final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser,
- m.mInjector.getCompatibility());
-
- if (!newSeInfo.equals(oldSeInfo)) {
- Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
- + oldSeInfo + " to: " + newSeInfo);
- ps.getPkgState().setOverrideSeInfo(newSeInfo);
- m.prepareAppDataAfterInstallLIF(pkg);
- }
+ final CompatChange.ChangeListener selinuxChangeListener = packageName -> {
+ synchronized (m.mInstallLock) {
+ final AndroidPackage pkg;
+ final PackageSetting ps;
+ final SharedUserSetting sharedUser;
+ final String oldSeInfo;
+ synchronized (m.mLock) {
+ ps = m.mSettings.getPackageLPr(packageName);
+ if (ps == null) {
+ Slog.e(TAG, "Failed to find package setting " + packageName);
+ return;
}
- });
+ pkg = ps.pkg;
+ sharedUser = ps.getSharedUser();
+ oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ }
+
+ if (pkg == null) {
+ Slog.e(TAG, "Failed to find package " + packageName);
+ return;
+ }
+ final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser,
+ m.mInjector.getCompatibility());
+
+ if (!newSeInfo.equals(oldSeInfo)) {
+ Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
+ + oldSeInfo + " to: " + newSeInfo);
+ ps.getPkgState().setOverrideSeInfo(newSeInfo);
+ m.prepareAppDataAfterInstallLIF(pkg);
+ }
+ }
+ };
+
+ injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_LATEST_CHANGES,
+ selinuxChangeListener);
+ injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_R_CHANGES,
+ selinuxChangeListener);
m.installWhitelistedSystemPackages();
ServiceManager.addService("package", m);
@@ -3239,17 +3247,18 @@ public class PackageManagerService extends IPackageManager.Stub
final List<String> stubSystemApps = new ArrayList<>();
if (!mOnlyCore) {
// do this first before mucking with mPackages for the "expecting better" case
- final Iterator<AndroidPackage> pkgIterator = mPackages.values().iterator();
- while (pkgIterator.hasNext()) {
- final AndroidPackage pkg = pkgIterator.next();
+ final int numPackages = mPackages.size();
+ for (int index = 0; index < numPackages; index++) {
+ final AndroidPackage pkg = mPackages.valueAt(index);
if (pkg.isStub()) {
stubSystemApps.add(pkg.getPackageName());
}
}
- final Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
- while (psit.hasNext()) {
- PackageSetting ps = psit.next();
+ // Iterates PackageSettings in reversed order because the item could be removed
+ // during the iteration.
+ for (int index = mSettings.mPackages.size() - 1; index >= 0; index--) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(index);
/*
* If this is not a system app, it can't be a
@@ -3286,7 +3295,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
- psit.remove();
+ mSettings.mPackages.removeAt(index);
logCriticalInfo(Log.WARN, "System package " + ps.name
+ " no longer exists; it's data will be wiped");
@@ -3769,6 +3778,8 @@ public class PackageManagerService extends IPackageManager.Stub
PackageParser.readConfigUseRoundIcon(mContext.getResources());
mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);
+
+ Slog.i(TAG, "Fix for b/169414761 is applied");
}
/**
@@ -4519,8 +4530,8 @@ public class PackageManagerService extends IPackageManager.Stub
AndroidPackage p = ps.pkg;
if (p != null) {
// Compute GIDs only if requested
- final int[] gids = (flags & PackageManager.GET_GIDS) == 0
- ? EMPTY_INT_ARRAY : mPermissionManager.getPackageGids(ps.name, userId);
+ final int[] gids = (flags & PackageManager.GET_GIDS) == 0 ? EMPTY_INT_ARRAY
+ : mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId));
// Compute granted permissions only if package has requested permissions
final Set<String> permissions = ArrayUtils.isEmpty(p.getRequestedPermissions())
? Collections.emptySet()
@@ -4995,13 +5006,13 @@ public class PackageManagerService extends IPackageManager.Stub
}
// TODO: Shouldn't this be checking for package installed state for userId and
// return null?
- return mPermissionManager.getPackageGids(packageName, userId);
+ return mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId));
}
if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null && ps.isMatch(flags)
&& !shouldFilterApplicationLocked(ps, callingUid, userId)) {
- return mPermissionManager.getPackageGids(packageName, userId);
+ return mPermissionManager.getGidsForUid(UserHandle.getUid(userId, ps.appId));
}
}
}
@@ -5330,8 +5341,8 @@ public class PackageManagerService extends IPackageManager.Stub
final VersionedPackage pkgToDelete = packagesToDelete.get(i);
// Delete the package synchronously (will fail of the lib used for any user).
if (deletePackageX(pkgToDelete.getPackageName(), pkgToDelete.getLongVersionCode(),
- UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
- == PackageManager.DELETE_SUCCEEDED) {
+ UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS,
+ true /*removedBySystem*/) == PackageManager.DELETE_SUCCEEDED) {
if (volume.getUsableSpace() >= neededSpace) {
return true;
}
@@ -5716,7 +5727,7 @@ public class PackageManagerService extends IPackageManager.Stub
continue;
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
PackageInfo packageInfo = getPackageInfoVersioned(declaringPackage, flags
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
@@ -6231,8 +6242,11 @@ public class PackageManagerService extends IPackageManager.Stub
synchronized (mLock) {
final AndroidPackage p = mPackages.get(packageName);
+ if (p == null) {
+ return false;
+ }
final PackageSetting ps = getPackageSetting(p.getPackageName());
- if (p == null || ps == null) {
+ if (ps == null) {
return false;
}
final int callingUid = Binder.getCallingUid();
@@ -6382,10 +6396,9 @@ public class PackageManagerService extends IPackageManager.Stub
final SharedUserSetting sus = (SharedUserSetting) obj;
final int N = sus.packages.size();
String[] res = new String[N];
- final Iterator<PackageSetting> it = sus.packages.iterator();
int i = 0;
- while (it.hasNext()) {
- PackageSetting ps = it.next();
+ for (int index = 0; index < N; index++) {
+ final PackageSetting ps = sus.packages.valueAt(index);
if (ps.getInstalled(userId)) {
res[i++] = ps.name;
}
@@ -6541,9 +6554,10 @@ public class PackageManagerService extends IPackageManager.Stub
final Object obj = mSettings.getSettingLPr(appId);
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
- final Iterator<PackageSetting> it = sus.packages.iterator();
- while (it.hasNext()) {
- if (it.next().isPrivileged()) {
+ final int numPackages = sus.packages.size();
+ for (int index = 0; index < numPackages; index++) {
+ final PackageSetting ps = sus.packages.valueAt(index);
+ if (ps.isPrivileged()) {
return true;
}
}
@@ -7646,7 +7660,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
private boolean isUserEnabled(int userId) {
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
UserInfo userInfo = mUserManager.getUserInfo(userId);
return userInfo != null && userInfo.isEnabled();
@@ -8057,7 +8071,7 @@ public class PackageManagerService extends IPackageManager.Stub
private ResolveInfo createForwardingResolveInfoUnchecked(IntentFilter filter,
int sourceUserId, int targetUserId) {
ResolveInfo forwardingResolveInfo = new ResolveInfo();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
boolean targetIsProfile;
try {
targetIsProfile = mUserManager.getUserInfo(targetUserId).isManagedProfile();
@@ -9042,10 +9056,10 @@ public class PackageManagerService extends IPackageManager.Stub
// reader
synchronized (mLock) {
- final Iterator<AndroidPackage> i = mPackages.values().iterator();
+ final int numPackages = mPackages.size();
final int userId = UserHandle.getCallingUserId();
- while (i.hasNext()) {
- final AndroidPackage p = i.next();
+ for (int index = 0; index < numPackages; index++) {
+ final AndroidPackage p = mPackages.valueAt(index);
final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
&& !p.isDirectBootAware();
@@ -9203,9 +9217,9 @@ public class PackageManagerService extends IPackageManager.Stub
// reader
synchronized (mLock) {
- final Iterator<ParsedInstrumentation> i = mInstrumentation.values().iterator();
- while (i.hasNext()) {
- final ParsedInstrumentation p = i.next();
+ final int numInstrumentations = mInstrumentation.size();
+ for (int index = 0; index < numInstrumentations; index++) {
+ final ParsedInstrumentation p = mInstrumentation.valueAt(index);
if (targetPackage == null
|| targetPackage.equals(p.getTargetPackage())) {
String packageName = p.getPackageName();
@@ -10192,7 +10206,7 @@ public class PackageManagerService extends IPackageManager.Stub
mPackageUsage.maybeWriteAsync(mSettings.mPackages);
mCompilerStats.maybeWriteAsync();
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mInstallLock) {
return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
@@ -11308,6 +11322,8 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.addRenamedPackageLPw(parsedPackage.getRealPackage(),
originalPkgSetting.name);
mTransferredPackages.add(originalPkgSetting.name);
+ } else {
+ mSettings.removeRenamedPackageLPw(parsedPackage.getPackageName());
}
}
if (pkgSetting.sharedUser != null) {
@@ -13185,7 +13201,7 @@ public class PackageManagerService extends IPackageManager.Stub
return false;
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
boolean sendAdded = false;
boolean sendRemoved = false;
@@ -13315,7 +13331,7 @@ public class PackageManagerService extends IPackageManager.Stub
info.removedUsers = new int[] {userId};
info.broadcastUsers = new int[] {userId};
info.uid = UserHandle.getUid(userId, pkgSetting.appId);
- info.sendPackageRemovedBroadcasts(true /*killApp*/);
+ info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
}
private void sendDistractingPackagesChanged(String[] pkgList, int[] uidList, int userId,
@@ -13352,7 +13368,7 @@ public class PackageManagerService extends IPackageManager.Stub
true /* requireFullPermission */, false /* checkShell */,
"getApplicationHidden for user " + userId);
PackageSetting ps;
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
// writer
synchronized (mLock) {
@@ -13407,7 +13423,7 @@ public class PackageManagerService extends IPackageManager.Stub
return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
boolean installed = false;
final boolean instantApp =
@@ -14498,7 +14514,7 @@ public class PackageManagerService extends IPackageManager.Stub
// was never set.
EventLog.writeEvent(0x534e4554, "150857253", callingUid, "");
- long binderToken = Binder.clearCallingIdentity();
+ final long binderToken = Binder.clearCallingIdentity();
try {
if (mInjector.getCompatibility().isChangeEnabledByUid(
THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE,
@@ -14790,20 +14806,6 @@ public class PackageManagerService extends IPackageManager.Stub
return mUser;
}
- /**
- * Gets the user handle for the user that the rollback agent should
- * use to look up information about this installation when enabling
- * rollback.
- */
- UserHandle getRollbackUser() {
- // The session for packages installed for "all" users is
- // associated with the "system" user.
- if (mUser == UserHandle.ALL) {
- return UserHandle.SYSTEM;
- }
- return mUser;
- }
-
HandlerParams setTraceMethod(String traceMethod) {
this.traceMethod = traceMethod;
return this;
@@ -15003,6 +15005,7 @@ public class PackageManagerService extends IPackageManager.Stub
@Nullable MultiPackageInstallParams mParentInstallParams;
final boolean forceQueryableOverride;
final int mDataLoaderType;
+ final long requiredInstalledVersionCode;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
@@ -15023,6 +15026,7 @@ public class PackageManagerService extends IPackageManager.Stub
this.installReason = PackageManager.INSTALL_REASON_UNKNOWN;
this.forceQueryableOverride = false;
this.mDataLoaderType = DataLoaderType.NONE;
+ this.requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
}
InstallParams(File stagedDir, IPackageInstallObserver2 observer,
@@ -15045,6 +15049,7 @@ public class PackageManagerService extends IPackageManager.Stub
forceQueryableOverride = sessionParams.forceQueryableOverride;
mDataLoaderType = (sessionParams.dataLoaderParams != null)
? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
+ requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
}
@Override
@@ -15191,6 +15196,18 @@ public class PackageManagerService extends IPackageManager.Stub
public void handleStartCopy() {
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
+
+ // For staged session, there is a delay between its verification and install. Device
+ // state can change within this delay and hence we need to re-verify certain conditions.
+ boolean isStaged = (installFlags & INSTALL_STAGED) != 0;
+ if (isStaged) {
+ mRet = verifyReplacingVersionCode(
+ pkgLite, requiredInstalledVersionCode, installFlags);
+ if (mRet != INSTALL_SUCCEEDED) {
+ return;
+ }
+ }
+
mRet = overrideInstallLocation(pkgLite);
}
@@ -15337,11 +15354,14 @@ public class PackageManagerService extends IPackageManager.Stub
PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
origin.resolvedPath, installFlags, packageAbiOverride);
- mRet = verifyReplacingVersionCode(pkgLite);
+ mRet = verifyReplacingVersionCode(pkgLite, requiredInstalledVersionCode, installFlags);
+ if (mRet != INSTALL_SUCCEEDED) {
+ return;
+ }
// Perform package verification and enable rollback (unless we are simply moving the
// package).
- if (mRet == INSTALL_SUCCEEDED && !origin.existing) {
+ if (!origin.existing) {
sendApkVerificationRequest(pkgLite);
if ((installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
sendEnableRollbackRequest();
@@ -15349,54 +15369,6 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private int verifyReplacingVersionCode(PackageInfoLite pkgLite) {
- String packageName = pkgLite.packageName;
- synchronized (mLock) {
- // Package which currently owns the data that the new package will own if installed.
- // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
- // will be null whereas dataOwnerPkg will contain information about the package
- // which was uninstalled while keeping its data.
- AndroidPackage dataOwnerPkg = mPackages.get(packageName);
- if (dataOwnerPkg == null) {
- PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps != null) {
- dataOwnerPkg = ps.pkg;
- }
- }
-
- if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
- if (dataOwnerPkg == null) {
- Slog.w(TAG, "Required installed version code was "
- + requiredInstalledVersionCode
- + " but package is not installed");
- return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
- }
-
- if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
- Slog.w(TAG, "Required installed version code was "
- + requiredInstalledVersionCode
- + " but actual installed version is "
- + dataOwnerPkg.getLongVersionCode());
- return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
- }
- }
-
- if (dataOwnerPkg != null) {
- if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
- dataOwnerPkg.isDebuggable())) {
- try {
- checkDowngrade(dataOwnerPkg, pkgLite);
- } catch (PackageManagerException e) {
- Slog.w(TAG, "Downgrade detected: " + e.getMessage());
- return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
- }
- }
- }
- }
- return PackageManager.INSTALL_SUCCEEDED;
- }
-
-
void sendApkVerificationRequest(PackageInfoLite pkgLite) {
final int verificationId = mPendingVerificationToken++;
@@ -16021,7 +15993,7 @@ public class PackageManagerService extends IPackageManager.Stub
return false;
}
- final File targetDir = codeFile.getParentFile();
+ final File targetDir = resolveTargetDir();
final File beforeCodeFile = codeFile;
final File afterCodeFile = getNextCodePath(targetDir, parsedPackage.getPackageName());
@@ -16064,6 +16036,17 @@ public class PackageManagerService extends IPackageManager.Stub
return true;
}
+ // TODO(b/168126411): Once staged install flow starts using the same folder as non-staged
+ // flow, we won't need this method anymore.
+ private File resolveTargetDir() {
+ boolean isStagedInstall = (installFlags & INSTALL_STAGED) != 0;
+ if (isStagedInstall) {
+ return Environment.getDataAppDirectory(null);
+ } else {
+ return codeFile.getParentFile();
+ }
+ }
+
int doPostInstall(int status, int uid) {
if (status != PackageManager.INSTALL_SUCCEEDED) {
cleanUp();
@@ -16435,12 +16418,24 @@ public class PackageManagerService extends IPackageManager.Stub
} else if (!previousUserIds.contains(userId)) {
ps.setInstallReason(installReason, userId);
}
+
+ // TODO(b/169721400): generalize Incremental States and create a Callback object
+ // that can be used for all the packages.
+ final IncrementalStatesCallback incrementalStatesCallback =
+ new IncrementalStatesCallback(ps, userId);
+ final String codePath = ps.getPathString();
+ if (IncrementalManager.isIncrementalPath(codePath) && mIncrementalManager != null) {
+ mIncrementalManager.registerCallback(codePath, incrementalStatesCallback);
+ ps.setIncrementalStatesCallback(incrementalStatesCallback);
+ }
+
// Ensure that the uninstall reason is UNKNOWN for users with the package installed.
for (int currentUserId : allUsersList) {
if (ps.getInstalled(currentUserId)) {
ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, currentUserId);
}
}
+
mSettings.writeKernelMappingLPr(ps);
}
res.name = pkgName;
@@ -17237,6 +17232,7 @@ public class PackageManagerService extends IPackageManager.Stub
mDexManager.notifyPackageUpdated(pkg.getPackageName(),
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
+ reconciledPkg.pkgSetting.setStatesOnCommit();
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
@@ -17333,6 +17329,164 @@ public class PackageManagerService extends IPackageManager.Stub
NativeLibraryHelper.waitForNativeBinariesExtraction(incrementalStorages);
}
+ private class IncrementalStatesCallback extends IPackageLoadingProgressCallback.Stub
+ implements IncrementalStates.Callback {
+ @GuardedBy("mPackageSetting")
+ private final PackageSetting mPackageSetting;
+ private final String mPackageName;
+ private final String mPathString;
+ private final int mUid;
+ private final int[] mInstalledUserIds;
+
+ IncrementalStatesCallback(PackageSetting packageSetting, int userId) {
+ mPackageSetting = packageSetting;
+ mPackageName = packageSetting.name;
+ mUid = UserHandle.getUid(userId, packageSetting.appId);
+ mPathString = packageSetting.getPathString();
+ final int[] allUserIds = resolveUserIds(userId);
+ final ArrayList<Integer> installedUserIds = new ArrayList<>();
+ for (int i = 0; i < allUserIds.length; i++) {
+ if (packageSetting.getInstalled(allUserIds[i])) {
+ installedUserIds.add(allUserIds[i]);
+ }
+ }
+ final int numInstalledUserId = installedUserIds.size();
+ mInstalledUserIds = new int[numInstalledUserId];
+ for (int i = 0; i < numInstalledUserId; i++) {
+ mInstalledUserIds[i] = installedUserIds.get(i);
+ }
+ }
+
+ @Override
+ public void onPackageLoadingProgressChanged(float progress) {
+ synchronized (mPackageSetting) {
+ mPackageSetting.setLoadingProgress(progress);
+ }
+ }
+
+ @Override
+ public void onPackageFullyLoaded() {
+ mIncrementalManager.unregisterCallback(mPathString, this);
+ final SparseArray<int[]> newBroadcastAllowList;
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(mPackageName);
+ if (ps == null) {
+ return;
+ }
+ newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
+ ps, mInstalledUserIds, mSettings.mPackages);
+ }
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, mUid);
+ extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_LOADED, mPackageName,
+ extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
+ }
+
+ @Override
+ public void onPackageUnstartable(int reason) {
+ final SparseArray<int[]> newBroadcastAllowList;
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(mPackageName);
+ if (ps == null) {
+ return;
+ }
+ newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
+ ps, mInstalledUserIds, mSettings.mPackages);
+ }
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, mUid);
+ extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+ extras.putInt(Intent.EXTRA_UNSTARTABLE_REASON, reason);
+ // send broadcast to users with this app installed
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTARTABLE, mPackageName,
+ extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
+ }
+
+ @Override
+ public void onPackageStartable() {
+ final SparseArray<int[]> newBroadcastAllowList;
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(mPackageName);
+ if (ps == null) {
+ return;
+ }
+ newBroadcastAllowList = mAppsFilter.getVisibilityAllowList(
+ ps, mInstalledUserIds, mSettings.mPackages);
+ }
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, mUid);
+ extras.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
+ // send broadcast to users with this app installed
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_STARTABLE, mPackageName,
+ extras, 0 /*flags*/,
+ null /*targetPackage*/, null /*finishedReceiver*/,
+ mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList);
+ }
+ }
+
+ /**
+ * This is an internal method that is used to indicate changes on the health status of the
+ * Incremental Storage used by an installed package with an associated user id. This might
+ * result in a change in the loading state of the package.
+ */
+ public void onStorageHealthStatusChanged(String packageName, int status, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, true, false,
+ "onStorageHealthStatusChanged");
+ final PackageSetting ps = getPackageSettingForUser(packageName, callingUid, userId);
+ if (ps == null) {
+ return;
+ }
+ ps.setStorageHealthStatus(status);
+ }
+
+ /**
+ * This is an internal method that is used to indicate changes on the stream status of the
+ * data loader used by an installed package with an associated user id. This might
+ * result in a change in the loading state of the package.
+ */
+ public void onStreamStatusChanged(String packageName, int status, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, true, false,
+ "onStreamStatusChanged");
+ final PackageSetting ps = getPackageSettingForUser(packageName, callingUid, userId);
+ if (ps == null) {
+ return;
+ }
+ ps.setStreamStatus(status);
+ }
+
+ @Nullable PackageSetting getPackageSettingForUser(String packageName, int callingUid,
+ int userId) {
+ final PackageSetting ps;
+ synchronized (mLock) {
+ ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ + " is not installed");
+ return null;
+ }
+ if (!ps.getInstalled(userId)) {
+ Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ + " is not installed for user " + userId);
+ return null;
+ }
+ if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+ Slog.w(TAG, "Failed to get package setting. Package " + packageName
+ + " is not visible to the calling app");
+ return null;
+ }
+ }
+ return ps;
+ }
+
private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
final PackageSetting pkgSetting = reconciledPkg.pkgSetting;
final PackageInstalledInfo pkgInstalledInfo = reconciledPkg.installResult;
@@ -18485,21 +18639,21 @@ public class PackageManagerService extends IPackageManager.Stub
if (doDeletePackage) {
if (!deleteAllUsers) {
returnCode = deletePackageX(internalPackageName, versionCode,
- userId, deleteFlags);
+ userId, deleteFlags, false /*removedBySystem*/);
} else {
int[] blockUninstallUserIds = getBlockUninstallForUsers(
internalPackageName, users);
// If nobody is blocking uninstall, proceed with delete for all users
if (ArrayUtils.isEmpty(blockUninstallUserIds)) {
returnCode = deletePackageX(internalPackageName, versionCode,
- userId, deleteFlags);
+ userId, deleteFlags, false /*removedBySystem*/);
} else {
// Otherwise uninstall individually for users with blockUninstalls=false
final int userFlags = deleteFlags & ~PackageManager.DELETE_ALL_USERS;
for (int userId1 : users) {
if (!ArrayUtils.contains(blockUninstallUserIds, userId1)) {
returnCode = deletePackageX(internalPackageName, versionCode,
- userId1, userFlags);
+ userId1, userFlags, false /*removedBySystem*/);
if (returnCode != PackageManager.DELETE_SUCCEEDED) {
Slog.w(TAG, "Package delete failed for user " + userId1
+ ", returnCode " + returnCode);
@@ -18726,8 +18880,12 @@ public class PackageManagerService extends IPackageManager.Stub
* updating mSettings to reflect current status
* persisting settings for later use
* sending a broadcast if necessary
+ *
+ * @param removedBySystem A boolean to indicate the package was removed automatically without
+ * the user-initiated action.
*/
- int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags) {
+ int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags,
+ boolean removedBySystem) {
final PackageRemovedInfo info = new PackageRemovedInfo(this);
final boolean res;
@@ -18824,7 +18982,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (res) {
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
- info.sendPackageRemovedBroadcasts(killApp);
+ info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
info.sendSystemPackageUpdatedBroadcasts();
}
// Force a gc here.
@@ -18910,8 +19068,8 @@ public class PackageManagerService extends IPackageManager.Stub
this.packageSender = packageSender;
}
- void sendPackageRemovedBroadcasts(boolean killApp) {
- sendPackageRemovedBroadcastInternal(killApp);
+ void sendPackageRemovedBroadcasts(boolean killApp, boolean removedBySystem) {
+ sendPackageRemovedBroadcastInternal(killApp, removedBySystem);
}
void sendSystemPackageUpdatedBroadcasts() {
@@ -18940,7 +19098,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- private void sendPackageRemovedBroadcastInternal(boolean killApp) {
+ private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
// Don't send static shared library removal broadcasts as these
// libs are visible only the the apps that depend on them an one
// cannot remove the library if it has a dependency.
@@ -18952,6 +19110,7 @@ public class PackageManagerService extends IPackageManager.Stub
extras.putInt(Intent.EXTRA_UID, removedUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
+ extras.putBoolean(Intent.EXTRA_REMOVED_BY_SYSTEM, removedBySystem);
if (isUpdate || isRemovedPackageSystemUpdate) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
@@ -19064,7 +19223,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
if ((deletedPs.sharedUser == null || deletedPs.sharedUser.packages.size() == 0)
&& !isUpdatedSystemApp(deletedPs)) {
- mPermissionManager.removePermissionsStateTEMP(removedAppId);
+ mPermissionManager.removeAppIdStateTEMP(removedAppId);
}
mPermissionManager.updatePermissions(deletedPs.name, null);
if (deletedPs.sharedUser != null) {
@@ -19903,9 +20062,9 @@ public class PackageManagerService extends IPackageManager.Stub
if (obj instanceof SharedUserSetting) {
final SharedUserSetting sus = (SharedUserSetting) obj;
int vers = Build.VERSION_CODES.CUR_DEVELOPMENT;
- final Iterator<PackageSetting> it = sus.packages.iterator();
- while (it.hasNext()) {
- final PackageSetting ps = it.next();
+ final int numPackages = sus.packages.size();
+ for (int index = 0; index < numPackages; index++) {
+ final PackageSetting ps = sus.packages.valueAt(index);
if (ps.pkg != null) {
int v = ps.pkg.getTargetSdkVersion();
if (v < vers) vers = v;
@@ -21071,7 +21230,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (packageName == null) {
return null;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (getPackageInfo(packageName, MATCH_FACTORY_ONLY, UserHandle.USER_SYSTEM) == null) {
PackageInfo packageInfo = getPackageInfo(packageName, 0, UserHandle.USER_SYSTEM);
@@ -21323,7 +21482,8 @@ public class PackageManagerService extends IPackageManager.Stub
// Prior to enabling the package, we need to decompress the APK(s) to the
// data partition and then replace the version on the system partition.
final AndroidPackage deletedPkg = pkgSetting.pkg;
- final boolean isSystemStub = deletedPkg.isStub()
+ final boolean isSystemStub = (deletedPkg != null)
+ && deletedPkg.isStub()
&& deletedPkg.isSystem();
if (isSystemStub
&& (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
@@ -21431,7 +21591,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
if (sendNow) {
int packageUid = UserHandle.getUid(userId, pkgSetting.appId);
@@ -21815,24 +21975,18 @@ public class PackageManagerService extends IPackageManager.Stub
mInjector.getStorageManagerInternal().addExternalStoragePolicy(
new StorageManagerInternal.ExternalStorageMountPolicy() {
- @Override
- public int getMountMode(int uid, String packageName) {
- if (Process.isIsolated(uid)) {
- return Zygote.MOUNT_EXTERNAL_NONE;
- }
- if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
- return Zygote.MOUNT_EXTERNAL_DEFAULT;
- }
- if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {
- return Zygote.MOUNT_EXTERNAL_READ;
- }
- return Zygote.MOUNT_EXTERNAL_WRITE;
- }
+ @Override
+ public int getMountMode(int uid, String packageName) {
+ if (Process.isIsolated(uid)) {
+ return Zygote.MOUNT_EXTERNAL_NONE;
+ }
+ return Zygote.MOUNT_EXTERNAL_DEFAULT;
+ }
- @Override
- public boolean hasExternalStorage(int uid, String packageName) {
- return true;
- }
+ @Override
+ public boolean hasExternalStorage(int uid, String packageName) {
+ return true;
+ }
});
// Now that we're mostly running, clean up stale users and apps
@@ -21936,8 +22090,6 @@ public class PackageManagerService extends IPackageManager.Stub
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
- mPermissionManager.writeStateToPackageSettingsTEMP();
-
DumpState dumpState = new DumpState();
boolean fullPreferred = false;
boolean checkin = false;
@@ -21987,6 +22139,7 @@ public class PackageManagerService extends IPackageManager.Stub
pw.println(" dexopt: dump dexopt state");
pw.println(" compiler-stats: dump compiler statistics");
pw.println(" service-permissions: dump permissions required by services");
+ pw.println(" known-packages: dump known packages");
pw.println(" <package.name>: info about given package");
return;
} else if ("--checkin".equals(opt)) {
@@ -22131,6 +22284,8 @@ public class PackageManagerService extends IPackageManager.Stub
dumpState.setDump(DumpState.DUMP_CHANGES);
} else if ("service-permissions".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_SERVICE_PERMISSIONS);
+ } else if ("known-packages".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
} else if ("write".equals(cmd)) {
synchronized (mLock) {
writeSettingsLPrTEMP();
@@ -22155,6 +22310,37 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ if (!checkin
+ && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES)
+ && packageName == null) {
+ if (dumpState.onTitlePrinted()) {
+ pw.println();
+ }
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
+ ipw.println("Known Packages:");
+ ipw.increaseIndent();
+ for (int i = 0; i < LAST_KNOWN_PACKAGE; i++) {
+ final String knownPackage = mPmInternal.knownPackageToString(i);
+ if ("Unknown".equals(knownPackage)) {
+ continue;
+ }
+ ipw.print(knownPackage);
+ ipw.println(":");
+ final String[] pkgNames = mPmInternal.getKnownPackageNames(i,
+ UserHandle.USER_SYSTEM);
+ ipw.increaseIndent();
+ if (ArrayUtils.isEmpty(pkgNames)) {
+ ipw.println("none");
+ } else {
+ for (String name : pkgNames) {
+ ipw.println(name);
+ }
+ }
+ ipw.decreaseIndent();
+ }
+ ipw.decreaseIndent();
+ }
+
if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
if (!checkin) {
if (dumpState.onTitlePrinted())
@@ -22202,9 +22388,9 @@ public class PackageManagerService extends IPackageManager.Stub
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
boolean printedHeader = false;
- final Iterator<String> it = mSharedLibraries.keySet().iterator();
- while (it.hasNext()) {
- String libName = it.next();
+ final int numSharedLibraries = mSharedLibraries.size();
+ for (int index = 0; index < numSharedLibraries; index++) {
+ final String libName = mSharedLibraries.keyAt(index);
LongSparseArray<SharedLibraryInfo> versionedLib
= mSharedLibraries.get(libName);
if (versionedLib == null) {
@@ -23819,7 +24005,6 @@ public class PackageManagerService extends IPackageManager.Stub
mDirtyUsers.remove(userId);
mUserNeedsBadging.delete(userId);
mPermissionManager.onUserRemoved(userId);
- mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.removeUserLPw(userId);
mPendingBroadcasts.remove(userId);
mInstantAppRegistry.onUserRemovedLPw(userId);
@@ -23836,9 +24021,9 @@ public class PackageManagerService extends IPackageManager.Stub
private void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
final boolean DEBUG_CLEAN_APKS = false;
int [] users = userManager.getUserIds();
- Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
- while (psit.hasNext()) {
- PackageSetting ps = psit.next();
+ final int numPackages = mSettings.mPackages.size();
+ for (int index = 0; index < numPackages; index++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(index);
if (ps.pkg == null) {
continue;
}
@@ -23874,7 +24059,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
//end run
mHandler.post(() -> deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
- userId, 0));
+ userId, 0, true /*removedBySystem*/));
}
}
}
@@ -24104,10 +24289,58 @@ public class PackageManagerService extends IPackageManager.Stub
// It is currently possible that the package will be deleted even if it is installed
// after this method returns.
mHandler.post(() -> deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
- 0, PackageManager.DELETE_ALL_USERS));
+ 0, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/));
}
}
+ private int verifyReplacingVersionCode(PackageInfoLite pkgLite,
+ long requiredInstalledVersionCode, int installFlags) {
+ String packageName = pkgLite.packageName;
+ synchronized (mLock) {
+ // Package which currently owns the data that the new package will own if installed.
+ // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
+ // will be null whereas dataOwnerPkg will contain information about the package
+ // which was uninstalled while keeping its data.
+ AndroidPackage dataOwnerPkg = mPackages.get(packageName);
+ if (dataOwnerPkg == null) {
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ dataOwnerPkg = ps.pkg;
+ }
+ }
+
+ if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
+ if (dataOwnerPkg == null) {
+ Slog.w(TAG, "Required installed version code was "
+ + requiredInstalledVersionCode
+ + " but package is not installed");
+ return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
+ }
+
+ if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
+ Slog.w(TAG, "Required installed version code was "
+ + requiredInstalledVersionCode
+ + " but actual installed version is "
+ + dataOwnerPkg.getLongVersionCode());
+ return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
+ }
+ }
+
+ if (dataOwnerPkg != null) {
+ if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
+ dataOwnerPkg.isDebuggable())) {
+ try {
+ checkDowngrade(dataOwnerPkg, pkgLite);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Downgrade detected: " + e.getMessage());
+ return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
+ }
+ }
+ }
+ }
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+
/**
* Check and throw if the given before/after packages would be considered a
* downgrade.
@@ -24600,7 +24833,7 @@ public class PackageManagerService extends IPackageManager.Stub
case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
return filterOnlySystemPackages(mAppPredictionServicePackage);
case PackageManagerInternal.PACKAGE_COMPANION:
- return filterOnlySystemPackages("com.android.companiondevicemanager");
+ return filterOnlySystemPackages(COMPANION_PACKAGE_NAME);
case PackageManagerInternal.PACKAGE_RETAIL_DEMO:
return TextUtils.isEmpty(mRetailDemoPackage)
? ArrayUtils.emptyArray(String.class)
@@ -25473,24 +25706,15 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public boolean registerInstalledLoadingProgressCallback(String packageName,
PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
- final int callingUid = Binder.getCallingUid();
- mPermissionManager.enforceCrossUserPermission(
- callingUid, userId, true, false,
- "registerLoadingProgressCallback");
- final PackageSetting ps;
- synchronized (mLock) {
- ps = mSettings.mPackages.get(packageName);
- if (ps == null) {
- Slog.w(TAG, "Failed registering loading progress callback. Package "
- + packageName + " is not installed");
- return false;
- }
- if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
- Slog.w(TAG, "Failed registering loading progress callback. Package "
- + packageName + " is not visible to the calling app");
- return false;
- }
- // TODO(b/165841827): return false if package is fully loaded
+ final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
+ userId);
+ if (ps == null) {
+ return false;
+ }
+ if (!ps.isPackageLoading()) {
+ Slog.w(TAG,
+ "Failed registering loading progress callback. Package is fully loaded.");
+ return false;
}
if (mIncrementalManager == null) {
Slog.w(TAG,
@@ -25541,11 +25765,11 @@ public class PackageManagerService extends IPackageManager.Stub
}
ArraySet<PackageSetting> packages = packageSetting.sharedUser.packages;
- String[] res = new String[packages.size()];
- final Iterator<PackageSetting> it = packages.iterator();
+ final int numPackages = packages.size();
+ String[] res = new String[numPackages];
int i = 0;
- while (it.hasNext()) {
- PackageSetting ps = it.next();
+ for (int index = 0; index < numPackages; index++) {
+ final PackageSetting ps = packages.valueAt(index);
if (ps.getInstalled(userId)) {
res[i++] = ps.name;
}
@@ -25659,25 +25883,16 @@ public class PackageManagerService extends IPackageManager.Stub
* @hide
*/
@Override
- public void logAppProcessStartIfNeeded(String processName, int uid, String seinfo,
- String apkFile, int pid) {
+ public void logAppProcessStartIfNeeded(String packageName, String processName, int uid,
+ String seinfo, String apkFile, int pid) {
if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
return;
}
if (!SecurityLog.isLoggingEnabled()) {
return;
}
- Bundle data = new Bundle();
- data.putLong("startTimestamp", System.currentTimeMillis());
- data.putString("processName", processName);
- data.putInt("uid", uid);
- data.putString("seinfo", seinfo);
- data.putString("apkFile", apkFile);
- data.putInt("pid", pid);
- Message msg = mProcessLoggingHandler.obtainMessage(
- ProcessLoggingHandler.LOG_APP_PROCESS_START_MSG);
- msg.setData(data);
- mProcessLoggingHandler.sendMessage(msg);
+ mProcessLoggingHandler.logAppProcessStart(mContext, this, apkFile, packageName, processName,
+ uid, seinfo, pid);
}
public CompilerStats.PackageStats getCompilerPackageStats(String pkgName) {
@@ -25801,6 +26016,32 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ @Override
+ public void grantImplicitAccess(int recipientUid, String visibleAuthority) {
+ // This API is exposed temporarily to only the contacts provider. (b/158688602)
+ final int callingUid = Binder.getCallingUid();
+ ProviderInfo contactsProvider = resolveContentProviderInternal(
+ ContactsContract.AUTHORITY, 0, UserHandle.getUserId(callingUid));
+ if (contactsProvider == null || contactsProvider.applicationInfo == null
+ || !UserHandle.isSameApp(contactsProvider.applicationInfo.uid, callingUid)) {
+ throw new SecurityException(callingUid + " is not allow to call grantImplicitAccess");
+ }
+ final int userId = UserHandle.getUserId(recipientUid);
+ final long token = Binder.clearCallingIdentity();
+ final ProviderInfo providerInfo;
+ try {
+ providerInfo = resolveContentProvider(visibleAuthority, 0 /*flags*/, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (providerInfo == null) {
+ return;
+ }
+ int visibleUid = providerInfo.applicationInfo.uid;
+ mPmInternal.grantImplicitAccess(userId, null /*Intent*/, UserHandle.getAppId(recipientUid),
+ visibleUid, false);
+ }
+
boolean canHaveOatDir(String packageName) {
synchronized (mLock) {
AndroidPackage p = mPackages.get(packageName);
@@ -25992,6 +26233,15 @@ public class PackageManagerService extends IPackageManager.Stub
mPermissionManager.writeStateToPackageSettingsTEMP();
mSettings.writeLPr();
}
+
+ @Override
+ public void holdLock(int durationMs) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity");
+ synchronized (mLock) {
+ SystemClock.sleep(durationMs);
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d77683e8ba61..c356fc72379f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -96,6 +96,7 @@ import android.system.Os;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -1450,7 +1451,7 @@ class PackageManagerShellCommand extends ShellCommand {
final PrintWriter pw = getOutPrintWriter();
final int parentSessionId = Integer.parseInt(getNextArg());
- List<Integer> otherSessionIds = new ArrayList<>();
+ IntArray otherSessionIds = new IntArray();
String opt;
while ((opt = getNextArg()) != null) {
otherSessionIds.add(Integer.parseInt(opt));
@@ -1459,7 +1460,7 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println("Error: At least two sessions are required.");
return 1;
}
- return doInstallAddSession(parentSessionId, ArrayUtils.convertToIntArray(otherSessionIds),
+ return doInstallAddSession(parentSessionId, otherSessionIds.toArray(),
true /*logSuccess*/);
}
@@ -2323,7 +2324,8 @@ class PackageManagerShellCommand extends ShellCommand {
private boolean isVendorApp(String pkg) {
try {
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+ final PackageInfo info = mInterface.getPackageInfo(
+ pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
return info != null && info.applicationInfo.isVendor();
} catch (RemoteException e) {
return false;
@@ -2332,7 +2334,8 @@ class PackageManagerShellCommand extends ShellCommand {
private boolean isProductApp(String pkg) {
try {
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+ final PackageInfo info = mInterface.getPackageInfo(
+ pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
return info != null && info.applicationInfo.isProduct();
} catch (RemoteException e) {
return false;
@@ -2341,7 +2344,8 @@ class PackageManagerShellCommand extends ShellCommand {
private boolean isSystemExtApp(String pkg) {
try {
- final PackageInfo info = mInterface.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
+ final PackageInfo info = mInterface.getPackageInfo(
+ pkg, PackageManager.MATCH_ANY_USER, UserHandle.USER_SYSTEM);
return info != null && info.applicationInfo.isSystemExt();
} catch (RemoteException e) {
return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 2bbca79741bd..1814a8e6322e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -27,6 +27,8 @@ import android.service.dataloader.DataLoaderService;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import libcore.io.IoUtils;
import java.io.IOException;
@@ -112,7 +114,9 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
}
}
- static class Metadata {
+ /** @hide */
+ @VisibleForTesting
+ public static class Metadata {
/**
* Full files read from stdin.
*/
@@ -137,7 +141,9 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
return new Metadata(STDIN, fileId);
}
- static Metadata forLocalFile(String filePath) {
+ /** @hide */
+ @VisibleForTesting
+ public static Metadata forLocalFile(String filePath) {
return new Metadata(LOCAL_FILE, filePath);
}
@@ -163,7 +169,9 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
return new Metadata(mode, data);
}
- byte[] toByteArray() {
+ /** @hide */
+ @VisibleForTesting
+ public byte[] toByteArray() {
byte[] dataBytes = this.mData.getBytes(StandardCharsets.UTF_8);
byte[] result = new byte[1 + dataBytes.length];
result[0] = this.mMode;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 276f88082df0..de9d3a09df35 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -28,7 +28,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.pkg.PackageStateUnserialized;
import java.io.File;
@@ -214,11 +214,12 @@ public class PackageSetting extends PackageSettingBase {
mimeGroups = updatedMimeGroups;
}
+ @Deprecated
@Override
- public PermissionsState getPermissionsState() {
+ public LegacyPermissionState getLegacyPermissionState() {
return (sharedUser != null)
- ? sharedUser.getPermissionsState()
- : super.getPermissionsState();
+ ? sharedUser.getLegacyPermissionState()
+ : super.getLegacyPermissionState();
}
public int getAppId() {
@@ -333,6 +334,8 @@ public class PackageSetting extends PackageSettingBase {
installSource.originatingPackageName);
proto.end(sourceToken);
}
+ proto.write(PackageProto.StatesProto.IS_STARTABLE, incrementalStates.isStartable());
+ proto.write(PackageProto.StatesProto.IS_LOADING, incrementalStates.isLoading());
writeUsersInfoToProto(proto, PackageProto.USERS);
proto.end(packageToken);
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index a7bbf8d66aac..d52ad46d4b7e 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.UninstallReason;
@@ -33,6 +34,7 @@ import android.content.pm.PackageUserState;
import android.content.pm.Signature;
import android.content.pm.SuspendDialogInfo;
import android.os.PersistableBundle;
+import android.os.incremental.IncrementalManager;
import android.service.pm.PackageProto;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -133,6 +135,9 @@ public abstract class PackageSettingBase extends SettingBase {
boolean forceQueryableOverride;
+ @NonNull
+ public IncrementalStates incrementalStates;
+
PackageSettingBase(String name, String realName, @NonNull File path,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString,
@@ -151,6 +156,7 @@ public abstract class PackageSettingBase extends SettingBase {
this.versionCode = pVersionCode;
this.signatures = new PackageSignatures();
this.installSource = InstallSource.EMPTY;
+ this.incrementalStates = new IncrementalStates();
}
/**
@@ -257,6 +263,7 @@ public abstract class PackageSettingBase extends SettingBase {
orig.usesStaticLibrariesVersions.length) : null;
updateAvailable = orig.updateAvailable;
forceQueryableOverride = orig.forceQueryableOverride;
+ incrementalStates = orig.incrementalStates;
}
@VisibleForTesting
@@ -733,6 +740,66 @@ public abstract class PackageSettingBase extends SettingBase {
modifyUserState(userId).resetOverrideComponentLabelIcon();
}
+ /**
+ * @return True if package is startable, false otherwise.
+ */
+ public boolean isPackageStartable() {
+ return incrementalStates.isStartable();
+ }
+
+ /**
+ * @return True if package is still being loaded, false if the package is fully loaded.
+ */
+ public boolean isPackageLoading() {
+ return incrementalStates.isLoading();
+ }
+
+ /**
+ * @return all current states in a Parcelable.
+ */
+ public IncrementalStatesInfo getIncrementalStates() {
+ return incrementalStates.getIncrementalStatesInfo();
+ }
+
+ /**
+ * Called to indicate that the package installation has been committed. This will create a
+ * new startable state and a new loading state with default values. By default, the package is
+ * startable after commit. For a package installed on Incremental, the loading state is true.
+ * For non-Incremental packages, the loading state is false.
+ */
+ public void setStatesOnCommit() {
+ incrementalStates.onCommit(IncrementalManager.isIncrementalPath(getPathString()));
+ }
+
+ /**
+ * Called to set the callback to listen for startable state changes.
+ */
+ public void setIncrementalStatesCallback(IncrementalStates.Callback callback) {
+ incrementalStates.setCallback(callback);
+ }
+
+ /**
+ * Called to report progress changes. This might trigger loading state change.
+ * @see IncrementalStates#setProgress(float)
+ */
+ public void setLoadingProgress(float progress) {
+ incrementalStates.setProgress(progress);
+ }
+
+ /**
+ * @see IncrementalStates#onStorageHealthStatusChanged(int)
+ */
+ public void setStorageHealthStatus(int status) {
+ incrementalStates.onStorageHealthStatusChanged(status);
+ }
+
+ /**
+ * @see IncrementalStates#onStreamStatusChanged(int)
+ */
+ public void setStreamStatus(int status) {
+ incrementalStates.onStreamStatusChanged(status);
+ }
+
protected PackageSettingBase updateFrom(PackageSettingBase other) {
super.copyFrom(other);
setPath(other.getPath());
@@ -756,6 +823,7 @@ public abstract class PackageSettingBase extends SettingBase {
this.updateAvailable = other.updateAvailable;
this.verificationInfo = other.verificationInfo;
this.forceQueryableOverride = other.forceQueryableOverride;
+ this.incrementalStates = other.incrementalStates;
if (mOldCodePaths != null) {
if (other.mOldCodePaths != null) {
diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
index c47dda4681f9..7fb34951d356 100644
--- a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
+++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
@@ -16,29 +16,47 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
+
import android.app.admin.SecurityLog;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ApkChecksum;
+import android.content.pm.Checksum;
+import android.content.pm.IPackageManager;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
import com.android.internal.os.BackgroundThread;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HashMap;
-import android.util.Slog;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
public final class ProcessLoggingHandler extends Handler {
-
private static final String TAG = "ProcessLoggingHandler";
- static final int LOG_APP_PROCESS_START_MSG = 1;
- static final int INVALIDATE_BASE_APK_HASH_MSG = 2;
- private final HashMap<String, String> mProcessLoggingBaseApkHashes = new HashMap();
+ private static final int LOG_APP_PROCESS_START_MSG = 1;
+
+ private static final int CHECKSUM_TYPE = Checksum.TYPE_WHOLE_SHA256;
+
+ static class LoggingInfo {
+ public String apkHash = null;
+ public List<Bundle> pendingLogEntries = new ArrayList<>();
+ }
+
+ // Apk path to logging info map.
+ private final ArrayMap<String, LoggingInfo> mLoggingInfo = new ArrayMap<>();
ProcessLoggingHandler() {
super(BackgroundThread.getHandler().getLooper());
@@ -49,64 +67,133 @@ public final class ProcessLoggingHandler extends Handler {
switch (msg.what) {
case LOG_APP_PROCESS_START_MSG: {
Bundle bundle = msg.getData();
+ long startTimestamp = bundle.getLong("startTimestamp");
String processName = bundle.getString("processName");
int uid = bundle.getInt("uid");
String seinfo = bundle.getString("seinfo");
- String apkFile = bundle.getString("apkFile");
int pid = bundle.getInt("pid");
- long startTimestamp = bundle.getLong("startTimestamp");
- String apkHash = computeStringHashOfApk(apkFile);
+ String apkHash = bundle.getString("apkHash");
SecurityLog.writeEvent(SecurityLog.TAG_APP_PROCESS_START, processName,
startTimestamp, uid, pid, seinfo, apkHash);
break;
}
- case INVALIDATE_BASE_APK_HASH_MSG: {
- Bundle bundle = msg.getData();
- mProcessLoggingBaseApkHashes.remove(bundle.getString("apkFile"));
- break;
- }
}
}
- void invalidateProcessLoggingBaseApkHash(String apkPath) {
+ void logAppProcessStart(Context context, IPackageManager pms, String apkFile,
+ String packageName, String processName, int uid, String seinfo, int pid) {
Bundle data = new Bundle();
- data.putString("apkFile", apkPath);
- Message msg = obtainMessage(INVALIDATE_BASE_APK_HASH_MSG);
- msg.setData(data);
- sendMessage(msg);
- }
+ data.putLong("startTimestamp", System.currentTimeMillis());
+ data.putString("processName", processName);
+ data.putInt("uid", uid);
+ data.putString("seinfo", seinfo);
+ data.putInt("pid", pid);
- private String computeStringHashOfApk(String apkFile) {
if (apkFile == null) {
- return "No APK";
+ enqueueSecurityLogEvent(data, "No APK");
+ return;
}
- String apkHash = mProcessLoggingBaseApkHashes.get(apkFile);
- if (apkHash == null) {
- try {
- byte[] hash = computeHashOfApkFile(apkFile);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < hash.length; i++) {
- sb.append(String.format("%02x", hash[i]));
- }
- apkHash = sb.toString();
- mProcessLoggingBaseApkHashes.put(apkFile, apkHash);
- } catch (IOException | NoSuchAlgorithmException e) {
- Slog.w(TAG, "computeStringHashOfApk() failed", e);
+
+ // Check cached apk hash.
+ boolean requestChecksums;
+ final LoggingInfo loggingInfo;
+ synchronized (mLoggingInfo) {
+ LoggingInfo cached = mLoggingInfo.get(apkFile);
+ requestChecksums = cached == null;
+ if (requestChecksums) {
+ // Create a new pending cache entry.
+ cached = new LoggingInfo();
+ mLoggingInfo.put(apkFile, cached);
}
+ loggingInfo = cached;
}
- return apkHash != null ? apkHash : "Failed to count APK hash";
+
+ synchronized (loggingInfo) {
+ // Still pending?
+ if (!TextUtils.isEmpty(loggingInfo.apkHash)) {
+ enqueueSecurityLogEvent(data, loggingInfo.apkHash);
+ return;
+ }
+
+ loggingInfo.pendingLogEntries.add(data);
+ }
+
+ if (!requestChecksums) {
+ return;
+ }
+
+ // Request base checksums when first added entry.
+ // Capturing local loggingInfo to still log even if hash was invalidated.
+ try {
+ pms.requestChecksums(packageName, false, 0, CHECKSUM_TYPE, null,
+ new IntentSender((IIntentSender) new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType,
+ IBinder allowlistToken, IIntentReceiver finishedReceiver,
+ String requiredPermission, Bundle options) {
+ processChecksums(loggingInfo, intent);
+ }
+ }), context.getUserId());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "requestChecksums() failed", e);
+ processChecksums(loggingInfo, null);
+ }
+ }
+
+ void processChecksums(final LoggingInfo loggingInfo, Intent intent) {
+ Parcelable[] parcelables = intent.getParcelableArrayExtra(EXTRA_CHECKSUMS);
+ ApkChecksum[] checksums = Arrays.copyOf(parcelables, parcelables.length,
+ ApkChecksum[].class);
+
+ for (ApkChecksum checksum : checksums) {
+ if (checksum.getType() == CHECKSUM_TYPE) {
+ processChecksum(loggingInfo, checksum.getValue());
+ break;
+ }
+ }
+ }
+
+ void processChecksum(final LoggingInfo loggingInfo, final byte[] hash) {
+ final String apkHash;
+ if (hash != null) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < hash.length; i++) {
+ sb.append(String.format("%02x", hash[i]));
+ }
+ apkHash = sb.toString();
+ } else {
+ apkHash = "Failed to count APK hash";
+ }
+
+ List<Bundle> pendingLogEntries;
+ synchronized (loggingInfo) {
+ if (!TextUtils.isEmpty(loggingInfo.apkHash)) {
+ return;
+ }
+ loggingInfo.apkHash = apkHash;
+
+ pendingLogEntries = loggingInfo.pendingLogEntries;
+ loggingInfo.pendingLogEntries = null;
+ }
+
+ if (pendingLogEntries != null) {
+ for (Bundle data : pendingLogEntries) {
+ enqueueSecurityLogEvent(data, apkHash);
+ }
+ }
+ }
+
+ void enqueueSecurityLogEvent(Bundle data, String apkHash) {
+ data.putString("apkHash", apkHash);
+
+ Message msg = this.obtainMessage(LOG_APP_PROCESS_START_MSG);
+ msg.setData(data);
+ this.sendMessage(msg);
}
- private byte[] computeHashOfApkFile(String packageArchiveLocation)
- throws IOException, NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- FileInputStream input = new FileInputStream(new File(packageArchiveLocation));
- byte[] buffer = new byte[65536];
- int size;
- while ((size = input.read(buffer)) > 0) {
- md.update(buffer, 0, size);
+ void invalidateBaseApkHash(String apkFile) {
+ synchronized (mLoggingInfo) {
+ mLoggingInfo.remove(apkFile);
}
- input.close();
- return md.digest();
}
}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index fdd9636ae7b2..c5fbfba9b049 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.Signature;
import android.os.Environment;
@@ -77,9 +78,21 @@ public final class SELinuxMMAC {
private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
/**
- * This change gates apps access to untrusted_app_R-targetSDk SELinux domain. Allows opt-in
+ * Allows opt-in to the latest targetSdkVersion enforced changes without changing target SDK.
+ * Turning this change off for an app targeting the latest SDK is a no-op.
+ *
+ * <p>Has no effect for apps using shared user id.
+ *
+ * TODO(b/143539591): Update description with relevant SELINUX changes this opts in to.
+ */
+ @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R)
+ @ChangeId
+ static final long SELINUX_LATEST_CHANGES = 143539591L;
+
+ /**
+ * This change gates apps access to untrusted_app_R-targetSDK SELinux domain. Allows opt-in
* to R targetSdkVersion enforced changes without changing target SDK. Turning this change
- * off for an app targeting R is a no-op.
+ * off for an app targeting S is a no-op.
*
* <p>Has no effect for apps using shared user id.
*
@@ -87,7 +100,7 @@ public final class SELinuxMMAC {
*/
@EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
@ChangeId
- static final long SELINUX_LATEST_CHANGES = 143539591L;
+ static final long SELINUX_R_CHANGES = 168782947L;
// Only initialize sMacPermissions once.
static {
@@ -349,9 +362,11 @@ public final class SELinuxMMAC {
if ((sharedUserSetting != null) && (sharedUserSetting.packages.size() != 0)) {
return sharedUserSetting.seInfoTargetSdkVersion;
}
- if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES,
- pkg.toAppInfoWithoutState())) {
- return android.os.Build.VERSION_CODES.R;
+ final ApplicationInfo appInfo = pkg.toAppInfoWithoutState();
+ if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) {
+ return android.os.Build.VERSION_CODES.S;
+ } else if (compatibility.isChangeEnabledInternal(SELINUX_R_CHANGES, appInfo)) {
+ return Math.max(android.os.Build.VERSION_CODES.R, pkg.getTargetSdkVersion());
}
return pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index 3e2ab05e83ec..968c4b514ed0 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -19,23 +19,29 @@ package com.android.server.pm;
import android.content.pm.ApplicationInfo;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.LegacyPermissionState;
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public abstract class SettingBase {
int pkgFlags;
int pkgPrivateFlags;
- protected final PermissionsState mPermissionsState;
+ /**
+ * The legacy permission state that is read from package settings persistence for migration.
+ * This state here can not reflect the current permission state and should not be used for
+ * purposes other than migration.
+ */
+ @Deprecated
+ protected final LegacyPermissionState mLegacyPermissionsState;
SettingBase(int pkgFlags, int pkgPrivateFlags) {
setFlags(pkgFlags);
setPrivateFlags(pkgPrivateFlags);
- mPermissionsState = new PermissionsState();
+ mLegacyPermissionsState = new LegacyPermissionState();
}
SettingBase(SettingBase orig) {
- mPermissionsState = new PermissionsState();
+ mLegacyPermissionsState = new LegacyPermissionState();
doCopy(orig);
}
@@ -46,11 +52,12 @@ public abstract class SettingBase {
private void doCopy(SettingBase orig) {
pkgFlags = orig.pkgFlags;
pkgPrivateFlags = orig.pkgPrivateFlags;
- mPermissionsState.copyFrom(orig.mPermissionsState);
+ mLegacyPermissionsState.copyFrom(orig.mLegacyPermissionsState);
}
- public PermissionsState getPermissionsState() {
- return mPermissionsState;
+ @Deprecated
+ public LegacyPermissionState getLegacyPermissionState() {
+ return mLegacyPermissionsState;
}
void setFlags(int pkgFlags) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 965430727d3c..4c8d2b9b0a9e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -79,6 +79,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.IntArray;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
@@ -107,9 +108,10 @@ import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.LegacyPermissionDataProvider;
+import com.android.server.pm.permission.LegacyPermissionState;
+import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
import com.android.server.pm.permission.PermissionSettings;
-import com.android.server.pm.permission.PermissionsState;
-import com.android.server.pm.permission.PermissionsState.PermissionState;
import com.android.server.utils.TimingsTraceAndSlog;
import libcore.io.IoUtils;
@@ -416,9 +418,12 @@ public final class Settings {
private final File mSystemDir;
public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
+
/** Settings and other information about permissions */
final PermissionSettings mPermissions;
+ private final LegacyPermissionDataProvider mPermissionDataProvider;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public Settings(Map<String, PackageSetting> pkgSettings) {
mLock = new Object();
@@ -426,6 +431,7 @@ public final class Settings {
mSystemDir = null;
mPermissions = null;
mRuntimePermissionsPersistence = null;
+ mPermissionDataProvider = null;
mSettingsFilename = null;
mBackupSettingsFilename = null;
mPackageListFilename = null;
@@ -434,12 +440,14 @@ public final class Settings {
mKernelMappingFilename = null;
}
- Settings(File dataDir, PermissionSettings permission,
- RuntimePermissionsPersistence runtimePermissionsPersistence, Object lock) {
+ Settings(File dataDir, PermissionSettings permissionSettings,
+ RuntimePermissionsPersistence runtimePermissionsPersistence,
+ LegacyPermissionDataProvider permissionDataProvider, Object lock) {
mLock = lock;
- mPermissions = permission;
+ mPermissions = permissionSettings;
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(
- runtimePermissionsPersistence, mLock);
+ runtimePermissionsPersistence);
+ mPermissionDataProvider = permissionDataProvider;
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
@@ -477,6 +485,10 @@ public final class Settings {
return mRenamedPackages.put(pkgName, origPkgName);
}
+ void removeRenamedPackageLPw(String pkgName) {
+ mRenamedPackages.remove(pkgName);
+ }
+
public boolean canPropagatePermissionToInstantApp(String permName) {
return mPermissions.canPropagatePermissionToInstantApp(permName);
}
@@ -721,7 +733,8 @@ public final class Settings {
pkgSetting.signatures = new PackageSignatures(disabledPkg.signatures);
pkgSetting.appId = disabledPkg.appId;
// Clone permissions
- pkgSetting.getPermissionsState().copyFrom(disabledPkg.getPermissionsState());
+ pkgSetting.getLegacyPermissionState()
+ .copyFrom(disabledPkg.getLegacyPermissionState());
// Clone component info
List<UserInfo> users = getAllUsers(userManager);
if (users != null) {
@@ -1239,7 +1252,7 @@ public final class Settings {
void writeAllRuntimePermissionsLPr() {
for (int userId : UserManagerService.getInstance().getUserIds()) {
- mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
+ mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId);
}
}
@@ -2102,7 +2115,7 @@ public final class Settings {
}
void readInstallPermissionsLPr(XmlPullParser parser,
- PermissionsState permissionsState) throws IOException, XmlPullParserException {
+ LegacyPermissionState permissionsState) throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2131,25 +2144,7 @@ public final class Settings {
final int flags = (flagsStr != null)
? Integer.parseInt(flagsStr, 16) : 0;
- if (granted) {
- if (permissionsState.grantInstallPermission(bp) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
- XmlUtils.skipCurrentTag(parser);
- } else {
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- }
- } else {
- if (permissionsState.revokeInstallPermission(bp) ==
- PermissionsState.PERMISSION_OPERATION_FAILURE) {
- Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
- XmlUtils.skipCurrentTag(parser);
- } else {
- permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- }
- }
+ permissionsState.putInstallPermissionState(new PermissionState(bp, granted, flags));
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: "
+ parser.getName());
@@ -2158,7 +2153,7 @@ public final class Settings {
}
}
- void writePermissionsLPr(XmlSerializer serializer, List<PermissionState> permissionStates)
+ void writePermissionsLPr(XmlSerializer serializer, Collection<PermissionState> permissionStates)
throws IOException {
if (permissionStates.isEmpty()) {
return;
@@ -2412,7 +2407,7 @@ public final class Settings {
serializer.attribute(null, "userId",
Integer.toString(usr.userId));
usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
- writePermissionsLPr(serializer, usr.getPermissionsState()
+ writePermissionsLPr(serializer, usr.getLegacyPermissionState()
.getInstallPermissionStates());
serializer.endTag(null, "shared-user");
}
@@ -2641,7 +2636,11 @@ public final class Settings {
}
final boolean isDebug = pkg.pkg.isDebuggable();
- final int[] gids = pkg.getPermissionsState().computeGids(userIds);
+ final IntArray gids = new IntArray();
+ for (final int userId : userIds) {
+ gids.addAll(mPermissionDataProvider.getGidsForUid(UserHandle.getUid(userId,
+ pkg.appId)));
+ }
// Avoid any application that has a space in its path.
if (dataPath.indexOf(' ') >= 0)
@@ -2673,11 +2672,12 @@ public final class Settings {
sb.append(" ");
sb.append(AndroidPackageUtils.getSeInfo(pkg.pkg, pkg));
sb.append(" ");
- if (gids != null && gids.length > 0) {
- sb.append(gids[0]);
- for (int i = 1; i < gids.length; i++) {
+ final int gidsSize = gids.size();
+ if (gids != null && gids.size() > 0) {
+ sb.append(gids.get(0));
+ for (int i = 1; i < gidsSize; i++) {
sb.append(",");
- sb.append(gids[i]);
+ sb.append(gids.get(i));
}
} else {
sb.append("none");
@@ -2735,7 +2735,7 @@ public final class Settings {
// If this is a shared user, the permissions will be written there.
if (pkg.sharedUser == null) {
- writePermissionsLPr(serializer, pkg.getPermissionsState()
+ writePermissionsLPr(serializer, pkg.getLegacyPermissionState()
.getInstallPermissionStates());
}
@@ -2810,6 +2810,12 @@ public final class Settings {
if (pkg.forceQueryableOverride) {
serializer.attribute(null, "forceQueryable", "true");
}
+ if (pkg.isPackageStartable()) {
+ serializer.attribute(null, "isStartable", "true");
+ }
+ if (pkg.isPackageLoading()) {
+ serializer.attribute(null, "isLoading", "true");
+ }
writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
@@ -2820,7 +2826,8 @@ public final class Settings {
serializer, "install-initiator-sigs", mPastSignatures);
}
- writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
+ writePermissionsLPr(serializer,
+ pkg.getLegacyPermissionState().getInstallPermissionStates());
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
@@ -3546,7 +3553,7 @@ public final class Settings {
}
if (parser.getName().equals(TAG_PERMISSIONS)) {
- readInstallPermissionsLPr(parser, ps.getPermissionsState());
+ readInstallPermissionsLPr(parser, ps.getLegacyPermissionState());
} else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
readUsesStaticLibLPw(parser, ps);
} else {
@@ -3595,6 +3602,8 @@ public final class Settings {
String version = null;
long versionCode = 0;
String installedForceQueryable = null;
+ String isStartable = null;
+ String isLoading = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3611,6 +3620,8 @@ public final class Settings {
cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride");
updateAvailable = parser.getAttributeValue(null, "updateAvailable");
installedForceQueryable = parser.getAttributeValue(null, "forceQueryable");
+ isStartable = parser.getAttributeValue(null, "isStartable");
+ isLoading = parser.getAttributeValue(null, "isLoading");
if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
primaryCpuAbiString = legacyCpuAbiString;
@@ -3794,6 +3805,8 @@ public final class Settings {
packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
packageSetting.updateAvailable = "true".equals(updateAvailable);
packageSetting.forceQueryableOverride = "true".equals(installedForceQueryable);
+ packageSetting.incrementalStates = new IncrementalStates("true".equals(isStartable),
+ "true".equals(isLoading));
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -3837,7 +3850,7 @@ public final class Settings {
packageSetting.signatures.readXml(parser, mPastSignatures);
} else if (tagName.equals(TAG_PERMISSIONS)) {
readInstallPermissionsLPr(parser,
- packageSetting.getPermissionsState());
+ packageSetting.getLegacyPermissionState());
packageSetting.installPermissionsFixed = true;
} else if (tagName.equals("proper-signing-keyset")) {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
@@ -4063,7 +4076,7 @@ public final class Settings {
if (tagName.equals("sigs")) {
su.signatures.readXml(parser, mPastSignatures);
} else if (tagName.equals("perms")) {
- readInstallPermissionsLPr(parser, su.getPermissionsState());
+ readInstallPermissionsLPr(parser, su.getLegacyPermissionState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <shared-user>: " + parser.getName());
@@ -4374,7 +4387,7 @@ public final class Settings {
*/
private static List<UserInfo> getUsers(UserManagerService userManager, boolean excludeDying,
boolean excludePreCreated) {
- long id = Binder.clearCallingIdentity();
+ final long id = Binder.clearCallingIdentity();
try {
return userManager.getUsers(/* excludePartial= */ true, excludeDying,
excludePreCreated);
@@ -4482,8 +4495,9 @@ public final class Settings {
}
void dumpPackageLPr(PrintWriter pw, String prefix, String checkinTag,
- ArraySet<String> permissionNames, PackageSetting ps, SimpleDateFormat sdf,
- Date date, List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) {
+ ArraySet<String> permissionNames, PackageSetting ps,
+ LegacyPermissionState permissionsState, SimpleDateFormat sdf, Date date,
+ List<UserInfo> users, boolean dumpAll, boolean dumpAllComponents) {
AndroidPackage pkg = ps.pkg;
if (checkinTag != null) {
pw.print(checkinTag);
@@ -4810,7 +4824,6 @@ public final class Settings {
}
if (ps.sharedUser == null || permissionNames != null || dumpAll) {
- PermissionsState permissionsState = ps.getPermissionsState();
dumpInstallPermissionsLPr(pw, prefix + " ", permissionNames, permissionsState);
}
@@ -4889,8 +4902,8 @@ public final class Settings {
}
if (ps.sharedUser == null) {
- PermissionsState permissionsState = ps.getPermissionsState();
- dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id));
+ dumpGidsLPr(pw, prefix + " ", mPermissionDataProvider.getGidsForUid(
+ UserHandle.getUid(user.id, ps.appId)));
dumpRuntimePermissionsLPr(pw, prefix + " ", permissionNames, permissionsState
.getRuntimePermissionStates(user.id), dumpAll);
}
@@ -4933,8 +4946,10 @@ public final class Settings {
&& !packageName.equals(ps.name)) {
continue;
}
+ final LegacyPermissionState permissionsState =
+ mPermissionDataProvider.getLegacyPermissionState(ps.appId);
if (permissionNames != null
- && !ps.getPermissionsState().hasRequestedPermission(permissionNames)) {
+ && !permissionsState.hasPermissionState(permissionNames)) {
continue;
}
@@ -4948,8 +4963,8 @@ public final class Settings {
pw.println("Packages:");
printedSomething = true;
}
- dumpPackageLPr(pw, " ", checkin ? "pkg" : null, permissionNames, ps, sdf, date, users,
- packageName != null, dumpAllComponents);
+ dumpPackageLPr(pw, " ", checkin ? "pkg" : null, permissionNames, ps, permissionsState,
+ sdf, date, users, packageName != null, dumpAllComponents);
}
printedSomething = false;
@@ -4989,8 +5004,10 @@ public final class Settings {
pw.println("Hidden system packages:");
printedSomething = true;
}
- dumpPackageLPr(pw, " ", checkin ? "dis" : null, permissionNames, ps, sdf, date,
- users, packageName != null, dumpAllComponents);
+ final LegacyPermissionState permissionsState =
+ mPermissionDataProvider.getLegacyPermissionState(ps.appId);
+ dumpPackageLPr(pw, " ", checkin ? "dis" : null, permissionNames, ps,
+ permissionsState, sdf, date, users, packageName != null, dumpAllComponents);
}
}
}
@@ -5018,8 +5035,10 @@ public final class Settings {
if (packageName != null && su != dumpState.getSharedUser()) {
continue;
}
+ final LegacyPermissionState permissionsState =
+ mPermissionDataProvider.getLegacyPermissionState(su.userId);
if (permissionNames != null
- && !su.getPermissionsState().hasRequestedPermission(permissionNames)) {
+ && !permissionsState.hasPermissionState(permissionNames)) {
continue;
}
if (!checkin) {
@@ -5054,12 +5073,12 @@ public final class Settings {
continue;
}
- final PermissionsState permissionsState = su.getPermissionsState();
dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState);
for (int userId : UserManagerService.getInstance().getUserIds()) {
- final int[] gids = permissionsState.computeGids(userId);
- final List<PermissionState> permissions =
+ final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid(
+ userId, su.userId));
+ final Collection<PermissionState> permissions =
permissionsState.getRuntimePermissionStates(userId);
if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) {
pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": ");
@@ -5120,7 +5139,7 @@ public final class Settings {
}
void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames,
- List<PermissionState> permissionStates, boolean dumpAll) {
+ Collection<PermissionState> permissionStates, boolean dumpAll) {
if (!permissionStates.isEmpty() || dumpAll) {
pw.print(prefix); pw.println("runtime permissions:");
for (PermissionState permissionState : permissionStates) {
@@ -5161,8 +5180,9 @@ public final class Settings {
}
void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames,
- PermissionsState permissionsState) {
- List<PermissionState> permissionStates = permissionsState.getInstallPermissionStates();
+ LegacyPermissionState permissionsState) {
+ Collection<PermissionState> permissionStates =
+ permissionsState.getInstallPermissionStates();
if (!permissionStates.isEmpty()) {
pw.print(prefix); pw.println("install permissions:");
for (PermissionState permissionState : permissionStates) {
@@ -5202,9 +5222,9 @@ public final class Settings {
public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
if (sync) {
- mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
+ mRuntimePermissionsPersistence.writeStateForUserSyncLPr(userId);
} else {
- mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
+ mRuntimePermissionsPersistence.writeStateForUserAsyncLPr(userId);
}
}
@@ -5292,8 +5312,6 @@ public final class Settings {
private final Handler mHandler = new MyHandler();
- private final Object mPersistenceLock;
-
@GuardedBy("mLock")
private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
@@ -5313,10 +5331,8 @@ public final class Settings {
// The mapping keys are user ids.
private final SparseBooleanArray mPermissionUpgradeNeeded = new SparseBooleanArray();
- public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence,
- Object persistenceLock) {
+ public RuntimePermissionPersistence(RuntimePermissionsPersistence persistence) {
mPersistence = persistence;
- mPersistenceLock = persistenceLock;
}
@GuardedBy("Settings.this.mLock")
@@ -5327,7 +5343,7 @@ public final class Settings {
@GuardedBy("Settings.this.mLock")
void setVersionLPr(int version, int userId) {
mVersions.put(userId, version);
- writePermissionsForUserAsyncLPr(userId);
+ writeStateForUserAsyncLPr(userId);
}
@GuardedBy("Settings.this.mLock")
@@ -5342,7 +5358,7 @@ public final class Settings {
+ "set before trying to update the fingerprint.");
}
mFingerprints.put(userId, mExtendedFingerprint);
- writePermissionsForUserAsyncLPr(userId);
+ writeStateForUserAsyncLPr(userId);
}
public void setPermissionControllerVersion(long version) {
@@ -5361,13 +5377,7 @@ public final class Settings {
return Build.FINGERPRINT + "?pc_version=" + version;
}
- public void writePermissionsForUserSyncLPr(int userId) {
- mHandler.removeMessages(userId);
- writePermissionsSync(userId);
- }
-
- @GuardedBy("Settings.this.mLock")
- public void writePermissionsForUserAsyncLPr(int userId) {
+ public void writeStateForUserAsyncLPr(int userId) {
final long currentTimeMillis = SystemClock.uptimeMillis();
if (mWriteScheduled.get(userId)) {
@@ -5399,59 +5409,53 @@ public final class Settings {
}
}
- private void writePermissionsSync(int userId) {
- RuntimePermissionsState runtimePermissions;
- synchronized (mPersistenceLock) {
- mWriteScheduled.delete(userId);
+ public void writeStateForUserSyncLPr(int userId) {
+ mHandler.removeMessages(userId);
+ mWriteScheduled.delete(userId);
- int version = mVersions.get(userId, INITIAL_VERSION);
+ int version = mVersions.get(userId, INITIAL_VERSION);
- String fingerprint = mFingerprints.get(userId);
+ String fingerprint = mFingerprints.get(userId);
- Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
- new ArrayMap<>();
- int packagesSize = mPackages.size();
- for (int i = 0; i < packagesSize; i++) {
- String packageName = mPackages.keyAt(i);
- PackageSetting packageSetting = mPackages.valueAt(i);
- if (packageSetting.sharedUser == null) {
- List<RuntimePermissionsState.PermissionState> permissions =
- getPermissionsFromPermissionsState(
- packageSetting.getPermissionsState(), userId);
- packagePermissions.put(packageName, permissions);
- }
- }
-
- Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
- new ArrayMap<>();
- final int sharedUsersSize = mSharedUsers.size();
- for (int i = 0; i < sharedUsersSize; i++) {
- String sharedUserName = mSharedUsers.keyAt(i);
- SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
+ Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
+ new ArrayMap<>();
+ int packagesSize = mPackages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ String packageName = mPackages.keyAt(i);
+ PackageSetting packageSetting = mPackages.valueAt(i);
+ if (packageSetting.sharedUser == null) {
List<RuntimePermissionsState.PermissionState> permissions =
getPermissionsFromPermissionsState(
- sharedUserSetting.getPermissionsState(), userId);
- sharedUserPermissions.put(sharedUserName, permissions);
+ packageSetting.getLegacyPermissionState(), userId);
+ packagePermissions.put(packageName, permissions);
}
+ }
- runtimePermissions = new RuntimePermissionsState(version, fingerprint,
- packagePermissions, sharedUserPermissions);
+ Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
+ new ArrayMap<>();
+ final int sharedUsersSize = mSharedUsers.size();
+ for (int i = 0; i < sharedUsersSize; i++) {
+ String sharedUserName = mSharedUsers.keyAt(i);
+ SharedUserSetting sharedUserSetting = mSharedUsers.valueAt(i);
+ List<RuntimePermissionsState.PermissionState> permissions =
+ getPermissionsFromPermissionsState(
+ sharedUserSetting.getLegacyPermissionState(), userId);
+ sharedUserPermissions.put(sharedUserName, permissions);
}
+ RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(version,
+ fingerprint, packagePermissions, sharedUserPermissions);
+
mPersistence.writeForUser(runtimePermissions, UserHandle.of(userId));
}
@NonNull
private List<RuntimePermissionsState.PermissionState> getPermissionsFromPermissionsState(
- @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
- List<PermissionState> permissionStates = permissionsState.getRuntimePermissionStates(
- userId);
- List<RuntimePermissionsState.PermissionState> permissions =
- new ArrayList<>();
- int permissionStatesSize = permissionStates.size();
- for (int i = 0; i < permissionStatesSize; i++) {
- PermissionState permissionState = permissionStates.get(i);
-
+ @NonNull LegacyPermissionState permissionsState, @UserIdInt int userId) {
+ Collection<PermissionState> permissionStates =
+ permissionsState.getRuntimePermissionStates(userId);
+ List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>();
+ for (PermissionState permissionState : permissionStates) {
RuntimePermissionsState.PermissionState permission =
new RuntimePermissionsState.PermissionState(permissionState.getName(),
permissionState.isGranted(), permissionState.getFlags());
@@ -5480,7 +5484,7 @@ public final class Settings {
userId));
if (runtimePermissions == null) {
readLegacyStateForUserSyncLPr(userId);
- writePermissionsForUserAsyncLPr(userId);
+ writeStateForUserAsyncLPr(userId);
return;
}
@@ -5507,11 +5511,11 @@ public final class Settings {
List<RuntimePermissionsState.PermissionState> permissions =
packagePermissions.get(packageName);
if (permissions != null) {
- readPermissionsStateLpr(permissions, packageSetting.getPermissionsState(),
+ readPermissionsStateLpr(permissions, packageSetting.getLegacyPermissionState(),
userId);
} else if (packageSetting.sharedUser == null && !isUpgradeToR) {
Slog.w(TAG, "Missing permission state for package: " + packageName);
- packageSetting.getPermissionsState().setMissing(true, userId);
+ packageSetting.getLegacyPermissionState().setMissing(true, userId);
}
}
@@ -5525,18 +5529,18 @@ public final class Settings {
List<RuntimePermissionsState.PermissionState> permissions =
sharedUserPermissions.get(sharedUserName);
if (permissions != null) {
- readPermissionsStateLpr(permissions, sharedUserSetting.getPermissionsState(),
- userId);
+ readPermissionsStateLpr(permissions,
+ sharedUserSetting.getLegacyPermissionState(), userId);
} else if (!isUpgradeToR) {
Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
- sharedUserSetting.getPermissionsState().setMissing(true, userId);
+ sharedUserSetting.getLegacyPermissionState().setMissing(true, userId);
}
}
}
private void readPermissionsStateLpr(
@NonNull List<RuntimePermissionsState.PermissionState> permissions,
- @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
+ @NonNull LegacyPermissionState permissionsState, @UserIdInt int userId) {
int permissionsSize = permissions.size();
for (int i = 0; i < permissionsSize; i++) {
RuntimePermissionsState.PermissionState permission = permissions.get(i);
@@ -5550,14 +5554,8 @@ public final class Settings {
boolean granted = permission.isGranted();
int flags = permission.getFlags();
- if (granted) {
- permissionsState.grantRuntimePermission(basePermission, userId);
- permissionsState.updatePermissionFlags(basePermission, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- } else {
- permissionsState.updatePermissionFlags(basePermission, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- }
+ permissionsState.putRuntimePermissionState(new PermissionState(basePermission,
+ granted, flags), userId);
}
}
@@ -5621,7 +5619,7 @@ public final class Settings {
XmlUtils.skipCurrentTag(parser);
continue;
}
- parsePermissionsLPr(parser, ps.getPermissionsState(), userId);
+ parsePermissionsLPr(parser, ps.getLegacyPermissionState(), userId);
} break;
case TAG_SHARED_USER: {
@@ -5632,14 +5630,15 @@ public final class Settings {
XmlUtils.skipCurrentTag(parser);
continue;
}
- parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
+ parsePermissionsLPr(parser, sus.getLegacyPermissionState(), userId);
} break;
}
}
}
- private void parsePermissionsLPr(XmlPullParser parser, PermissionsState permissionsState,
- int userId) throws IOException, XmlPullParserException {
+ private void parsePermissionsLPr(XmlPullParser parser,
+ LegacyPermissionState permissionsState, int userId)
+ throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -5666,15 +5665,8 @@ public final class Settings {
final int flags = (flagsStr != null)
? Integer.parseInt(flagsStr, 16) : 0;
- if (granted) {
- permissionsState.grantRuntimePermission(bp, userId);
- permissionsState.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- } else {
- permissionsState.updatePermissionFlags(bp, userId,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, flags);
- }
-
+ permissionsState.putRuntimePermissionState(new PermissionState(bp, granted,
+ flags), userId);
}
break;
}
@@ -5690,7 +5682,9 @@ public final class Settings {
public void handleMessage(Message message) {
final int userId = message.what;
Runnable callback = (Runnable) message.obj;
- writePermissionsSync(userId);
+ synchronized (mLock) {
+ writeStateForUserSyncLPr(userId);
+ }
if (callback != null) {
callback.run();
}
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index d4132136c226..3bad3cb1d372 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -17,7 +17,6 @@
package com.android.server.pm;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
@@ -46,8 +45,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelableException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -80,13 +77,11 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -97,7 +92,6 @@ public class StagingManager {
private static final String TAG = "StagingManager";
- private final PackageInstallerService mPi;
private final ApexManager mApexManager;
private final PowerManager mPowerManager;
private final Context mContext;
@@ -117,9 +111,7 @@ public class StagingManager {
@GuardedBy("mSuccessfulStagedSessionIds")
private final List<Integer> mSuccessfulStagedSessionIds = new ArrayList<>();
- StagingManager(PackageInstallerService pi, Context context,
- Supplier<PackageParser2> packageParserSupplier) {
- mPi = pi;
+ StagingManager(Context context, Supplier<PackageParser2> packageParserSupplier) {
mContext = context;
mPackageParserSupplier = packageParserSupplier;
@@ -226,7 +218,7 @@ public class StagingManager {
final IntArray childSessionIds = new IntArray();
if (session.isMultiPackage()) {
for (PackageInstallerSession s : session.getChildSessions()) {
- if (isApexSession(s)) {
+ if (s.isApexSession()) {
childSessionIds.add(s.sessionId);
}
}
@@ -329,31 +321,6 @@ public class StagingManager {
}
}
- private static boolean isApexSession(@NonNull PackageInstallerSession session) {
- return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
- }
-
- private boolean sessionContains(@NonNull PackageInstallerSession session,
- Predicate<PackageInstallerSession> filter) {
- if (!session.isMultiPackage()) {
- return filter.test(session);
- }
- for (PackageInstallerSession s : session.getChildSessions()) {
- if (filter.test(s)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean sessionContainsApex(@NonNull PackageInstallerSession session) {
- return sessionContains(session, (s) -> isApexSession(s));
- }
-
- private boolean sessionContainsApk(@NonNull PackageInstallerSession session) {
- return sessionContains(session, (s) -> !isApexSession(s));
- }
-
// Reverts apex sessions and user data (if checkpoint is supported). Also reboots the device.
private void abortCheckpoint(int sessionId, String errorMsg) {
String failureReason = "Failed to install sessionId: " + sessionId + " Error: " + errorMsg;
@@ -400,7 +367,7 @@ public class StagingManager {
List<PackageInstallerSession> apexSessions = new ArrayList<>();
if (session.isMultiPackage()) {
for (PackageInstallerSession s : session.getChildSessions()) {
- if (sessionContainsApex(s)) {
+ if (s.containsApexSession()) {
apexSessions.add(s);
}
}
@@ -521,7 +488,7 @@ public class StagingManager {
private void resumeSession(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Resuming session " + session.sessionId);
- final boolean hasApex = sessionContainsApex(session);
+ final boolean hasApex = session.containsApexSession();
ApexSessionInfo apexSessionInfo = null;
if (hasApex) {
// Check with apexservice whether the apex packages have been activated.
@@ -676,118 +643,6 @@ public class StagingManager {
return "";
}
- private List<String> findAPKsInDir(File stageDir) {
- List<String> ret = new ArrayList<>();
- if (stageDir != null && stageDir.exists()) {
- for (File file : stageDir.listFiles()) {
- if (file.getAbsolutePath().toLowerCase().endsWith(".apk")) {
- ret.add(file.getAbsolutePath());
- }
- }
- }
- return ret;
- }
-
- private PackageInstallerSession createAndWriteApkSession(
- PackageInstallerSession originalSession) throws PackageManagerException {
- final int errorCode = SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
- if (originalSession.stageDir == null) {
- Slog.wtf(TAG, "Attempting to install a staged APK session with no staging dir");
- throw new PackageManagerException(errorCode,
- "Attempting to install a staged APK session with no staging dir");
- }
- List<String> apkFilePaths = findAPKsInDir(originalSession.stageDir);
- if (apkFilePaths.isEmpty()) {
- Slog.w(TAG, "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
- throw new PackageManagerException(errorCode,
- "Can't find staged APK in " + originalSession.stageDir.getAbsolutePath());
- }
-
- PackageInstaller.SessionParams params = originalSession.params.copy();
- params.isStaged = false;
- params.installFlags |= PackageManager.INSTALL_STAGED;
- params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
- try {
- int apkSessionId = mPi.createSession(
- params, originalSession.getInstallerPackageName(),
- originalSession.getInstallerAttributionTag(), originalSession.userId);
- PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
- apkSession.open();
- for (int i = 0, size = apkFilePaths.size(); i < size; i++) {
- final String apkFilePath = apkFilePaths.get(i);
- File apkFile = new File(apkFilePath);
- ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
- ParcelFileDescriptor.MODE_READ_ONLY);
- long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
- if (sizeBytes < 0) {
- Slog.e(TAG, "Unable to get size of: " + apkFilePath);
- throw new PackageManagerException(errorCode,
- "Unable to get size of: " + apkFilePath);
- }
- apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
- }
- return apkSession;
- } catch (IOException | ParcelableException e) {
- Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
- throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
- }
- }
-
- /**
- * Extract apks in the given session into a new session. Returns {@code null} if there is no
- * apks in the given session. Only parent session is returned for multi-package session.
- */
- @Nullable
- private PackageInstallerSession extractApksInSession(PackageInstallerSession session)
- throws PackageManagerException {
- if (!session.isMultiPackage() && !isApexSession(session)) {
- return createAndWriteApkSession(session);
- } else if (session.isMultiPackage()) {
- // For multi-package staged sessions containing APKs, we identify which child sessions
- // contain an APK, and with those then create a new multi-package group of sessions,
- // carrying over all the session parameters and unmarking them as staged. On commit the
- // sessions will be installed atomically.
- final List<PackageInstallerSession> childSessions = new ArrayList<>();
- for (PackageInstallerSession s : session.getChildSessions()) {
- if (!isApexSession(s)) {
- childSessions.add(s);
- }
- }
- if (childSessions.isEmpty()) {
- // APEX-only multi-package staged session, nothing to do.
- return null;
- }
- final PackageInstaller.SessionParams params = session.params.copy();
- params.isStaged = false;
- final int apkParentSessionId = mPi.createSession(
- params, session.getInstallerPackageName(), session.getInstallerAttributionTag(),
- session.userId);
- final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
- try {
- apkParentSession.open();
- } catch (IOException e) {
- Slog.e(TAG, "Unable to prepare multi-package session for staged session "
- + session.sessionId);
- throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Unable to prepare multi-package session for staged session");
- }
-
- for (int i = 0, size = childSessions.size(); i < size; i++) {
- final PackageInstallerSession apkChildSession = createAndWriteApkSession(
- childSessions.get(i));
- try {
- apkParentSession.addChildSessionId(apkChildSession.sessionId);
- } catch (IllegalStateException e) {
- Slog.e(TAG, "Failed to add a child session for installing the APK files", e);
- throw new PackageManagerException(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
- "Failed to add a child session " + apkChildSession.sessionId);
- }
- }
- return apkParentSession;
- }
- return null;
- }
-
/**
* Throws a PackageManagerException if there are duplicate packages in apk and apk-in-apex.
*/
@@ -798,7 +653,7 @@ public class StagingManager {
}
final Set<String> apkNames = new ArraySet<>();
for (PackageInstallerSession s : session.getChildSessions()) {
- if (!isApexSession(s)) {
+ if (!s.isApexSession()) {
apkNames.add(s.getPackageName());
}
}
@@ -820,18 +675,18 @@ public class StagingManager {
private void installApksInSession(PackageInstallerSession session)
throws PackageManagerException {
- final PackageInstallerSession apksToInstall = extractApksInSession(session);
- if (apksToInstall == null) {
+ if (!session.containsApkSession()) {
return;
}
- if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+ if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is available for this session, notify the rollback
// manager of the apk session so it can properly enable rollback.
final RollbackManagerInternal rm =
LocalServices.getService(RollbackManagerInternal.class);
try {
- rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
+ // TODO(b/136257624): extra apk session id in rollback is now redundant.
+ rm.notifyStagedApkSession(session.sessionId, session.sessionId);
} catch (RuntimeException re) {
Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
+ session.sessionId, re);
@@ -839,7 +694,7 @@ public class StagingManager {
}
final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
- apksToInstall.commit(receiver.getIntentSender(), false);
+ session.installStagedSession(receiver.getIntentSender());
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
@@ -854,6 +709,8 @@ public class StagingManager {
}
void commitSession(@NonNull PackageInstallerSession session) {
+ // Store this parent session which will be used to check overlapping later
+ createSession(session);
mPreRebootVerificationHandler.startPreRebootVerification(session);
}
@@ -883,14 +740,16 @@ public class StagingManager {
* </ul>
* @throws PackageManagerException if session fails the check
*/
- void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
+ private void checkNonOverlappingWithStagedSessions(@NonNull PackageInstallerSession session)
throws PackageManagerException {
if (session.isMultiPackage()) {
// We cannot say a parent session overlaps until we process its children
return;
}
- if (session.getPackageName() == null) {
- throw new PackageManagerException(PackageManager.INSTALL_FAILED_INVALID_APK,
+
+ String packageName = session.getPackageName();
+ if (packageName == null) {
+ throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Cannot stage session " + session.sessionId + " with package name null");
}
@@ -902,40 +761,26 @@ public class StagingManager {
synchronized (mStagedSessions) {
for (int i = 0; i < mStagedSessions.size(); i++) {
final PackageInstallerSession stagedSession = mStagedSessions.valueAt(i);
- if (!stagedSession.isCommitted() || stagedSession.isStagedAndInTerminalState()
+ if (stagedSession.hasParentSessionId() || !stagedSession.isCommitted()
+ || stagedSession.isStagedAndInTerminalState()
|| stagedSession.isDestroyed()) {
continue;
}
- if (stagedSession.isMultiPackage()) {
- // This active parent staged session is useless as it doesn't have a package
- // name and the session we are checking is not a parent session either.
- continue;
- }
- // Check if stagedSession has an active parent session or not
- if (stagedSession.hasParentSessionId()) {
- final int parentId = stagedSession.getParentSessionId();
- final PackageInstallerSession parentSession = mStagedSessions.get(parentId);
- if (parentSession == null || parentSession.isStagedAndInTerminalState()
- || parentSession.isDestroyed()) {
- // Parent session has been abandoned or terminated already
- continue;
- }
- }
- // From here on, stagedSession is a non-parent active staged session
+ // From here on, stagedSession is a parent active staged session
// Check if session is one of the active sessions
- if (session.sessionId == stagedSession.sessionId) {
+ if (getSessionIdForParentOrSelf(session) == stagedSession.sessionId) {
Slog.w(TAG, "Session " + session.sessionId + " is already staged");
continue;
}
// New session cannot have same package name as one of the active sessions
- if (session.getPackageName().equals(stagedSession.getPackageName())) {
+ if (stagedSession.sessionContains(s -> s.getPackageName().equals(packageName))) {
if (isRollback) {
// If the new session is a rollback, then it gets priority. The existing
// session is failed to unblock rollback.
- final PackageInstallerSession root = getParentSessionOrSelf(stagedSession);
+ final PackageInstallerSession root = stagedSession;
if (!ensureActiveApexSessionIsAborted(root)) {
Slog.e(TAG, "Failed to abort apex session " + root.sessionId);
// Safe to ignore active apex session abort failure since session
@@ -949,7 +794,7 @@ public class StagingManager {
+ "blocking rollback session: " + session.sessionId);
} else {
throw new PackageManagerException(
- PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
+ SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Package: " + session.getPackageName() + " in session: "
+ session.sessionId + " has been staged already by session:"
+ " " + stagedSession.sessionId, null);
@@ -959,17 +804,16 @@ public class StagingManager {
// Staging multiple root sessions is not allowed if device doesn't support
// checkpoint. If session and stagedSession do not have common ancestor, they are
// from two different root sessions.
- if (!supportsCheckpoint && getSessionIdForParentOrSelf(session)
- != getSessionIdForParentOrSelf(stagedSession)) {
+ if (!supportsCheckpoint) {
throw new PackageManagerException(
- PackageManager.INSTALL_FAILED_OTHER_STAGED_SESSION_IN_PROGRESS,
+ SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Cannot stage multiple sessions without checkpoint support", null);
}
}
}
}
- void createSession(@NonNull PackageInstallerSession sessionInfo) {
+ private void createSession(@NonNull PackageInstallerSession sessionInfo) {
synchronized (mStagedSessions) {
mStagedSessions.append(sessionInfo.sessionId, sessionInfo);
}
@@ -1020,7 +864,7 @@ public class StagingManager {
* @return returns true if it is ensured that there is no active apex session, otherwise false
*/
private boolean ensureActiveApexSessionIsAborted(PackageInstallerSession session) {
- if (!sessionContainsApex(session)) {
+ if (!session.containsApexSession()) {
return true;
}
final ApexSessionInfo apexSession = mApexManager.getStagedSessionInfo(session.sessionId);
@@ -1045,16 +889,15 @@ public class StagingManager {
}
void restoreSession(@NonNull PackageInstallerSession session, boolean isDeviceUpgrading) {
- PackageInstallerSession sessionToResume = session;
- synchronized (mStagedSessions) {
- mStagedSessions.append(session.sessionId, session);
- if (session.hasParentSessionId()) {
- // Only parent sessions can be restored
- return;
- }
+ if (session.hasParentSessionId()) {
+ // Only parent sessions can be restored
+ return;
}
+ // Store this parent session which will be used to check overlapping later
+ createSession(session);
// The preconditions used during pre-reboot verification might have changed when device
// is upgrading. Updated staged sessions to activation failed before we resume the session.
+ PackageInstallerSession sessionToResume = session;
if (isDeviceUpgrading && !sessionToResume.isStagedAndInTerminalState()) {
sessionToResume.setStagedSessionFailed(SessionInfo.STAGED_SESSION_ACTIVATION_FAILED,
"Build fingerprint has changed");
@@ -1305,6 +1148,19 @@ public class StagingManager {
* See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
*/
private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
+ try {
+ if (session.isMultiPackage()) {
+ for (PackageInstallerSession s : session.getChildSessions()) {
+ checkNonOverlappingWithStagedSessions(s);
+ }
+ } else {
+ checkNonOverlappingWithStagedSessions(session);
+ }
+ } catch (PackageManagerException e) {
+ onPreRebootVerificationFailure(session, e.error, e.getMessage());
+ return;
+ }
+
int rollbackId = -1;
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is enabled for this session, we call through to the RollbackManager
@@ -1342,7 +1198,7 @@ public class StagingManager {
*/
private void handlePreRebootVerification_Apex(
@NonNull PackageInstallerSession session, int rollbackId) {
- final boolean hasApex = sessionContainsApex(session);
+ final boolean hasApex = session.containsApexSession();
// APEX checks. For single-package sessions, check if they contain an APEX. For
// multi-package sessions, find all the child sessions that contain an APEX.
@@ -1372,7 +1228,7 @@ public class StagingManager {
* {@link #notifyPreRebootVerification_Apk_Complete}
*/
private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
- if (!sessionContainsApk(session)) {
+ if (!session.containsApkSession()) {
notifyPreRebootVerification_Apk_Complete(session);
return;
}
@@ -1421,7 +1277,7 @@ public class StagingManager {
Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
session.setStagedSessionReady();
if (session.isStagedSessionReady()) {
- final boolean hasApex = sessionContainsApex(session);
+ final boolean hasApex = session.containsApexSession();
if (hasApex) {
try {
mApexManager.markStagedSessionReady(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 485868237895..7d48d0a6e266 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -33,16 +33,13 @@
"name": "CtsContentTestCases",
"options": [
{
- "include-filter": "android.content.pm.cts.ChecksumsTest"
- },
- {
- "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
- "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
+ "exclude-annotation": "org.junit.Ignore"
},
{
- "include-filter": "android.content.pm.cts.PackageManagerTest"
+ "include-filter": "android.content.pm.cts"
}
]
},
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0f0057905100..3ec156f6a3a1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -221,10 +221,12 @@ public class UserManagerService extends IUserManager.Stub {
private static final int ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION =
UserInfo.FLAG_MANAGED_PROFILE
+ | UserInfo.FLAG_PROFILE
| UserInfo.FLAG_EPHEMERAL
| UserInfo.FLAG_RESTRICTED
| UserInfo.FLAG_GUEST
- | UserInfo.FLAG_DEMO;
+ | UserInfo.FLAG_DEMO
+ | UserInfo.FLAG_FULL;
@VisibleForTesting
static final int MIN_USER_ID = UserHandle.MIN_SECONDARY_USER_ID;
@@ -1658,7 +1660,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
if (changed) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
sendUserInfoChangedBroadcast(userId);
} finally {
@@ -1727,6 +1729,7 @@ public class UserManagerService extends IUserManager.Stub {
}
public void makeInitialized(@UserIdInt int userId) {
+ if (DBG) Slog.d(LOG_TAG, "makeInitialized(" + userId + ")");
checkManageUsersPermission("makeInitialized");
boolean scheduleWriteUser = false;
UserData userData;
@@ -3551,8 +3554,7 @@ public class UserManagerService extends IUserManager.Stub {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
// intialize it.
- // TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
- // callback on SystemService instead.
+ // NOTE: user will be stopped on UserController.finishUserUnlockedCompleted().
Slog.i(LOG_TAG, "starting pre-created user " + userInfo.toFullString());
final IActivityManager am = ActivityManager.getService();
try {
@@ -3767,7 +3769,7 @@ public class UserManagerService extends IUserManager.Stub {
if (user == null) {
return null;
}
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user.id);
// Change the setting before applying the DISALLOW_SHARE_LOCATION restriction, otherwise
@@ -3820,7 +3822,7 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final UserData userData;
synchronized (mPackagesLock) {
@@ -3881,7 +3883,7 @@ public class UserManagerService extends IUserManager.Stub {
}
private boolean removeUserUnchecked(@UserIdInt int userId) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final UserData userData;
int currentUser = ActivityManager.getCurrentUser();
@@ -3989,7 +3991,7 @@ public class UserManagerService extends IUserManager.Stub {
// Let other services shutdown any activity and clean up their state before completely
// wiping the user's system directory and removing from the user list
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED);
removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
@@ -4151,7 +4153,7 @@ public class UserManagerService extends IUserManager.Stub {
}
private int getUidForPackage(String packageName) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mContext.getPackageManager().getApplicationInfo(packageName,
PackageManager.MATCH_ANY_USER).uid;
@@ -4706,9 +4708,12 @@ public class UserManagerService extends IUserManager.Stub {
final UserInfo user = users.get(i);
final boolean running = am.isUserRunning(user.id, 0);
final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
if (verbose) {
- pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s\n", i, user.id, user.name,
+ pw.printf("%d: id=%d, name=%s, flags=%s%s%s%s%s%s%s\n", i, user.id, user.name,
UserInfo.flagsToString(user.flags),
+ hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
running ? " (running)" : "",
user.partial ? " (partial)" : "",
user.preCreated ? " (pre-created)" : "",
@@ -4789,6 +4794,11 @@ public class UserManagerService extends IUserManager.Stub {
pw.print(" "); pw.print(userInfo);
pw.print(" serialNo="); pw.print(userInfo.serialNumber);
pw.print(" isPrimary="); pw.print(userInfo.isPrimary());
+ if (userInfo.profileGroupId != userInfo.id
+ && userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ pw.print(" parentId="); pw.print(userInfo.profileGroupId);
+ }
+
if (mRemovingUserIds.get(userId)) {
pw.print(" <removing> ");
}
@@ -4963,6 +4973,9 @@ public class UserManagerService extends IUserManager.Stub {
UserData userData = getUserDataNoChecks(userId);
if (userData != null) {
writeUserLP(userData);
+ } else {
+ Slog.i(LOG_TAG, "handle(WRITE_USER_MSG): no data for user " + userId
+ + ", it was probably removed before handler could handle it");
}
}
}
@@ -5062,7 +5075,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mPackagesLock) {
UserData userData = getUserDataNoChecks(userId);
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 37127233be13..f74913c03ce2 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -501,7 +501,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
") to " + outDexFile);
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mInstallLock) {
return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
index a5672664f6fd..8afe62aabd59 100644
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
@@ -46,7 +46,7 @@ public class ViewCompiler {
final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
") to " + outDexFile);
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mInstallLock) {
return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 865b8a1e97eb..fedbed2f7a01 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -32,17 +32,19 @@ import android.annotation.Nullable;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
import android.content.pm.parsing.component.ParsedPermission;
+import android.os.Build;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.util.ArrayUtils;
import com.android.server.pm.DumpState;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageSettingBase;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import libcore.util.EmptyArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -95,7 +97,8 @@ public final class BasePermission {
int uid;
/** Additional GIDs given to apps granted this permission */
- private int[] gids;
+ @NonNull
+ private int[] gids = EmptyArray.INT;
/**
* Flag indicating that {@link #gids} should be adjusted based on the
@@ -132,7 +135,7 @@ public final class BasePermission {
public int getUid() {
return uid;
}
- public void setGids(int[] gids, boolean perUser) {
+ public void setGids(@NonNull int[] gids, boolean perUser) {
this.gids = gids;
this.perUser = perUser;
}
@@ -141,18 +144,20 @@ public final class BasePermission {
}
public boolean hasGids() {
- return !ArrayUtils.isEmpty(gids);
+ return gids.length != 0;
}
+ @NonNull
public int[] computeGids(int userId) {
if (perUser) {
final int[] userGids = new int[gids.length];
for (int i = 0; i < gids.length; i++) {
- userGids[i] = UserHandle.getUid(userId, gids[i]);
+ final int gid = gids[i];
+ userGids[i] = UserHandle.getUid(userId, gid);
}
return userGids;
} else {
- return gids;
+ return gids.length != 0 ? gids.clone() : gids;
}
}
@@ -206,6 +211,11 @@ public final class BasePermission {
return perm != null && (perm.getFlags() & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0;
}
+ public boolean isInstallerExemptIgnored() {
+ return perm != null
+ && (perm.getFlags() & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0;
+ }
+
public boolean isSignature() {
return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
PermissionInfo.PROTECTION_SIGNATURE;
@@ -286,7 +296,8 @@ public final class BasePermission {
pendingPermissionInfo.packageName = newPackageName;
}
uid = 0;
- setGids(null, false);
+ gids = EmptyArray.INT;
+ perUser = false;
}
public boolean addToTree(@ProtectionLevel int protectionLevel,
@@ -423,19 +434,6 @@ public final class BasePermission {
throw new SecurityException("No permission tree found for " + permName);
}
- public void enforceDeclaredUsedAndRuntimeOrDevelopment(AndroidPackage pkg,
- UidPermissionState uidState) {
- int index = pkg.getRequestedPermissions().indexOf(name);
- if (!uidState.hasRequestedPermission(name) && index == -1) {
- throw new SecurityException("Package " + pkg.getPackageName()
- + " has not requested permission " + name);
- }
- if (!isRuntime() && !isDevelopment()) {
- throw new SecurityException("Permission " + name + " requested by "
- + pkg.getPackageName() + " is not a changeable permission type");
- }
- }
-
private static BasePermission findPermissionTree(
Collection<BasePermission> permissionTrees, String permName) {
for (BasePermission bp : permissionTrees) {
@@ -448,36 +446,38 @@ public final class BasePermission {
return null;
}
- public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
- if (groupName == null) {
- if (perm == null || perm.getGroup() == null) {
- return generatePermissionInfo(protectionLevel, flags);
- }
- } else {
- if (perm != null && groupName.equals(perm.getGroup())) {
- return PackageInfoUtils.generatePermissionInfo(perm, flags);
- }
- }
- return null;
+ @Nullable
+ public String getGroup() {
+ return perm != null ? perm.getGroup() : null;
+ }
+
+ @NonNull
+ public PermissionInfo generatePermissionInfo(int flags) {
+ return generatePermissionInfo(flags, Build.VERSION_CODES.CUR_DEVELOPMENT);
}
- public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+ @NonNull
+ public PermissionInfo generatePermissionInfo(int flags, int targetSdkVersion) {
PermissionInfo permissionInfo;
if (perm != null) {
- final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
permissionInfo = PackageInfoUtils.generatePermissionInfo(perm, flags);
- if (protectionLevelChanged) {
- // if we return different protection level, don't use the cached info
- permissionInfo = new PermissionInfo(permissionInfo);
- permissionInfo.protectionLevel = adjustedProtectionLevel;
+ } else {
+ permissionInfo = new PermissionInfo();
+ permissionInfo.name = name;
+ permissionInfo.packageName = sourcePackageName;
+ permissionInfo.nonLocalizedLabel = name;
+ }
+ if (targetSdkVersion >= Build.VERSION_CODES.O) {
+ permissionInfo.protectionLevel = protectionLevel;
+ } else {
+ final int protection = protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ if (protection == PermissionInfo.PROTECTION_SIGNATURE) {
+ // Signature permission's protection flags are always reported.
+ permissionInfo.protectionLevel = protectionLevel;
+ } else {
+ permissionInfo.protectionLevel = protection;
}
- return permissionInfo;
}
- permissionInfo = new PermissionInfo();
- permissionInfo.name = name;
- permissionInfo.packageName = sourcePackageName;
- permissionInfo.nonLocalizedLabel = name;
- permissionInfo.protectionLevel = protectionLevel;
return permissionInfo;
}
diff --git a/services/core/java/com/android/server/pm/permission/DevicePermissionState.java b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
index b9456acfced5..18936dda2ecc 100644
--- a/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/DevicePermissionState.java
@@ -21,57 +21,38 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
-
/**
* Permission state for this device.
*/
public final class DevicePermissionState {
- @GuardedBy("mLock")
- @NonNull
private final SparseArray<UserPermissionState> mUserStates = new SparseArray<>();
- @NonNull
- private final Object mLock;
-
- public DevicePermissionState(@NonNull Object lock) {
- mLock = lock;
- }
-
@Nullable
public UserPermissionState getUserState(@UserIdInt int userId) {
- synchronized (mLock) {
- return mUserStates.get(userId);
- }
+ return mUserStates.get(userId);
}
@NonNull
public UserPermissionState getOrCreateUserState(@UserIdInt int userId) {
- synchronized (mLock) {
- UserPermissionState userState = mUserStates.get(userId);
- if (userState == null) {
- userState = new UserPermissionState(mLock);
- mUserStates.put(userId, userState);
- }
- return userState;
+ UserPermissionState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new UserPermissionState();
+ mUserStates.put(userId, userState);
}
+ return userState;
}
public void removeUserState(@UserIdInt int userId) {
- synchronized (mLock) {
- mUserStates.delete(userId);
- }
+ mUserStates.delete(userId);
}
public int[] getUserIds() {
- synchronized (mLock) {
- final int userStatesSize = mUserStates.size();
- final int[] userIds = new int[userStatesSize];
- for (int i = 0; i < userStatesSize; i++) {
- final int userId = mUserStates.keyAt(i);
- userIds[i] = userId;
- }
- return userIds;
+ final int userStatesSize = mUserStates.size();
+ final int[] userIds = new int[userStatesSize];
+ for (int i = 0; i < userStatesSize; i++) {
+ final int userId = mUserStates.keyAt(i);
+ userIds[i] = userId;
}
+ return userIds;
}
}
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java
new file mode 100644
index 000000000000..346a2c527fcb
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionDataProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.pm.permission;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+
+/**
+ * An interface for legacy code to read permission data in order to maintain compatibility.
+ */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface LegacyPermissionDataProvider {
+ /**
+ * Get the legacy permission state of an app ID, either a package or a shared user.
+ *
+ * @param appId the app ID
+ * @return the legacy permission state
+ */
+ @NonNull
+ LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId);
+
+ /**
+ * Get the GIDs computed from the permission state of a UID, either a package or a shared user.
+ *
+ * @param uid the UID
+ * @return the GIDs for the UID
+ */
+ @NonNull
+ int[] getGidsForUid(int uid);
+}
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
new file mode 100644
index 000000000000..63f69cede59c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+
+/**
+ * Legacy permission state that was associated with packages or shared users.
+ */
+//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public final class LegacyPermissionState {
+ // Maps from user IDs to user states.
+ @NonNull
+ private final SparseArray<UserState> mUserStates = new SparseArray<>();
+
+ // Keyed by user IDs.
+ @NonNull
+ private final SparseBooleanArray mMissing = new SparseBooleanArray();
+
+ /**
+ * Copy from another permission state.
+ *
+ * @param other the other permission state.
+ *
+ * @hide
+ */
+ public void copyFrom(@NonNull LegacyPermissionState other) {
+ if (other == this) {
+ return;
+ }
+
+ mUserStates.clear();
+ final int userStatesSize = other.mUserStates.size();
+ for (int i = 0; i < userStatesSize; i++) {
+ mUserStates.put(other.mUserStates.keyAt(i),
+ new UserState(other.mUserStates.valueAt(i)));
+ }
+
+ mMissing.clear();
+ final int missingSize = other.mMissing.size();
+ for (int i = 0; i < missingSize; i++) {
+ mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i));
+ }
+ }
+
+ /**
+ * Reset this permission state.
+ *
+ * @hide
+ */
+ public void reset() {
+ mUserStates.clear();
+ mMissing.clear();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object == null) {
+ return false;
+ }
+ if (getClass() != object.getClass()) {
+ return false;
+ }
+ final LegacyPermissionState other = (LegacyPermissionState) object;
+ return Objects.equals(mUserStates, other.mUserStates)
+ && Objects.equals(mMissing, other.mMissing);
+ }
+
+ /**
+ * Put a install permission state.
+ *
+ * @param permissionState the permission state
+ */
+ public void putInstallPermissionState(@NonNull PermissionState permissionState) {
+ putPermissionState(permissionState, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Put a runtime permission state for a user.
+ *
+ * @param permissionState the permission state
+ * @param userId the user ID
+ */
+ public void putRuntimePermissionState(@NonNull PermissionState permissionState,
+ @UserIdInt int userId) {
+ checkUserId(userId);
+ putPermissionState(permissionState, userId);
+ }
+
+ private void putPermissionState(@NonNull PermissionState permissionState,
+ @UserIdInt int userId) {
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new UserState();
+ mUserStates.put(userId, userState);
+ }
+ userState.putPermissionState(permissionState);
+ }
+
+ /**
+ * Check whether there are any permission states for the given permissions.
+ *
+ * @param permissionNames the permission names
+ * @return whether there are any permission states
+ *
+ * @hide
+ */
+ public boolean hasPermissionState(@NonNull Collection<String> permissionNames) {
+ final int userStatesSize = mUserStates.size();
+ for (int i = 0; i < userStatesSize; i++) {
+ final UserState userState = mUserStates.valueAt(i);
+ for (final String permissionName : permissionNames) {
+ if (userState.getPermissionState(permissionName) != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get all the install permission states.
+ *
+ * @return the install permission states
+ */
+ @NonNull
+ public Collection<PermissionState> getInstallPermissionStates() {
+ return getPermissionStates(UserHandle.USER_ALL);
+ }
+
+ /**
+ * Get all the runtime permission states for a user.
+ *
+ * @param userId the user ID
+ * @return the runtime permission states
+ */
+ @NonNull
+ public Collection<PermissionState> getRuntimePermissionStates(@UserIdInt int userId) {
+ checkUserId(userId);
+ return getPermissionStates(userId);
+ }
+
+ @NonNull
+ private Collection<PermissionState> getPermissionStates(@UserIdInt int userId) {
+ final UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ return Collections.emptyList();
+ }
+ return userState.getPermissionStates();
+ }
+
+ /**
+ * Check whether the permission state is missing for a user.
+ * <p>
+ * This can happen if permission state is rolled back and we'll need to generate a reasonable
+ * default state to keep the app usable.
+ *
+ * @param userId the user ID
+ * @return whether the permission state is missing
+ */
+ public boolean isMissing(@UserIdInt int userId) {
+ checkUserId(userId);
+ return mMissing.get(userId);
+ }
+
+ /**
+ * Set whether the permission state is missing for a user.
+ * <p>
+ * This can happen if permission state is rolled back and we'll need to generate a reasonable
+ * default state to keep the app usable.
+ *
+ * @param missing whether the permission state is missing
+ * @param userId the user ID
+ */
+ public void setMissing(boolean missing, @UserIdInt int userId) {
+ checkUserId(userId);
+ if (missing) {
+ mMissing.put(userId, true);
+ } else {
+ mMissing.delete(userId);
+ }
+ }
+
+ private static void checkUserId(@UserIdInt int userId) {
+ if (userId < 0) {
+ throw new IllegalArgumentException("Invalid user ID " + userId);
+ }
+ }
+
+ /**
+ * Legacy state for permissions for a user.
+ */
+ private static final class UserState {
+ // Maps from permission names to permission states.
+ @NonNull
+ private final ArrayMap<String, PermissionState> mPermissionStates = new ArrayMap<>();
+
+ public UserState() {}
+
+ public UserState(@NonNull UserState other) {
+ final int permissionStatesSize = other.mPermissionStates.size();
+ for (int i = 0; i < permissionStatesSize; i++) {
+ mPermissionStates.put(other.mPermissionStates.keyAt(i),
+ new PermissionState(other.mPermissionStates.valueAt(i)));
+ }
+ }
+
+ @Nullable
+ public PermissionState getPermissionState(@NonNull String permissionName) {
+ return mPermissionStates.get(permissionName);
+ }
+
+ public void putPermissionState(@NonNull PermissionState permissionState) {
+ mPermissionStates.put(permissionState.getName(), permissionState);
+ }
+
+ @NonNull
+ public Collection<PermissionState> getPermissionStates() {
+ return Collections.unmodifiableCollection(mPermissionStates.values());
+ }
+ }
+
+ /**
+ * Legacy state for a single permission.
+ */
+ public static final class PermissionState {
+ @NonNull
+ private final BasePermission mPermission;
+
+ private final boolean mGranted;
+
+ private final int mFlags;
+
+ /**
+ * Create a new instance of this class.
+ *
+ * @param permission the {@link BasePermission} for the permission
+ * @param granted whether the permission is granted
+ * @param flags the permission flags
+ */
+ public PermissionState(@NonNull BasePermission permission, boolean granted, int flags) {
+ mPermission = permission;
+ mGranted = granted;
+ mFlags = flags;
+ }
+
+ private PermissionState(@NonNull PermissionState other) {
+ mPermission = other.mPermission;
+ mGranted = other.mGranted;
+ mFlags = other.mFlags;
+ }
+
+ /**
+ * Get the {@link BasePermission} for the permission.
+ *
+ * @return the {@link BasePermission}
+ */
+ @NonNull
+ public BasePermission getPermission() {
+ return mPermission;
+ }
+
+ /**
+ * Get the permission name.
+ *
+ * @return the permission name
+ */
+ @NonNull
+ public String getName() {
+ return mPermission.getName();
+ }
+
+ /**
+ * Get whether the permission is granted.
+ *
+ * @return whether the permission is granted
+ */
+ @NonNull
+ public boolean isGranted() {
+ return mGranted;
+ }
+
+ /**
+ * Get the permission flags.
+ *
+ * @return the permission flags
+ */
+ @NonNull
+ public int getFlags() {
+ return mFlags;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
index 01dc01efaca8..0e88862e01b1 100644
--- a/services/core/java/com/android/server/pm/permission/OWNERS
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -1,4 +1,5 @@
moltmann@google.com
+zhanghai@google.com
per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
@@ -7,3 +8,4 @@ per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
per-file DefaultPermissionGrantPolicy.java = patb@google.com
per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com
per-file DefaultPermissionGrantPolicy.java = moltmann@google.com
+per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index aa327ba02356..45872a833469 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -24,12 +24,14 @@ import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -53,11 +55,11 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING
import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.pm.permission.UidPermissionState.PERMISSION_OPERATION_FAILURE;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.Manifest;
+import android.annotation.AppIdInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,7 +68,6 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.compat.annotation.ChangeId;
@@ -102,7 +103,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.os.storage.StorageManager;
-import android.os.storage.StorageManagerInternal;
import android.permission.IOnPermissionsChangeListener;
import android.permission.IPermissionManager;
import android.permission.PermissionControllerManager;
@@ -137,7 +137,6 @@ import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.PackageSetting;
@@ -152,6 +151,8 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal.Permiss
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.policy.SoftRestrictedPermissionPolicy;
+import libcore.util.EmptyArray;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -177,15 +178,6 @@ import java.util.function.Consumer;
public class PermissionManagerService extends IPermissionManager.Stub {
private static final String TAG = "PackageManager";
- /** Permission grant: not grant the permission. */
- private static final int GRANT_DENIED = 1;
- /** Permission grant: grant the permission as an install permission. */
- private static final int GRANT_INSTALL = 2;
- /** Permission grant: grant the permission as a runtime one. */
- private static final int GRANT_RUNTIME = 3;
- /** Permission grant: grant as runtime a permission that was granted as an install time one. */
- private static final int GRANT_UPGRADE = 4;
-
private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60);
/** Cap the size of permission trees that 3rd party apps can define; in characters of text */
@@ -224,8 +216,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
/** Internal connection to the user manager */
private final UserManagerInternal mUserManagerInt;
+ @GuardedBy("mLock")
@NonNull
- private final DevicePermissionState mState;
+ private final DevicePermissionState mState = new DevicePermissionState();
/** Permission controller: User space permission management */
private PermissionControllerManager mPermissionControllerManager;
@@ -247,6 +240,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private final SparseArray<ArraySet<String>> mSystemPermissions;
/** Built-in group IDs given to all packages. Read from system configuration files. */
+ @NonNull
private final int[] mGlobalGids;
private final HandlerThread mHandlerThread;
@@ -393,7 +387,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
mSettings = new PermissionSettings(mLock);
- mState = new DevicePermissionState(mLock);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mHandlerThread = new ServiceThread(TAG,
@@ -551,22 +544,35 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
@Nullable
- public PermissionInfo getPermissionInfo(String permName, String packageName,
+ public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
@PermissionInfoFlags int flags) {
final int callingUid = getCallingUid();
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
}
- final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+ final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName);
+ final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage,
+ callingUid);
synchronized (mLock) {
final BasePermission bp = mSettings.getPermissionLocked(permName);
if (bp == null) {
return null;
}
- final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
- bp.getProtectionLevel(), pkg, callingUid);
- return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+ return bp.generatePermissionInfo(flags, targetSdkVersion);
+ }
+ }
+
+ private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID
+ || appId == Process.SHELL_UID) {
+ // System sees all flags.
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
+ if (pkg == null) {
+ return Build.VERSION_CODES.CUR_DEVELOPMENT;
+ }
+ return pkg.getTargetSdkVersion();
}
@Override
@@ -583,9 +589,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
for (BasePermission bp : mSettings.mPermissions.values()) {
- final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
- if (pi != null) {
- out.add(pi);
+ if (Objects.equals(bp.getGroup(), groupName)) {
+ out.add(bp.generatePermissionInfo(flags));
}
}
return new ParceledListSlice<>(out);
@@ -672,20 +677,23 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (pkg == null) {
return 0;
}
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ return 0;
+ }
+
synchronized (mLock) {
if (mSettings.getPermissionLocked(permName) == null) {
return 0;
}
+
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+ return 0;
+ }
+
+ return uidState.getPermissionFlags(permName);
}
- if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
- return 0;
- }
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
- return 0;
- }
- return uidState.getPermissionFlags(permName);
}
@Override
@@ -695,7 +703,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
boolean overridePolicy = false;
if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) {
if (checkAdjustPolicyFlagPermission) {
@@ -779,48 +787,52 @@ public class PermissionManagerService extends IPermissionManager.Stub {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
+ boolean isRequested = false;
+ // Fast path, the current package has requested the permission.
+ if (pkg.getRequestedPermissions().contains(permName)) {
+ isRequested = true;
+ }
+ if (!isRequested) {
+ // Slow path, go through all shared user packages.
+ String[] sharedUserPackageNames =
+ mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId);
+ for (String sharedUserPackageName : sharedUserPackageNames) {
+ AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage(
+ sharedUserPackageName);
+ if (sharedUserPkg != null
+ && sharedUserPkg.getRequestedPermissions().contains(permName)) {
+ isRequested = true;
+ break;
+ }
+ }
+ }
+
final BasePermission bp;
+ final boolean permissionUpdated;
synchronized (mLock) {
bp = mSettings.getPermissionLocked(permName);
- }
- if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + permName);
- }
+ if (bp == null) {
+ throw new IllegalArgumentException("Unknown permission: " + permName);
+ }
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
- return;
- }
+ if (bp.isInstallerExemptIgnored()) {
+ flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+ }
- final boolean hadState = uidState.getPermissionState(permName) != null;
- if (!hadState) {
- boolean isRequested = false;
- // Fast path, the current package has requested the permission.
- if (pkg.getRequestedPermissions().contains(permName)) {
- isRequested = true;
- }
- if (!isRequested) {
- // Slow path, go through all shared user packages.
- String[] sharedUserPackageNames =
- mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId);
- for (String sharedUserPackageName : sharedUserPackageNames) {
- AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage(
- sharedUserPackageName);
- if (sharedUserPkg != null
- && sharedUserPkg.getRequestedPermissions().contains(permName)) {
- isRequested = true;
- break;
- }
- }
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+ return;
}
- if (!isRequested) {
+
+ if (!uidState.hasPermissionState(permName) && !isRequested) {
Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName);
return;
}
+
+ permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues);
}
- final boolean permissionUpdated =
- uidState.updatePermissionFlags(bp, flagMask, flagValues);
+
if (permissionUpdated && bp.isRuntime()) {
notifyRuntimePermissionStateChanged(packageName, userId);
}
@@ -864,14 +876,17 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean[] changed = new boolean[1];
mPackageManagerInt.forEachPackage(pkg -> {
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return;
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG,
+ "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
+ return;
+ }
+ changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
+ effectiveFlagMask, effectiveFlagValues);
}
- changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
- effectiveFlagMask, effectiveFlagValues);
mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
});
@@ -902,16 +917,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
private int checkPermissionImpl(String permName, String pkgName, int userId) {
- try {
- enforceCrossUserOrProfilePermission(Binder.getCallingUid(), userId,
- false, false, "checkPermissionImpl");
- } catch (Exception e) {
- Slog.e(TAG, "Invalid cross user access", e);
- EventLog.writeEvent(0x534e4554, "153996875", "checkPermissionImpl", pkgName);
-
- throw e;
- }
-
final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName);
if (pkg == null) {
return PackageManager.PERMISSION_DENIED;
@@ -933,33 +938,37 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final int uid = UserHandle.getUid(userId, pkg.getUid());
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return PackageManager.PERMISSION_DENIED;
- }
+ final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null;
- if (checkSinglePermissionInternal(uid, uidState, permissionName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
+ return PackageManager.PERMISSION_DENIED;
+ }
- final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
- if (fullerPermissionName != null
- && checkSinglePermissionInternal(uid, uidState, fullerPermissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+
+ final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+ if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState,
+ fullerPermissionName, isInstantApp)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
}
return PackageManager.PERMISSION_DENIED;
}
- private boolean checkSinglePermissionInternal(int uid,
- @NonNull UidPermissionState uidState, @NonNull String permissionName) {
- if (!uidState.hasPermission(permissionName)) {
+ private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState,
+ @NonNull String permissionName, boolean isInstantApp) {
+ if (!uidState.isPermissionGranted(permissionName)) {
return false;
}
- if (mPackageManagerInt.getInstantAppPackageName(uid) != null) {
+ if (isInstantApp) {
return mSettings.isPermissionInstant(permissionName);
}
@@ -989,16 +998,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
private int checkUidPermissionImpl(String permName, int uid) {
- try {
- enforceCrossUserOrProfilePermission(Binder.getCallingUid(), UserHandle.getUserId(uid),
- false, false, "checkUidPermissionImpl");
- } catch (Exception e) {
- Slog.e(TAG, "Invalid cross user access", e);
- EventLog.writeEvent(0x534e4554, "153996875", "checkUidPermissionImpl", uid);
-
- throw e;
- }
-
final AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
return checkUidPermissionInternal(pkg, uid, permName);
}
@@ -1017,24 +1016,25 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return checkPermissionInternal(pkg, false, permissionName, userId);
}
- if (checkSingleUidPermissionInternal(uid, permissionName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
+ synchronized (mLock) {
+ if (checkSingleUidPermissionInternalLocked(uid, permissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
- final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
- if (fullerPermissionName != null
- && checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
- return PackageManager.PERMISSION_GRANTED;
+ final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+ if (fullerPermissionName != null
+ && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
}
return PackageManager.PERMISSION_DENIED;
}
- private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) {
- synchronized (mLock) {
- ArraySet<String> permissions = mSystemPermissions.get(uid);
- return permissions != null && permissions.contains(permissionName);
- }
+ private boolean checkSingleUidPermissionInternalLocked(int uid,
+ @NonNull String permissionName) {
+ ArraySet<String> permissions = mSystemPermissions.get(uid);
+ return permissions != null && permissions.contains(permissionName);
}
@Override
@@ -1067,7 +1067,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// check.
if (packageName != null) {
// Allow access to a package that has been granted the READ_DEVICE_IDENTIFIERS appop.
- long token = mInjector.clearCallingIdentity();
+ final long token = mInjector.clearCallingIdentity();
AppOpsManager appOpsManager = (AppOpsManager) mInjector.getSystemService(
Context.APP_OPS_SERVICE);
try {
@@ -1118,7 +1118,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
Preconditions.checkArgumentNonNegative(userId, null);
if (UserHandle.getCallingUserId() != userId) {
@@ -1142,16 +1143,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
- && !isCallerPrivileged) {
- throw new SecurityException("Querying system whitelist requires "
+ if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) {
+ throw new SecurityException("Querying system or role allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
- throw new SecurityException("Querying upgrade or installer whitelist"
+ throw new SecurityException("Querying upgrade or installer allowlist"
+ " requires being installer on record or "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -1159,39 +1160,45 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final long identity = Binder.clearCallingIdentity();
try {
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
- return null;
- }
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user "
+ + userId);
+ return null;
+ }
- int queryFlags = 0;
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
- queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
- }
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
- queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
- }
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
- queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
- }
+ int queryFlags = 0;
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
+ queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+ }
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
+ queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+ }
+ if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+ queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+ }
+ if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) {
+ queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ }
- ArrayList<String> whitelistedPermissions = null;
+ ArrayList<String> whitelistedPermissions = null;
- final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
- for (int i = 0; i < permissionCount; i++) {
- final String permissionName = pkg.getRequestedPermissions().get(i);
- final int currentFlags =
- uidState.getPermissionFlags(permissionName);
- if ((currentFlags & queryFlags) != 0) {
- if (whitelistedPermissions == null) {
- whitelistedPermissions = new ArrayList<>();
+ final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
+ for (int i = 0; i < permissionCount; i++) {
+ final String permissionName = pkg.getRequestedPermissions().get(i);
+ final int currentFlags =
+ uidState.getPermissionFlags(permissionName);
+ if ((currentFlags & queryFlags) != 0) {
+ if (whitelistedPermissions == null) {
+ whitelistedPermissions = new ArrayList<>();
+ }
+ whitelistedPermissions.add(permissionName);
}
- whitelistedPermissions.add(permissionName);
}
- }
- return whitelistedPermissions;
+ return whitelistedPermissions;
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1267,7 +1274,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE);
Preconditions.checkArgument(Integer.bitCount(flags) == 1);
Preconditions.checkArgumentNonNegative(userId, null);
@@ -1293,15 +1301,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean isCallerInstallerOnRecord =
mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
- if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+ if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0
&& !isCallerPrivileged) {
- throw new SecurityException("Modifying system whitelist requires "
+ throw new SecurityException("Modifying system or role allowlist requires "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
- throw new SecurityException("Modifying upgrade whitelist requires"
+ throw new SecurityException("Modifying upgrade allowlist requires"
+ " being installer on record or "
+ Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
}
@@ -1454,12 +1463,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
"grantRuntimePermission");
final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
- final PackageSetting ps = (PackageSetting) mPackageManagerInt.getPackageSetting(
- packageName);
+ final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
if (pkg == null || ps == null) {
Log.e(TAG, "Unknown package: " + packageName);
return;
}
+ if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+
final BasePermission bp;
synchronized (mLock) {
bp = mSettings.getPermissionLocked(permName);
@@ -1467,46 +1479,17 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + permName);
}
- if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
- throw new IllegalArgumentException("Unknown package: " + packageName);
- }
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return;
+ if (!(bp.isRuntime() || bp.isDevelopment())) {
+ throw new SecurityException("Permission " + permName + " requested by "
+ + pkg.getPackageName() + " is not a changeable permission type");
}
- bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState);
-
// If a permission review is required for legacy apps we represent
// their permissions as always granted runtime ones since we need
// to keep the review required permission flag per user while an
// install permission's state is shared across all users.
- if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
- && bp.isRuntime()) {
- return;
- }
-
- final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-
- final int flags = uidState.getPermissionFlags(permName);
- if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
- Log.e(TAG, "Cannot grant system fixed permission "
- + permName + " for package " + packageName);
- return;
- }
- if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
- Log.e(TAG, "Cannot grant policy fixed permission "
- + permName + " for package " + packageName);
- return;
- }
-
- if (bp.isHardRestricted()
- && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
- Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
- + permName + " for package " + packageName);
+ if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
return;
}
@@ -1518,72 +1501,83 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return;
}
- if (bp.isDevelopment()) {
- // Development permissions must be handled specially, since they are not
- // normal runtime permissions. For now they apply to all users.
- // TODO(zhanghai): We are breaking the behavior above by making all permission state
- // per-user. It isn't documented behavior and relatively rarely used anyway.
- if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
- if (callback != null) {
- callback.onInstallPermissionGranted();
- }
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
+ return;
}
- return;
- }
- if (ps.getInstantApp(userId) && !bp.isInstant()) {
- throw new SecurityException("Cannot grant non-ephemeral permission"
- + permName + " for package " + packageName);
- }
+ if (!(uidState.hasPermissionState(permName)
+ || pkg.getRequestedPermissions().contains(permName))) {
+ throw new SecurityException("Package " + pkg.getPackageName()
+ + " has not requested permission " + permName);
+ }
- if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
- Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
- return;
- }
+ final int flags = uidState.getPermissionFlags(permName);
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+ Log.e(TAG, "Cannot grant system fixed permission "
+ + permName + " for package " + packageName);
+ return;
+ }
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ Log.e(TAG, "Cannot grant policy fixed permission "
+ + permName + " for package " + packageName);
+ return;
+ }
- final int result = uidState.grantPermission(bp);
- switch (result) {
- case PERMISSION_OPERATION_FAILURE: {
+ if (bp.isHardRestricted()
+ && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+ Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
+ + permName + " for package " + packageName);
return;
}
- case UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
- if (callback != null) {
- callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
+ if (bp.isDevelopment()) {
+ // Development permissions must be handled specially, since they are not
+ // normal runtime permissions. For now they apply to all users.
+ // TODO(zhanghai): We are breaking the behavior above by making all permission state
+ // per-user. It isn't documented behavior and relatively rarely used anyway.
+ if (!uidState.grantPermission(bp)) {
+ return;
+ }
+ } else {
+ if (ps.getInstantApp(userId) && !bp.isInstant()) {
+ throw new SecurityException("Cannot grant non-ephemeral permission" + permName
+ + " for package " + packageName);
+ }
+
+ if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
+ Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+ return;
+ }
+
+ if (!uidState.grantPermission(bp)) {
+ return;
}
}
- break;
}
if (bp.isRuntime()) {
logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
}
+ final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
if (callback != null) {
- callback.onPermissionGranted(uid, userId);
+ if (bp.isDevelopment()) {
+ callback.onInstallPermissionGranted();
+ } else {
+ callback.onPermissionGranted(uid, userId);
+ }
+ if (bp.hasGids()) {
+ callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
+ }
}
if (bp.isRuntime()) {
notifyRuntimePermissionStateChanged(packageName, userId);
}
-
- // Only need to do this if user is initialized. Otherwise it's a new user
- // and there are no processes running as the user yet and there's no need
- // to make an expensive call to remount processes for the changed permissions.
- if (READ_EXTERNAL_STORAGE.equals(permName)
- || WRITE_EXTERNAL_STORAGE.equals(permName)) {
- final long token = Binder.clearCallingIdentity();
- try {
- if (mUserManagerInt.isUserInitialized(userId)) {
- StorageManagerInternal storageManagerInternal = LocalServices.getService(
- StorageManagerInternal.class);
- storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
}
@Override
@@ -1632,61 +1626,57 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final BasePermission bp = mSettings.getPermissionLocked(permName);
+ final BasePermission bp = mSettings.getPermission(permName);
if (bp == null) {
throw new IllegalArgumentException("Unknown permission: " + permName);
}
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return;
+ if (!(bp.isRuntime() || bp.isDevelopment())) {
+ throw new SecurityException("Permission " + permName + " requested by "
+ + pkg.getPackageName() + " is not a changeable permission type");
}
- bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg, uidState);
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
+ return;
+ }
- // If a permission review is required for legacy apps we represent
- // their permissions as always granted runtime ones since we need
- // to keep the review required permission flag per user while an
- // install permission's state is shared across all users.
- if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
- && bp.isRuntime()) {
- return;
- }
+ if (!(uidState.hasPermissionState(permName)
+ || pkg.getRequestedPermissions().contains(permName))) {
+ throw new SecurityException("Package " + pkg.getPackageName()
+ + " has not requested permission " + permName);
+ }
- final int flags = uidState.getPermissionFlags(permName);
- // Only the system may revoke SYSTEM_FIXED permissions.
- if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
- && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
- throw new SecurityException("Non-System UID cannot revoke system fixed permission "
- + permName + " for package " + packageName);
- }
- if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
- throw new SecurityException("Cannot revoke policy fixed permission "
- + permName + " for package " + packageName);
- }
+ // If a permission review is required for legacy apps we represent
+ // their permissions as always granted runtime ones since we need
+ // to keep the review required permission flag per user while an
+ // install permission's state is shared across all users.
+ if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
+ return;
+ }
+
+ final int flags = uidState.getPermissionFlags(permName);
+ // Only the system may revoke SYSTEM_FIXED permissions.
+ if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+ && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
+ throw new SecurityException("Non-System UID cannot revoke system fixed permission "
+ + permName + " for package " + packageName);
+ }
+ if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+ throw new SecurityException("Cannot revoke policy fixed permission "
+ + permName + " for package " + packageName);
+ }
- if (bp.isDevelopment()) {
// Development permissions must be handled specially, since they are not
// normal runtime permissions. For now they apply to all users.
// TODO(zhanghai): We are breaking the behavior above by making all permission state
// per-user. It isn't documented behavior and relatively rarely used anyway.
- if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
- if (callback != null) {
- mDefaultPermissionCallback.onInstallPermissionRevoked();
- }
+ if (!uidState.revokePermission(bp)) {
+ return;
}
- return;
- }
-
- // Permission is already revoked, no need to do anything.
- if (!uidState.hasPermission(permName)) {
- return;
- }
-
- if (uidState.revokePermission(bp) == PERMISSION_OPERATION_FAILURE) {
- return;
}
if (bp.isRuntime()) {
@@ -1694,8 +1684,12 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
if (callback != null) {
- callback.onPermissionRevoked(UserHandle.getUid(userId,
- UserHandle.getAppId(pkg.getUid())), userId, reason);
+ if (bp.isDevelopment()) {
+ mDefaultPermissionCallback.onInstallPermissionRevoked();
+ } else {
+ callback.onPermissionRevoked(UserHandle.getUid(userId,
+ UserHandle.getAppId(pkg.getUid())), userId, reason);
+ }
}
if (bp.isRuntime()) {
@@ -1730,7 +1724,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
* @param pkg The package for which to reset.
* @param userId The device user for which to do a reset.
*/
- @GuardedBy("mLock")
private void resetRuntimePermissionsInternal(final AndroidPackage pkg,
final int userId) {
final String packageName = pkg.getPackageName();
@@ -2099,6 +2092,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return false;
}
+ BasePermission permission = getPermission(permName);
+ if (permission == null) {
+ return false;
+ }
+ if (permission.isHardRestricted()
+ && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+ return false;
+ }
+
final long token = Binder.clearCallingIdentity();
try {
if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
@@ -2245,32 +2247,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- private int adjustPermissionProtectionFlagsLocked(int protectionLevel,
- @Nullable AndroidPackage pkg, int uid) {
- // Signature permission flags area always reported
- final int protectionLevelMasked = protectionLevel
- & (PermissionInfo.PROTECTION_NORMAL
- | PermissionInfo.PROTECTION_DANGEROUS
- | PermissionInfo.PROTECTION_SIGNATURE);
- if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
- return protectionLevel;
- }
- // System sees all flags.
- final int appId = UserHandle.getAppId(uid);
- if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
- || appId == Process.SHELL_UID) {
- return protectionLevel;
- }
- if (pkg == null) {
- return protectionLevel;
- }
- if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
- return protectionLevelMasked;
- }
- // Apps that target O see flags for all protection levels.
- return protectionLevel;
- }
-
/**
* We might auto-grant permissions if any permission of the group is already granted. Hence if
* the group of a granted permission changes we need to revoke it to avoid having permissions of
@@ -2358,7 +2334,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// Assume by default that we did not install this permission into the system.
p.setFlags(p.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
- synchronized (PermissionManagerService.this.mLock) {
+ synchronized (mLock) {
// Now that permission groups have a special meaning, we ignore permission
// groups for legacy apps to prevent unexpected behavior. In particular,
// permissions for one app being granted to someone just because they happen
@@ -2498,57 +2474,47 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (ps == null) {
return Collections.emptySet();
}
- final UidPermissionState uidState = getUidState(ps, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
- return Collections.emptySet();
- }
- if (!ps.getInstantApp(userId)) {
- return uidState.getPermissions();
- } else {
- // Install permission state is shared among all users, but instant app state is
- // per-user, so we can only filter it here unless we make install permission state
- // per-user as well.
- final Set<String> instantPermissions = new ArraySet<>(uidState.getPermissions());
- instantPermissions.removeIf(permissionName -> {
- BasePermission permission = mSettings.getPermission(permissionName);
- if (permission == null) {
- return true;
- }
- if (!permission.isInstant()) {
- EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
- ps.getAppId()), permissionName);
- return true;
- }
- return false;
- });
- return instantPermissions;
+
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(ps, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+ return Collections.emptySet();
+ }
+ if (!ps.getInstantApp(userId)) {
+ return uidState.getGrantedPermissions();
+ } else {
+ // Install permission state is shared among all users, but instant app state is
+ // per-user, so we can only filter it here unless we make install permission state
+ // per-user as well.
+ final Set<String> instantPermissions =
+ new ArraySet<>(uidState.getGrantedPermissions());
+ instantPermissions.removeIf(permissionName -> {
+ BasePermission permission = mSettings.getPermission(permissionName);
+ if (permission == null) {
+ return true;
+ }
+ if (!permission.isInstant()) {
+ EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
+ ps.getAppId()), permissionName);
+ return true;
+ }
+ return false;
+ });
+ return instantPermissions;
+ }
}
}
- @Nullable
+ @NonNull
private int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
BasePermission permission = mSettings.getPermission(permissionName);
if (permission == null) {
- return null;
+ return EmptyArray.INT;
}
return permission.computeGids(userId);
}
- @Nullable
- private int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
- final PackageSetting ps = mPackageManagerInt.getPackageSetting(packageName);
- if (ps == null) {
- return null;
- }
- final UidPermissionState uidState = getUidState(ps, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
- return null;
- }
- return uidState.computeGids(userId);
- }
-
/**
* Restore the permission state for a package.
*
@@ -2589,502 +2555,355 @@ public class PermissionManagerService extends IPermissionManager.Stub {
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
- for (final int userId : userIds) {
- final UserPermissionState userState = mState.getOrCreateUserState(userId);
- final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
+ final ArraySet<String> shouldGrantSignaturePermission = new ArraySet<>();
+ final List<String> requestedPermissions = pkg.getRequestedPermissions();
+ final int requestedPermissionsSize = requestedPermissions.size();
+ for (int i = 0; i < requestedPermissionsSize; i++) {
+ final String permissionName = pkg.getRequestedPermissions().get(i);
- if (uidState.isMissing()) {
- Collection<String> requestedPermissions;
- int targetSdkVersion;
- if (!ps.isSharedUser()) {
- requestedPermissions = pkg.getRequestedPermissions();
- targetSdkVersion = pkg.getTargetSdkVersion();
- } else {
- requestedPermissions = new ArraySet<>();
- targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- List<AndroidPackage> packages = ps.getSharedUser().getPackages();
- int packagesSize = packages.size();
- for (int i = 0; i < packagesSize; i++) {
- AndroidPackage sharedUserPackage = packages.get(i);
- requestedPermissions.addAll(sharedUserPackage.getRequestedPermissions());
- targetSdkVersion = Math.min(targetSdkVersion,
- sharedUserPackage.getTargetSdkVersion());
- }
+ final BasePermission permission = mSettings.getPermission(permissionName);
+ if (permission == null) {
+ continue;
+ }
+ if (permission.isSignature() && shouldGrantSignaturePermission(pkg, ps,
+ permission)) {
+ shouldGrantSignaturePermission.add(permissionName);
+ }
+ }
+
+ final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray();
+ if (mPermissionPolicyInternal != null) {
+ for (final int userId : userIds) {
+ if (mPermissionPolicyInternal.isInitialized(userId)) {
+ isPermissionPolicyInitialized.put(userId, true);
}
+ }
+ }
- for (String permissionName : requestedPermissions) {
- BasePermission permission = mSettings.getPermission(permissionName);
- if (permission == null) {
- continue;
+ synchronized (mLock) {
+ for (final int userId : userIds) {
+ final UserPermissionState userState = mState.getOrCreateUserState(userId);
+ final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
+
+ if (uidState.isMissing()) {
+ Collection<String> uidRequestedPermissions;
+ int targetSdkVersion;
+ if (!ps.isSharedUser()) {
+ uidRequestedPermissions = pkg.getRequestedPermissions();
+ targetSdkVersion = pkg.getTargetSdkVersion();
+ } else {
+ uidRequestedPermissions = new ArraySet<>();
+ targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ List<AndroidPackage> packages = ps.getSharedUser().getPackages();
+ int packagesSize = packages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ AndroidPackage sharedUserPackage = packages.get(i);
+ uidRequestedPermissions.addAll(
+ sharedUserPackage.getRequestedPermissions());
+ targetSdkVersion = Math.min(targetSdkVersion,
+ sharedUserPackage.getTargetSdkVersion());
+ }
}
- if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
- && permission.isRuntime() && !permission.isRemoved()) {
- if (permission.isHardOrSoftRestricted()
- || permission.isImmutablyRestricted()) {
- uidState.updatePermissionFlags(permission,
- FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
- FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+
+ for (String permissionName : uidRequestedPermissions) {
+ BasePermission permission = mSettings.getPermission(permissionName);
+ if (permission == null) {
+ continue;
}
- if (targetSdkVersion < Build.VERSION_CODES.M) {
- uidState.updatePermissionFlags(permission,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
- uidState.grantPermission(permission);
+ if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
+ && permission.isRuntime() && !permission.isRemoved()) {
+ if (permission.isHardOrSoftRestricted()
+ || permission.isImmutablyRestricted()) {
+ uidState.updatePermissionFlags(permission,
+ FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+ FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.M) {
+ uidState.updatePermissionFlags(permission,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+ uidState.grantPermission(permission);
+ }
}
}
- }
- uidState.setMissing(false);
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- }
+ uidState.setMissing(false);
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ }
- UidPermissionState origState = uidState;
+ UidPermissionState origState = uidState;
- boolean changedInstallPermission = false;
+ boolean changedInstallPermission = false;
- if (replace) {
- userState.setInstallPermissionsFixed(ps.name, false);
- if (!ps.isSharedUser()) {
- origState = new UidPermissionState(uidState);
- uidState.reset();
- } else {
- // We need to know only about runtime permission changes since the
- // calling code always writes the install permissions state but
- // the runtime ones are written only if changed. The only cases of
- // changed runtime permissions here are promotion of an install to
- // runtime and revocation of a runtime from a shared user.
- synchronized (mLock) {
- if (revokeUnusedSharedUserPermissionsLocked(
- ps.getSharedUser().getPackages(), uidState)) {
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- runtimePermissionsRevoked = true;
+ if (replace) {
+ userState.setInstallPermissionsFixed(ps.name, false);
+ if (!ps.isSharedUser()) {
+ origState = new UidPermissionState(uidState);
+ uidState.reset();
+ } else {
+ // We need to know only about runtime permission changes since the
+ // calling code always writes the install permissions state but
+ // the runtime ones are written only if changed. The only cases of
+ // changed runtime permissions here are promotion of an install to
+ // runtime and revocation of a runtime from a shared user.
+ synchronized (mLock) {
+ if (revokeUnusedSharedUserPermissionsLocked(
+ ps.getSharedUser().getPackages(), uidState)) {
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ runtimePermissionsRevoked = true;
+ }
}
}
}
- }
- uidState.setGlobalGids(mGlobalGids);
+ ArraySet<String> newImplicitPermissions = new ArraySet<>();
+ final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
- ArraySet<String> newImplicitPermissions = new ArraySet<>();
- final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
+ for (int i = 0; i < requestedPermissionsSize; i++) {
+ final String permName = requestedPermissions.get(i);
- final int N = pkg.getRequestedPermissions().size();
- for (int i = 0; i < N; i++) {
- final String permName = pkg.getRequestedPermissions().get(i);
- final BasePermission bp = mSettings.getPermission(permName);
- final boolean appSupportsRuntimePermissions =
- pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
- String upgradedActivityRecognitionPermission = null;
+ final BasePermission bp = mSettings.getPermission(permName);
+ final boolean appSupportsRuntimePermissions =
+ pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
+ String legacyActivityRecognitionPermission = null;
- if (DEBUG_INSTALL && bp != null) {
- Log.i(TAG, "Package " + friendlyName
- + " checking " + permName + ": " + bp);
- }
+ if (DEBUG_INSTALL && bp != null) {
+ Log.i(TAG, "Package " + friendlyName
+ + " checking " + permName + ": " + bp);
+ }
- if (bp == null || getSourcePackageSetting(bp) == null) {
- if (packageOfInterest == null || packageOfInterest.equals(
- pkg.getPackageName())) {
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Unknown permission " + permName
- + " in package " + friendlyName);
+ // TODO(zhanghai): I don't think we need to check source package setting if
+ // permission is present, because otherwise the permission should have been
+ // removed.
+ if (bp == null /*|| getSourcePackageSetting(bp) == null*/) {
+ if (packageOfInterest == null || packageOfInterest.equals(
+ pkg.getPackageName())) {
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Unknown permission " + permName
+ + " in package " + friendlyName);
+ }
}
+ continue;
}
- continue;
- }
- // Cache newImplicitPermissions before modifing permissionsState as for the shared
- // uids the original and new state are the same object
- if (!origState.hasRequestedPermission(permName)
- && (pkg.getImplicitPermissions().contains(permName)
- || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
- if (pkg.getImplicitPermissions().contains(permName)) {
- // If permName is an implicit permission, try to auto-grant
- newImplicitPermissions.add(permName);
+ // Cache newImplicitPermissions before modifing permissionsState as for the
+ // shared uids the original and new state are the same object
+ if (!origState.hasPermissionState(permName)
+ && (pkg.getImplicitPermissions().contains(permName)
+ || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
+ if (pkg.getImplicitPermissions().contains(permName)) {
+ // If permName is an implicit permission, try to auto-grant
+ newImplicitPermissions.add(permName);
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, permName + " is newly added for " + friendlyName);
- }
- } else {
- // Special case for Activity Recognition permission. Even if AR permission
- // is not an implicit permission we want to add it to the list (try to
- // auto-grant it) if the app was installed on a device before AR permission
- // was split, regardless of if the app now requests the new AR permission
- // or has updated its target SDK and AR is no longer implicit to it.
- // This is a compatibility workaround for apps when AR permission was
- // split in Q.
- final List<SplitPermissionInfoParcelable> permissionList =
- getSplitPermissions();
- int numSplitPerms = permissionList.size();
- for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
- SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
- String splitPermName = sp.getSplitPermission();
- if (sp.getNewPermissions().contains(permName)
- && origState.hasInstallPermission(splitPermName)) {
- upgradedActivityRecognitionPermission = splitPermName;
- newImplicitPermissions.add(permName);
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, permName + " is newly added for " + friendlyName);
+ }
+ } else {
+ // Special case for Activity Recognition permission. Even if AR
+ // permission is not an implicit permission we want to add it to the
+ // list (try to auto-grant it) if the app was installed on a device
+ // before AR permission was split, regardless of if the app now requests
+ // the new AR permission or has updated its target SDK and AR is no
+ // longer implicit to it. This is a compatibility workaround for apps
+ // when AR permission was split in Q.
+ // TODO(zhanghai): This calls into SystemConfig, which generally
+ // shouldn't cause deadlock, but maybe we should keep a cache of the
+ // split permission list and just eliminate the possibility.
+ final List<SplitPermissionInfoParcelable> permissionList =
+ getSplitPermissions();
+ int numSplitPerms = permissionList.size();
+ for (int splitPermNum = 0; splitPermNum < numSplitPerms;
+ splitPermNum++) {
+ SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
+ String splitPermName = sp.getSplitPermission();
+ if (sp.getNewPermissions().contains(permName)
+ && origState.isPermissionGranted(splitPermName)) {
+ legacyActivityRecognitionPermission = splitPermName;
+ newImplicitPermissions.add(permName);
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, permName + " is newly added for "
- + friendlyName);
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, permName + " is newly added for "
+ + friendlyName);
+ }
+ break;
}
- break;
}
}
}
- }
- // TODO(b/140256621): The package instant app method has been removed
- // as part of work in b/135203078, so this has been commented out in the meantime
- // Limit ephemeral apps to ephemeral allowed permissions.
- // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
- // if (DEBUG_PERMISSIONS) {
- // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
- // + " for package " + pkg.getPackageName());
- // }
- // continue;
- // }
-
- if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
- if (DEBUG_PERMISSIONS) {
- Log.i(TAG, "Denying runtime-only permission " + bp.getName()
- + " for package " + friendlyName);
+ // TODO(b/140256621): The package instant app method has been removed
+ // as part of work in b/135203078, so this has been commented out in the
+ // meantime
+ // Limit ephemeral apps to ephemeral allowed permissions.
+ // if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
+ // if (DEBUG_PERMISSIONS) {
+ // Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+ // + " for package " + pkg.getPackageName());
+ // }
+ // continue;
+ // }
+
+ if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+ if (DEBUG_PERMISSIONS) {
+ Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+ + " for package " + friendlyName);
+ }
+ continue;
}
- continue;
- }
- final String perm = bp.getName();
- boolean allowedSig = false;
- int grant = GRANT_DENIED;
+ final String perm = bp.getName();
- // Keep track of app op permissions.
- if (bp.isAppOp()) {
- mSettings.addAppOpPackage(perm, pkg.getPackageName());
- }
-
- if (bp.isNormal()) {
- // For all apps normal permissions are install time ones.
- grant = GRANT_INSTALL;
- } else if (bp.isRuntime()) {
- if (origState.hasInstallPermission(bp.getName())
- || upgradedActivityRecognitionPermission != null) {
- // Before Q we represented some runtime permissions as install permissions,
- // in Q we cannot do this anymore. Hence upgrade them all.
- grant = GRANT_UPGRADE;
- } else {
- // For modern apps keep runtime permissions unchanged.
- grant = GRANT_RUNTIME;
+ // Keep track of app op permissions.
+ if (bp.isAppOp()) {
+ mSettings.addAppOpPackage(perm, pkg.getPackageName());
}
- } else if (bp.isSignature()) {
- // For all apps signature permissions are install time ones.
- allowedSig = shouldGrantSignaturePermission(perm, pkg, ps, bp, origState);
- if (allowedSig) {
- grant = GRANT_INSTALL;
- }
- }
- if (grant != GRANT_DENIED) {
- if (!ps.isSystem() && userState.areInstallPermissionsFixed(ps.name)
- && !bp.isRuntime()) {
+ boolean shouldGrantNormalPermission = true;
+ if (bp.isNormal() && !origState.isPermissionGranted(perm)) {
// If this is an existing, non-system package, then
// we can't add any new permissions to it. Runtime
- // permissions can be added any time - they ad dynamic.
- if (!allowedSig && !origState.hasInstallPermission(perm)) {
+ // permissions can be added any time - they are dynamic.
+ if (!ps.isSystem() && userState.areInstallPermissionsFixed(ps.name)) {
// Except... if this is a permission that was added
// to the platform (note: need to only do this when
// updating the platform).
if (!isNewPlatformPermissionForPackage(perm, pkg)) {
- grant = GRANT_DENIED;
+ shouldGrantNormalPermission = false;
}
}
}
- }
-
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Considering granting permission " + perm + " to package "
- + pkg.getPackageName());
- }
- synchronized (mLock) {
- if (grant != GRANT_DENIED) {
- switch (grant) {
- case GRANT_INSTALL: {
- // Revoke this as runtime permission to handle the case of
- // a runtime permission being downgraded to an install one.
- // Also in permission review mode we keep dangerous permissions
- // for legacy apps
- final PermissionState origPermissionState =
- origState.getPermissionState(perm);
- if (origPermissionState != null
- && origPermissionState.isRuntime()) {
- // Revoke the runtime permission and clear the flags.
- origState.revokePermission(bp);
- origState.updatePermissionFlags(bp,
- PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
- // If we revoked a permission permission, we have to write.
- updatedUserIds = ArrayUtils.appendInt(
- updatedUserIds, userId);
- }
- // Grant an install permission.
- if (uidState.grantPermission(bp) != PERMISSION_OPERATION_FAILURE) {
- changedInstallPermission = true;
- }
- } break;
-
- case GRANT_RUNTIME: {
- boolean hardRestricted = bp.isHardRestricted();
- boolean softRestricted = bp.isSoftRestricted();
-
- // If permission policy is not ready we don't deal with restricted
- // permissions as the policy may whitelist some permissions. Once
- // the policy is initialized we would re-evaluate permissions.
- final boolean permissionPolicyInitialized =
- mPermissionPolicyInternal != null
- && mPermissionPolicyInternal.isInitialized(userId);
-
- PermissionState origPermState = origState.getPermissionState(perm);
- int flags = origPermState != null ? origPermState.getFlags() : 0;
-
- boolean wasChanged = false;
-
- boolean restrictionExempt =
- (origState.getPermissionFlags(bp.name)
- & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- boolean restrictionApplied = (origState.getPermissionFlags(
- bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-
- if (appSupportsRuntimePermissions) {
- // If hard restricted we don't allow holding it
- if (permissionPolicyInitialized && hardRestricted) {
- if (!restrictionExempt) {
- if (origPermState != null && origPermState.isGranted()
- && uidState.revokePermission(
- bp) != PERMISSION_OPERATION_FAILURE) {
- wasChanged = true;
- }
- if (!restrictionApplied) {
- flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
- wasChanged = true;
- }
- }
- // If soft restricted we allow holding in a restricted form
- } else if (permissionPolicyInitialized && softRestricted) {
- // Regardless if granted set the restriction flag as it
- // may affect app treatment based on this permission.
- if (!restrictionExempt && !restrictionApplied) {
- flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
- wasChanged = true;
- }
- }
-
- // Remove review flag as it is not necessary anymore
- if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
- wasChanged = true;
- }
-
- if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
- flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
- wasChanged = true;
- // Hard restricted permissions cannot be held.
- } else if (!permissionPolicyInitialized
- || (!hardRestricted || restrictionExempt)) {
- if (origPermState != null && origPermState.isGranted()) {
- if (uidState.grantPermission(bp)
- == PERMISSION_OPERATION_FAILURE) {
- wasChanged = true;
- }
- }
- }
- } else {
- if (origPermState == null) {
- // New permission
- if (PLATFORM_PACKAGE_NAME.equals(
- bp.getSourcePackageName())) {
- if (!bp.isRemoved()) {
- flags |= FLAG_PERMISSION_REVIEW_REQUIRED
- | FLAG_PERMISSION_REVOKED_COMPAT;
- wasChanged = true;
- }
- }
- }
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, "Considering granting permission " + perm + " to package "
+ + pkg.getPackageName());
+ }
- if (!uidState.hasPermission(bp.name)
- && uidState.grantPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
+ if ((bp.isNormal() && shouldGrantNormalPermission) || (bp.isSignature()
+ && (shouldGrantSignaturePermission.contains(permName)
+ || (bp.isDevelopment() && origState.isPermissionGranted(permName))))) {
+ // Grant an install permission.
+ if (uidState.grantPermission(bp)) {
+ changedInstallPermission = true;
+ }
+ } else if (bp.isRuntime()) {
+ boolean hardRestricted = bp.isHardRestricted();
+ boolean softRestricted = bp.isSoftRestricted();
+
+ // If permission policy is not ready we don't deal with restricted
+ // permissions as the policy may whitelist some permissions. Once
+ // the policy is initialized we would re-evaluate permissions.
+ final boolean permissionPolicyInitialized =
+ isPermissionPolicyInitialized.get(userId);
+
+ PermissionState origPermState = origState.getPermissionState(perm);
+ int flags = origPermState != null ? origPermState.getFlags() : 0;
+
+ boolean wasChanged = false;
+
+ boolean restrictionExempt =
+ (origState.getPermissionFlags(bp.name)
+ & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+ boolean restrictionApplied = (origState.getPermissionFlags(
+ bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+
+ if (appSupportsRuntimePermissions) {
+ // If hard restricted we don't allow holding it
+ if (permissionPolicyInitialized && hardRestricted) {
+ if (!restrictionExempt) {
+ if (origPermState != null && origPermState.isGranted()
+ && uidState.revokePermission(bp)) {
wasChanged = true;
}
-
- // If legacy app always grant the permission but if restricted
- // and not exempt take a note a restriction should be applied.
- if (permissionPolicyInitialized
- && (hardRestricted || softRestricted)
- && !restrictionExempt && !restrictionApplied) {
+ if (!restrictionApplied) {
flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
wasChanged = true;
}
}
-
- // If unrestricted or restriction exempt, don't apply restriction.
- if (permissionPolicyInitialized) {
- if (!(hardRestricted || softRestricted) || restrictionExempt) {
- if (restrictionApplied) {
- flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION;
- // Dropping restriction on a legacy app implies a review
- if (!appSupportsRuntimePermissions) {
- flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
- }
- wasChanged = true;
- }
- }
- }
-
- if (wasChanged) {
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ // If soft restricted we allow holding in a restricted form
+ } else if (permissionPolicyInitialized && softRestricted) {
+ // Regardless if granted set the restriction flag as it
+ // may affect app treatment based on this permission.
+ if (!restrictionExempt && !restrictionApplied) {
+ flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+ wasChanged = true;
}
+ }
- uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
- flags);
- } break;
-
- case GRANT_UPGRADE: {
- // Upgrade from Pre-Q to Q permission model. Make all permissions
- // runtime
- PermissionState origPermState = origState.getPermissionState(perm);
- int flags = (origPermState != null) ? origPermState.getFlags() : 0;
-
- BasePermission bpToRevoke =
- upgradedActivityRecognitionPermission == null
- ? bp : mSettings.getPermissionLocked(
- upgradedActivityRecognitionPermission);
- // Remove install permission
- if (origState.revokePermission(bpToRevoke)
- != PERMISSION_OPERATION_FAILURE) {
- origState.updatePermissionFlags(bpToRevoke,
- (MASK_PERMISSION_FLAGS_ALL
- & ~FLAG_PERMISSION_APPLY_RESTRICTION), 0);
- changedInstallPermission = true;
- }
-
- boolean hardRestricted = bp.isHardRestricted();
- boolean softRestricted = bp.isSoftRestricted();
-
- // If permission policy is not ready we don't deal with restricted
- // permissions as the policy may whitelist some permissions. Once
- // the policy is initialized we would re-evaluate permissions.
- final boolean permissionPolicyInitialized =
- mPermissionPolicyInternal != null
- && mPermissionPolicyInternal.isInitialized(userId);
-
- boolean wasChanged = false;
-
- boolean restrictionExempt =
- (origState.getPermissionFlags(bp.name)
- & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- boolean restrictionApplied = (origState.getPermissionFlags(
- bp.name) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-
- if (appSupportsRuntimePermissions) {
- // If hard restricted we don't allow holding it
- if (permissionPolicyInitialized && hardRestricted) {
- if (!restrictionExempt) {
- if (origPermState != null && origPermState.isGranted()
- && uidState.revokePermission(
- bp) != PERMISSION_OPERATION_FAILURE) {
- wasChanged = true;
- }
- if (!restrictionApplied) {
- flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
- wasChanged = true;
- }
- }
- // If soft restricted we allow holding in a restricted form
- } else if (permissionPolicyInitialized && softRestricted) {
- // Regardless if granted set the restriction flag as it
- // may affect app treatment based on this permission.
- if (!restrictionExempt && !restrictionApplied) {
- flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
- wasChanged = true;
- }
- }
-
- // Remove review flag as it is not necessary anymore
- if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
- wasChanged = true;
- }
-
- if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
- flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
- wasChanged = true;
- // Hard restricted permissions cannot be held.
- } else if (!permissionPolicyInitialized ||
- (!hardRestricted || restrictionExempt)) {
- if (uidState.grantPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
- wasChanged = true;
- }
- }
- } else {
- if (!uidState.hasPermission(bp.name)
- && uidState.grantPermission(bp)
- != PERMISSION_OPERATION_FAILURE) {
- flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
- wasChanged = true;
- }
+ // Remove review flag as it is not necessary anymore
+ if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
+ wasChanged = true;
+ }
- // If legacy app always grant the permission but if restricted
- // and not exempt take a note a restriction should be applied.
- if (permissionPolicyInitialized
- && (hardRestricted || softRestricted)
- && !restrictionExempt && !restrictionApplied) {
- flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+ if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0) {
+ flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
+ wasChanged = true;
+ // Hard restricted permissions cannot be held.
+ } else if (!permissionPolicyInitialized
+ || (!hardRestricted || restrictionExempt)) {
+ if ((origPermState != null && origPermState.isGranted())
+ || legacyActivityRecognitionPermission != null) {
+ if (!uidState.grantPermission(bp)) {
wasChanged = true;
}
}
-
- // If unrestricted or restriction exempt, don't apply restriction.
- if (permissionPolicyInitialized) {
- if (!(hardRestricted || softRestricted) || restrictionExempt) {
- if (restrictionApplied) {
- flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION;
- // Dropping restriction on a legacy app implies a review
- if (!appSupportsRuntimePermissions) {
- flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
- }
- wasChanged = true;
- }
+ }
+ } else {
+ if (origPermState == null) {
+ // New permission
+ if (PLATFORM_PACKAGE_NAME.equals(
+ bp.getSourcePackageName())) {
+ if (!bp.isRemoved()) {
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED
+ | FLAG_PERMISSION_REVOKED_COMPAT;
+ wasChanged = true;
}
}
+ }
- if (wasChanged) {
- updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- }
+ if (!uidState.isPermissionGranted(bp.name)
+ && uidState.grantPermission(bp)) {
+ wasChanged = true;
+ }
- uidState.updatePermissionFlags(bp,
- MASK_PERMISSION_FLAGS_ALL, flags);
- } break;
+ // If legacy app always grant the permission but if restricted
+ // and not exempt take a note a restriction should be applied.
+ if (permissionPolicyInitialized
+ && (hardRestricted || softRestricted)
+ && !restrictionExempt && !restrictionApplied) {
+ flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+ wasChanged = true;
+ }
+ }
- default: {
- if (packageOfInterest == null
- || packageOfInterest.equals(pkg.getPackageName())) {
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Not granting permission " + perm
- + " to package " + friendlyName
- + " because it was previously installed without");
+ // If unrestricted or restriction exempt, don't apply restriction.
+ if (permissionPolicyInitialized) {
+ if (!(hardRestricted || softRestricted) || restrictionExempt) {
+ if (restrictionApplied) {
+ flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION;
+ // Dropping restriction on a legacy app implies a review
+ if (!appSupportsRuntimePermissions) {
+ flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
}
+ wasChanged = true;
}
- } break;
+ }
}
+
+ if (wasChanged) {
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ }
+
+ uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
+ flags);
} else {
- if (uidState.revokePermission(bp) != PERMISSION_OPERATION_FAILURE) {
- // Also drop the permission flags.
- uidState.updatePermissionFlags(bp,
- MASK_PERMISSION_FLAGS_ALL, 0);
- changedInstallPermission = true;
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, "Un-granting permission " + perm
+ if (DEBUG_PERMISSIONS) {
+ boolean wasGranted = uidState.isPermissionGranted(bp.name);
+ if (wasGranted || bp.isAppOp()) {
+ Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
+ + " permission " + perm
+ " from package " + friendlyName
+ " (protectionLevel=" + bp.getProtectionLevel()
+ " flags=0x"
@@ -3092,35 +2911,22 @@ public class PermissionManagerService extends IPermissionManager.Stub {
ps))
+ ")");
}
- } else if (bp.isAppOp()) {
- // Don't print warning for app op permissions, since it is fine for them
- // not to be granted, there is a UI for the user to decide.
- if (DEBUG_PERMISSIONS
- && (packageOfInterest == null
- || packageOfInterest.equals(pkg.getPackageName()))) {
- Slog.i(TAG, "Not granting permission " + perm
- + " to package " + friendlyName
- + " (protectionLevel=" + bp.getProtectionLevel()
- + " flags=0x"
- + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
- ps))
- + ")");
- }
+ }
+ if (uidState.removePermissionState(bp.name)) {
+ changedInstallPermission = true;
}
}
}
- }
- if ((changedInstallPermission || replace)
- && !userState.areInstallPermissionsFixed(ps.name)
- && !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) {
- // This is the first that we have heard about this package, so the
- // permissions we have now selected are fixed until explicitly
- // changed.
- userState.setInstallPermissionsFixed(ps.name, true);
- }
+ if ((changedInstallPermission || replace)
+ && !userState.areInstallPermissionsFixed(ps.name)
+ && !ps.isSystem() || ps.getPkgState().isUpdatedSystemApp()) {
+ // This is the first that we have heard about this package, so the
+ // permissions we have now selected are fixed until explicitly
+ // changed.
+ userState.setInstallPermissionsFixed(ps.name, true);
+ }
- synchronized (mLock) {
updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg,
userId, updatedUserIds);
updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState,
@@ -3172,20 +2978,19 @@ public class PermissionManagerService extends IPermissionManager.Stub {
boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
>= Build.VERSION_CODES.M;
- for (String permission : ps.getPermissions()) {
+ for (String permission : ps.getGrantedPermissions()) {
if (!pkg.getImplicitPermissions().contains(permission)) {
- if (!ps.hasInstallPermission(permission)) {
+ BasePermission bp = mSettings.getPermissionLocked(permission);
+ if (bp.isRuntime()) {
int flags = ps.getPermissionFlags(permission);
if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
- BasePermission bp = mSettings.getPermissionLocked(permission);
int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
&& supportsRuntimePermissions) {
- int revokeResult = ps.revokePermission(bp);
- if (revokeResult != PERMISSION_OPERATION_FAILURE) {
+ if (ps.revokePermission(bp)) {
if (DEBUG_PERMISSIONS) {
Slog.i(TAG, "Revoking runtime permission "
+ permission + " for " + pkgName
@@ -3229,7 +3034,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
int numSourcePerm = sourcePerms.size();
for (int i = 0; i < numSourcePerm; i++) {
String sourcePerm = sourcePerms.valueAt(i);
- if (ps.hasPermission(sourcePerm)) {
+ if (ps.isPermissionGranted(sourcePerm)) {
if (!isGranted) {
flags = 0;
}
@@ -3324,8 +3129,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
ArraySet<String> sourcePerms = newToSplitPerms.get(newPerm);
if (sourcePerms != null) {
- if (!ps.hasInstallPermission(newPerm)) {
- BasePermission bp = mSettings.getPermissionLocked(newPerm);
+ BasePermission bp = mSettings.getPermissionLocked(newPerm);
+ if (bp.isRuntime()) {
if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
ps.updatePermissionFlags(bp,
@@ -3334,27 +3139,31 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
- boolean inheritsFromInstallPerm = false;
- for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
- sourcePermNum++) {
- if (ps.hasInstallPermission(sourcePerms.valueAt(sourcePermNum))) {
- inheritsFromInstallPerm = true;
- break;
+ if (!origPs.hasPermissionState(sourcePerms)) {
+ boolean inheritsFromInstallPerm = false;
+ for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
+ sourcePermNum++) {
+ final String sourcePerm = sourcePerms.valueAt(sourcePermNum);
+ BasePermission sourceBp = mSettings.getPermissionLocked(sourcePerm);
+ if (!sourceBp.isRuntime()) {
+ inheritsFromInstallPerm = true;
+ break;
+ }
}
- }
- if (!origPs.hasRequestedPermission(sourcePerms)
- && !inheritsFromInstallPerm) {
- // Both permissions are new so nothing to inherit.
- if (DEBUG_PERMISSIONS) {
- Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
- + " for " + pkgName + " as split permission is also new");
+ if (!inheritsFromInstallPerm) {
+ // Both permissions are new so nothing to inherit.
+ if (DEBUG_PERMISSIONS) {
+ Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
+ + " for " + pkgName + " as split permission is also new");
+ }
+ continue;
}
- } else {
- // Inherit from new install or existing runtime permissions
- inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
- newPerm, ps, pkg);
}
+
+ // Inherit from new install or existing runtime permissions
+ inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps,
+ pkg);
}
}
}
@@ -3392,7 +3201,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
+ " to register permissions as one time.");
Objects.requireNonNull(packageName);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
timeoutMillis, importanceToResetTimer, importanceToKeepSessionAlive);
@@ -3408,7 +3217,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
+ " to remove permissions as one time.");
Objects.requireNonNull(packageName);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
getOneTimePermissionUserManager(userId).stopPackageOneTimeSession(packageName);
} finally {
@@ -3440,7 +3249,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return result;
}
- private boolean isNewPlatformPermissionForPackage(String perm, AndroidPackage pkg) {
+ private static boolean isNewPlatformPermissionForPackage(String perm, AndroidPackage pkg) {
boolean allowed = false;
final int NP = PackageParser.NEW_PERMISSIONS.length;
for (int ip=0; ip<NP; ip++) {
@@ -3457,102 +3266,13 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return allowed;
}
- /**
- * Determines whether a package is whitelisted for a particular privapp permission.
- *
- * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
- *
- * <p>This handles parent/child apps.
- */
- private boolean hasPrivappWhitelistEntry(String perm, AndroidPackage pkg) {
- ArraySet<String> wlPermissions;
- if (pkg.isVendor()) {
- wlPermissions =
- SystemConfig.getInstance().getVendorPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- wlPermissions =
- SystemConfig.getInstance().getProductPrivAppPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- wlPermissions =
- SystemConfig.getInstance().getSystemExtPrivAppPermissions(
- pkg.getPackageName());
- } else {
- wlPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg.getPackageName());
- }
-
- return wlPermissions != null && wlPermissions.contains(perm);
- }
-
- private boolean shouldGrantSignaturePermission(String perm, AndroidPackage pkg,
- PackageSetting pkgSetting, BasePermission bp, UidPermissionState origPermissions) {
- boolean oemPermission = bp.isOEM();
- boolean vendorPrivilegedPermission = bp.isVendorPrivileged();
- boolean privilegedPermission = bp.isPrivileged() || bp.isVendorPrivileged();
- boolean privappPermissionsDisable =
- RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
- boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
- boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.getPackageName());
- if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
- && !platformPackage && platformPermission) {
- if (!hasPrivappWhitelistEntry(perm, pkg)) {
- // Only enforce whitelist this on boot
- if (!mSystemReady
- // Updated system apps do not need to be whitelisted
- && !pkgSetting.getPkgState().isUpdatedSystemApp()) {
- ApexManager apexMgr = ApexManager.getInstance();
- String apexContainingPkg = apexMgr.getActiveApexPackageNameContainingPackage(
- pkg);
-
- // Apps that are in updated apexs' do not need to be whitelisted
- if (apexContainingPkg == null || apexMgr.isFactory(
- apexMgr.getPackageInfo(apexContainingPkg, MATCH_ACTIVE_PACKAGE))) {
- // it's only a reportable violation if the permission isn't explicitly
- // denied
- ArraySet<String> deniedPermissions = null;
- if (pkg.isVendor()) {
- deniedPermissions = SystemConfig.getInstance()
- .getVendorPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isProduct()) {
- deniedPermissions = SystemConfig.getInstance()
- .getProductPrivAppDenyPermissions(pkg.getPackageName());
- } else if (pkg.isSystemExt()) {
- deniedPermissions = SystemConfig.getInstance()
- .getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
- } else {
- deniedPermissions = SystemConfig.getInstance()
- .getPrivAppDenyPermissions(pkg.getPackageName());
- }
- final boolean permissionViolation =
- deniedPermissions == null || !deniedPermissions.contains(perm);
- if (permissionViolation) {
- Slog.w(TAG, "Privileged permission " + perm + " for package "
- + pkg.getPackageName() + " (" + pkg.getPath()
- + ") not in privapp-permissions whitelist");
-
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- if (mPrivappPermissionsViolations == null) {
- mPrivappPermissionsViolations = new ArraySet<>();
- }
- mPrivappPermissionsViolations.add(
- pkg.getPackageName() + " (" + pkg.getPath() + "): "
- + perm);
- }
- } else {
- return false;
- }
- }
- }
- if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
- return false;
- }
- }
- }
+ private boolean shouldGrantSignaturePermission(@NonNull AndroidPackage pkg,
+ @NonNull PackageSetting pkgSetting, @NonNull BasePermission bp) {
// expect single system package
String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
final AndroidPackage systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
-
// check if the package is allow to use this signature permission. A package is allowed to
// use a signature permission if:
// - it has the same set of signing certificates as the source package
@@ -3571,143 +3291,142 @@ public class PermissionManagerService extends IPermissionManager.Stub {
|| systemPackage.getSigningDetails().checkCapability(
pkg.getSigningDetails(),
PackageParser.SigningDetails.CertCapabilities.PERMISSION);
- if (!allowed && (privilegedPermission || oemPermission)) {
- if (pkg.isSystem()) {
- // For updated system applications, a privileged/oem permission
- // is granted only if it had been defined by the original application.
- if (pkgSetting.getPkgState().isUpdatedSystemApp()) {
- final PackageSetting disabledPs = mPackageManagerInt
- .getDisabledSystemPackage(pkg.getPackageName());
- final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
- if (disabledPkg != null && isPackageRequestingPermission(disabledPkg, perm)
- && ((privilegedPermission && disabledPkg.isPrivileged())
- || (oemPermission && canGrantOemPermission(disabledPkg,
- perm)))) {
- allowed = true;
- }
- } else {
- allowed = (privilegedPermission && pkg.isPrivileged())
- || (oemPermission && canGrantOemPermission(pkg, perm));
- }
- // In any case, don't grant a privileged permission to privileged vendor apps, if
- // the permission's protectionLevel does not have the extra 'vendorPrivileged'
- // flag.
- if (allowed && privilegedPermission &&
- !vendorPrivilegedPermission && pkg.isVendor()) {
- Slog.w(TAG, "Permission " + perm + " cannot be granted to privileged vendor apk "
- + pkg.getPackageName()
- + " because it isn't a 'vendorPrivileged' permission.");
- allowed = false;
+ final boolean isVendorPrivilegedPermission = bp.isVendorPrivileged();
+ final boolean isPrivilegedPermission = bp.isPrivileged() || isVendorPrivilegedPermission;
+ final boolean isOemPermission = bp.isOEM();
+ if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
+ final String permissionName = bp.name;
+ // For updated system applications, a privileged/oem permission
+ // is granted only if it had been defined by the original application.
+ if (pkgSetting.getPkgState().isUpdatedSystemApp()) {
+ final PackageSetting disabledPs = mPackageManagerInt
+ .getDisabledSystemPackage(pkg.getPackageName());
+ final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.pkg;
+ if (disabledPkg != null && disabledPkg.getRequestedPermissions().contains(
+ permissionName)) {
+ allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(disabledPkg,
+ true, bp)) || (isOemPermission && canGrantOemPermission(disabledPkg,
+ permissionName));
}
- }
- }
- if (!allowed) {
- if (!allowed
- && bp.isPre23()
- && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
- // If this was a previously normal/dangerous permission that got moved
- // to a system permission as part of the runtime permission redesign, then
- // we still want to blindly grant it to old apps.
- allowed = true;
- }
- // TODO (moltmann): The installer now shares the platforms signature. Hence it does not
- // need a separate flag anymore. Hence we need to check which
- // permissions are needed by the permission controller
- if (!allowed && bp.isInstaller()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
- pkg.getPackageName()) || ArrayUtils.contains(
- mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
- UserHandle.USER_SYSTEM), pkg.getPackageName())) {
- // If this permission is to be granted to the system installer and
- // this app is an installer, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isVerifier()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // If this permission is to be granted to the system verifier and
- // this app is a verifier, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isPreInstalled()
- && pkg.isSystem()) {
- // Any pre-installed system app is allowed to get this permission.
- allowed = true;
- }
- if (!allowed && bp.isDevelopment()) {
- // For development permissions, a development permission
- // is granted only if it was already granted.
- allowed = origPermissions.hasInstallPermission(perm);
- }
- if (!allowed && bp.isSetup()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // If this permission is to be granted to the system setup wizard and
- // this app is a setup wizard, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isSystemTextClassifier()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
- UserHandle.USER_SYSTEM), pkg.getPackageName())) {
- // Special permissions for the system default text classifier.
- allowed = true;
- }
- if (!allowed && bp.isConfigurator()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_CONFIGURATOR,
- UserHandle.USER_SYSTEM), pkg.getPackageName())) {
- // Special permissions for the device configurator.
- allowed = true;
- }
- if (!allowed && bp.isWellbeing()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // Special permission granted only to the OEM specified wellbeing app
- allowed = true;
- }
- if (!allowed && bp.isDocumenter()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // If this permission is to be granted to the documenter and
- // this app is the documenter, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isIncidentReportApprover()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
- UserHandle.USER_SYSTEM), pkg.getPackageName())) {
- // If this permission is to be granted to the incident report approver and
- // this app is the incident report approver, then it gets the permission.
- allowed = true;
- }
- if (!allowed && bp.isAppPredictor()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // Special permissions for the system app predictor.
- allowed = true;
- }
- if (!allowed && bp.isCompanion()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM),
- pkg.getPackageName())) {
- // Special permissions for the system companion device manager.
- allowed = true;
- }
- if (!allowed && bp.isRetailDemo()
- && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
- PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM),
- pkg.getPackageName()) && isProfileOwner(pkg.getUid())) {
- // Special permission granted only to the OEM specified retail demo app
- allowed = true;
- }
+ } else {
+ allowed = (isPrivilegedPermission && canGrantPrivilegedPermission(pkg, false, bp))
+ || (isOemPermission && canGrantOemPermission(pkg, permissionName));
+ }
+ // In any case, don't grant a privileged permission to privileged vendor apps, if
+ // the permission's protectionLevel does not have the extra 'vendorPrivileged'
+ // flag.
+ if (allowed && isPrivilegedPermission && !isVendorPrivilegedPermission
+ && pkg.isVendor()) {
+ Slog.w(TAG, "Permission " + permissionName
+ + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+ + " because it isn't a 'vendorPrivileged' permission.");
+ allowed = false;
+ }
+ }
+ if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
+ // If this was a previously normal/dangerous permission that got moved
+ // to a system permission as part of the runtime permission redesign, then
+ // we still want to blindly grant it to old apps.
+ allowed = true;
+ }
+ // TODO (moltmann): The installer now shares the platforms signature. Hence it does not
+ // need a separate flag anymore. Hence we need to check which
+ // permissions are needed by the permission controller
+ if (!allowed && bp.isInstaller()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
+ pkg.getPackageName()) || ArrayUtils.contains(
+ mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
+ UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+ // If this permission is to be granted to the system installer and
+ // this app is an installer, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isVerifier()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // If this permission is to be granted to the system verifier and
+ // this app is a verifier, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isPreInstalled()
+ && pkg.isSystem()) {
+ // Any pre-installed system app is allowed to get this permission.
+ allowed = true;
+ }
+ // Deferred to be checked under permission data lock inside restorePermissionState().
+ //if (!allowed && bp.isDevelopment()) {
+ // // For development permissions, a development permission
+ // // is granted only if it was already granted.
+ // allowed = origPermissions.isPermissionGranted(permissionName);
+ //}
+ if (!allowed && bp.isSetup()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // If this permission is to be granted to the system setup wizard and
+ // this app is a setup wizard, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isSystemTextClassifier()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+ UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+ // Special permissions for the system default text classifier.
+ allowed = true;
+ }
+ if (!allowed && bp.isConfigurator()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_CONFIGURATOR,
+ UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+ // Special permissions for the device configurator.
+ allowed = true;
+ }
+ if (!allowed && bp.isWellbeing()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // Special permission granted only to the OEM specified wellbeing app
+ allowed = true;
+ }
+ if (!allowed && bp.isDocumenter()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // If this permission is to be granted to the documenter and
+ // this app is the documenter, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isIncidentReportApprover()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
+ UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+ // If this permission is to be granted to the incident report approver and
+ // this app is the incident report approver, then it gets the permission.
+ allowed = true;
+ }
+ if (!allowed && bp.isAppPredictor()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // Special permissions for the system app predictor.
+ allowed = true;
+ }
+ if (!allowed && bp.isCompanion()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM),
+ pkg.getPackageName())) {
+ // Special permissions for the system companion device manager.
+ allowed = true;
+ }
+ if (!allowed && bp.isRetailDemo()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM),
+ pkg.getPackageName()) && isProfileOwner(pkg.getUid())) {
+ // Special permission granted only to the OEM specified retail demo app
+ allowed = true;
}
return allowed;
}
@@ -3728,14 +3447,86 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return mPackageManagerInt.getPackageSetting(sourcePackageName);
}
- private static boolean isProfileOwner(int uid) {
- DevicePolicyManagerInternal dpmInternal =
- LocalServices.getService(DevicePolicyManagerInternal.class);
- if (dpmInternal != null) {
- return dpmInternal
- .isActiveAdminWithPolicy(uid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ private boolean canGrantPrivilegedPermission(@NonNull AndroidPackage pkg,
+ boolean isUpdatedSystemApp, @NonNull BasePermission permission) {
+ if (!pkg.isPrivileged()) {
+ return false;
}
- return false;
+ final boolean isPlatformPermission = PLATFORM_PACKAGE_NAME.equals(
+ permission.getSourcePackageName());
+ if (!isPlatformPermission) {
+ return true;
+ }
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+ return true;
+ }
+ final String permissionName = permission.name;
+ if (isInSystemConfigPrivAppPermissions(pkg, permissionName)) {
+ return true;
+ }
+ // Only enforce whitelist this on boot
+ if (!mSystemReady
+ // Updated system apps do not need to be whitelisted
+ && !isUpdatedSystemApp) {
+ final ApexManager apexManager = ApexManager.getInstance();
+ final String packageName = pkg.getPackageName();
+ final String containingApexPackageName =
+ apexManager.getActiveApexPackageNameContainingPackage(packageName);
+ final boolean isInUpdatedApex = containingApexPackageName != null
+ && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+ MATCH_ACTIVE_PACKAGE));
+ // Apps that are in updated apexs' do not need to be whitelisted
+ if (!isInUpdatedApex) {
+ // it's only a reportable violation if the permission isn't explicitly
+ // denied
+ if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName)) {
+ return false;
+ }
+ Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+ + packageName + " (" + pkg.getPath()
+ + ") not in privapp-permissions whitelist");
+ if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+ if (mPrivappPermissionsViolations == null) {
+ mPrivappPermissionsViolations = new ArraySet<>();
+ }
+ mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+ + permissionName);
+ }
+ }
+ }
+ return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+ }
+
+ private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+ }
+ return permissions != null && permissions.contains(permission);
+ }
+
+ private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+ @NonNull String permission) {
+ final SystemConfig systemConfig = SystemConfig.getInstance();
+ final Set<String> permissions;
+ if (pkg.isVendor()) {
+ permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isProduct()) {
+ permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+ } else if (pkg.isSystemExt()) {
+ permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+ } else {
+ permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+ }
+ return permissions != null && permissions.contains(permission);
}
private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
@@ -3752,6 +3543,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return Boolean.TRUE == granted;
}
+ private static boolean isProfileOwner(int uid) {
+ DevicePolicyManagerInternal dpmInternal =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
+ if (dpmInternal != null) {
+ return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid);
+ }
+ return false;
+ }
+
private boolean isPermissionsReviewRequired(@NonNull AndroidPackage pkg,
@UserIdInt int userId) {
// Permission review applies only to apps not supporting the new permission model.
@@ -3760,24 +3561,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
// Legacy apps have the permission and get user consent on launch.
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return false;
- }
- return uidState.isPermissionReviewRequired();
- }
-
- private boolean isPackageRequestingPermission(AndroidPackage pkg, String permission) {
- final int permCount = pkg.getRequestedPermissions().size();
- for (int j = 0; j < permCount; j++) {
- String requestedPermission = pkg.getRequestedPermissions().get(j);
- if (permission.equals(requestedPermission)) {
- return true;
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+ + userId);
+ return false;
}
+ return uidState.isPermissionReviewRequired();
}
- return false;
}
private void grantRequestedRuntimePermissions(AndroidPackage pkg, int[] userIds,
@@ -3790,13 +3582,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private void grantRequestedRuntimePermissionsForUser(AndroidPackage pkg, int userId,
String[] grantedPermissions, int callingUid, PermissionCallback callback) {
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- return;
- }
-
final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
| PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -3818,7 +3603,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
&& (supportsRuntimePermissions || !bp.isRuntimeOnly())
&& (grantedPermissions == null
|| ArrayUtils.contains(grantedPermissions, permission))) {
- final int flags = uidState.getPermissionFlags(permission);
+ final int flags = getPermissionFlagsInternal(permission, pkg.getPackageName(),
+ callingUid, userId);
if (supportsRuntimePermissions) {
// Installer cannot change immutable permissions.
if ((flags & immutableFlags) == 0) {
@@ -3846,12 +3632,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
for (int i = 0; i < userIds.length; i++) {
int userId = userIds[i];
- final UidPermissionState uidState = getUidState(pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
- + userId);
- continue;
- }
for (int j = 0; j < permissionCount; j++) {
final String permissionName = pkg.getRequestedPermissions().get(j);
@@ -3862,14 +3642,26 @@ public class PermissionManagerService extends IPermissionManager.Stub {
continue;
}
- if (uidState.hasPermission(permissionName)) {
+ final boolean isGranted;
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+ + " and user " + userId);
+ continue;
+ }
+ isGranted = uidState.isPermissionGranted(permissionName);
+ }
+
+ if (isGranted) {
if (oldGrantedRestrictedPermissions.get(userId) == null) {
oldGrantedRestrictedPermissions.put(userId, new ArraySet<>());
}
oldGrantedRestrictedPermissions.get(userId).add(permissionName);
}
- final int oldFlags = uidState.getPermissionFlags(permissionName);
+ final int oldFlags = getPermissionFlagsInternal(permissionName,
+ pkg.getPackageName(), callingUid, userId);
int newFlags = oldFlags;
int mask = 0;
@@ -3905,6 +3697,15 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
break;
+ case FLAG_PERMISSION_ALLOWLIST_ROLE: {
+ mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ if (permissions != null && permissions.contains(permissionName)) {
+ newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ } else {
+ newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT;
+ }
+ }
+ break;
}
}
@@ -3924,7 +3725,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// as whitelisting trumps policy i.e. policy cannot grant a non
// grantable permission.
if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
- final boolean isGranted = uidState.hasPermission(permissionName);
if (!isWhitelisted && isGranted) {
mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -3958,15 +3758,19 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final int oldGrantedCount = oldPermsForUser.size();
for (int j = 0; j < oldGrantedCount; j++) {
- final String permission = oldPermsForUser.valueAt(j);
+ final String permissionName = oldPermsForUser.valueAt(j);
// Sometimes we create a new permission state instance during update.
- final UidPermissionState newUidState = getUidState(pkg, userId);
- if (newUidState == null) {
- Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
- + " and user " + userId);
- continue;
+ final boolean isGranted;
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+ + " and user " + userId);
+ continue;
+ }
+ isGranted = uidState.isPermissionGranted(permissionName);
}
- if (!newUidState.hasPermission(permission)) {
+ if (!isGranted) {
callback.onPermissionRevoked(pkg.getUid(), userId, null);
break;
}
@@ -4015,13 +3819,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
continue;
}
- UidPermissionState uidState = getUidState(deletedPs.pkg, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
- + " and user " + userId);
- continue;
- }
-
PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
deletedPs.pkg.getPackageName());
@@ -4040,15 +3837,19 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
- // The package is gone - no need to keep flags for applying policy.
- uidState.updatePermissionFlags(bp, PackageManager.MASK_PERMISSION_FLAGS_ALL, 0);
+ synchronized (mLock) {
+ UidPermissionState uidState = getUidStateLocked(deletedPs.pkg, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for " + deletedPs.pkg.getPackageName()
+ + " and user " + userId);
+ continue;
+ }
- // Try to revoke as a runtime permission which is per user.
- // TODO(zhanghai): This doesn't make sense. revokePermission() doesn't fail, and why are
- // we only killing the uid when gids changed, instead of any permission change?
- if (uidState.revokePermission(bp)
- == UidPermissionState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
- affectedUserId = userId;
+ // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
+ // permission change?
+ if (uidState.removePermissionState(bp.name) && bp.hasGids()) {
+ affectedUserId = userId;
+ }
}
}
@@ -4080,17 +3881,14 @@ public class PermissionManagerService extends IPermissionManager.Stub {
boolean runtimePermissionChanged = false;
// Prune permissions
- final List<com.android.server.pm.permission.PermissionState> permissionStates =
- uidState.getPermissionStates();
+ final List<PermissionState> permissionStates = uidState.getPermissionStates();
final int permissionStatesSize = permissionStates.size();
for (int i = permissionStatesSize - 1; i >= 0; i--) {
PermissionState permissionState = permissionStates.get(i);
if (!usedPermissions.contains(permissionState.getName())) {
BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
if (bp != null) {
- uidState.revokePermission(bp);
- uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL, 0);
- if (permissionState.isRuntime()) {
+ if (uidState.removePermissionState(bp.name) && permissionState.isRuntime()) {
runtimePermissionChanged = true;
}
}
@@ -4346,17 +4144,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
} else {
mPackageManagerInt.forEachPackage(p -> {
final int[] userIds = mUserManagerInt.getUserIds();
- for (final int userId : userIds) {
- final UidPermissionState uidState = getUidState(p, userId);
- if (uidState == null) {
- Slog.e(TAG, "Missing permissions state for "
- + p.getPackageName() + " and user " + userId);
- return;
- }
- if (uidState.getPermissionState(bp.getName()) != null) {
- uidState.revokePermission(bp);
- uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
- 0);
+ synchronized (mLock) {
+ for (final int userId : userIds) {
+ final UidPermissionState uidState = getUidStateLocked(p,
+ userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for "
+ + p.getPackageName() + " and user " + userId);
+ continue;
+ }
+ uidState.removePermissionState(bp.name);
}
}
});
@@ -4529,7 +4326,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final int callingUserId = UserHandle.getUserId(callingUid);
if (hasCrossUserPermission(
- Binder.getCallingPid(), callingUid, callingUserId, userId, requireFullPermission,
+ callingUid, callingUserId, userId, requireFullPermission,
requirePermissionWhenSameUser)) {
return;
}
@@ -4556,79 +4353,53 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
boolean requireFullPermission, boolean checkShell,
String message) {
- int callingPid = Binder.getCallingPid();
- final int callingUserId = UserHandle.getUserId(callingUid);
-
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
-
- if (callingUserId == userId) {
+ if (checkShell) {
+ PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+ }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+ /*requirePermissionWhenSameUser= */ false)) {
return;
}
-
- // Prevent endless loop between when checking permission while checking a permission
- if (callingPid == ActivityManagerService.MY_PID) {
+ final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+ if (isSameProfileGroup && PermissionChecker.checkPermissionForPreflight(
+ mContext,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ PermissionChecker.PID_UNKNOWN,
+ callingUid,
+ mPackageManagerInt.getPackage(callingUid).getPackageName())
+ == PermissionChecker.PERMISSION_GRANTED) {
return;
}
-
- long token = Binder.clearCallingIdentity();
- try {
- if (checkShell) {
- PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
- UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
- }
- if (hasCrossUserPermission(callingPid, callingUid, callingUserId, userId,
- requireFullPermission, /*requirePermissionWhenSameUser= */ false)) {
- return;
- }
- final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
-
- if (isSameProfileGroup) {
- AndroidPackage callingPkg = mPackageManagerInt.getPackage(callingUid);
- String callingPkgName = null;
- if (callingPkg != null) {
- callingPkgName = callingPkg.getPackageName();
- }
-
- if (PermissionChecker.checkPermissionForPreflight(
- mContext,
- android.Manifest.permission.INTERACT_ACROSS_PROFILES,
- PermissionChecker.PID_UNKNOWN,
- callingUid,
- callingPkgName)
- == PermissionChecker.PERMISSION_GRANTED) {
- return;
- }
- }
-
String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
callingUid, userId, message, requireFullPermission, isSameProfileGroup);
Slog.w(TAG, errorMessage);
throw new SecurityException(errorMessage);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
- private boolean hasCrossUserPermission(int callingPid, int callingUid, int callingUserId,
- int userId, boolean requireFullPermission, boolean requirePermissionWhenSameUser) {
+ private boolean hasCrossUserPermission(
+ int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+ boolean requirePermissionWhenSameUser) {
if (!requirePermissionWhenSameUser && userId == callingUserId) {
return true;
}
if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
return true;
}
-
- if (!requireFullPermission) {
- if (mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
- callingPid, callingUid) == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
+ if (requireFullPermission) {
+ return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
+ return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+ }
- return mContext.checkPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+ private boolean hasPermission(String permission) {
+ return mContext.checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED;
}
private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
@@ -4791,29 +4562,27 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
@Nullable
- private UidPermissionState getUidState(@NonNull PackageSetting ps,
+ private UidPermissionState getUidStateLocked(@NonNull PackageSetting ps,
@UserIdInt int userId) {
- return getUidState(ps.getAppId(), userId);
+ return getUidStateLocked(ps.getAppId(), userId);
}
@Nullable
- private UidPermissionState getUidState(@NonNull AndroidPackage pkg,
+ private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg,
@UserIdInt int userId) {
- return getUidState(pkg.getUid(), userId);
+ return getUidStateLocked(pkg.getUid(), userId);
}
@Nullable
- private UidPermissionState getUidState(int appId, @UserIdInt int userId) {
- synchronized (mLock) {
- final UserPermissionState userState = mState.getUserState(userId);
- if (userState == null) {
- return null;
- }
- return userState.getUidState(appId);
+ private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) {
+ final UserPermissionState userState = mState.getUserState(userId);
+ if (userState == null) {
+ return null;
}
+ return userState.getUidState(appId);
}
- private void removeAppState(int appId) {
+ private void removeAppIdState(@AppIdInt int appId) {
synchronized (mLock) {
final int[] userIds = mState.getUserIds();
for (final int userId : userIds) {
@@ -4827,7 +4596,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final int[] userIds = getAllUserIds();
mPackageManagerInt.forEachPackageSetting(ps -> {
final int appId = ps.getAppId();
- final PermissionsState permissionsState = ps.getPermissionsState();
+ final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
synchronized (mLock) {
for (final int userId : userIds) {
@@ -4836,34 +4605,33 @@ public class PermissionManagerService extends IPermissionManager.Stub {
userState.setInstallPermissionsFixed(ps.name, ps.areInstallPermissionsFixed());
final UidPermissionState uidState = userState.getOrCreateUidState(appId);
uidState.reset();
- uidState.setGlobalGids(permissionsState.getGlobalGids());
- uidState.setMissing(permissionsState.isMissing(userId));
+ uidState.setMissing(legacyState.isMissing(userId));
readStateFromPermissionStates(uidState,
- permissionsState.getInstallPermissionStates(), false);
+ legacyState.getInstallPermissionStates());
readStateFromPermissionStates(uidState,
- permissionsState.getRuntimePermissionStates(userId), true);
+ legacyState.getRuntimePermissionStates(userId));
}
}
});
}
private void readStateFromPermissionStates(@NonNull UidPermissionState uidState,
- @NonNull List<PermissionsState.PermissionState> permissionStates, boolean isRuntime) {
- final int permissionStatesSize = permissionStates.size();
- for (int i = 0; i < permissionStatesSize; i++) {
- final PermissionsState.PermissionState permissionState = permissionStates.get(i);
- final BasePermission permission = permissionState.getPermission();
- uidState.putPermissionState(permission, isRuntime, permissionState.isGranted(),
- permissionState.getFlags());
+ @NonNull Collection<LegacyPermissionState.PermissionState> permissionStates) {
+ for (final LegacyPermissionState.PermissionState permissionState : permissionStates) {
+ uidState.putPermissionState(permissionState.getPermission(),
+ permissionState.isGranted(), permissionState.getFlags());
}
}
private void writeStateToPackageSettings() {
- final int[] userIds = mState.getUserIds();
+ final int[] userIds;
+ synchronized (mLock) {
+ userIds = mState.getUserIds();
+ }
mPackageManagerInt.forEachPackageSetting(ps -> {
ps.setInstallPermissionsFixed(false);
- final PermissionsState permissionsState = ps.getPermissionsState();
- permissionsState.reset();
+ final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
+ legacyState.reset();
final int appId = ps.getAppId();
synchronized (mLock) {
@@ -4885,27 +4653,21 @@ public class PermissionManagerService extends IPermissionManager.Stub {
continue;
}
- permissionsState.setGlobalGids(uidState.getGlobalGids());
- permissionsState.setMissing(uidState.isMissing(), userId);
+ legacyState.setMissing(uidState.isMissing(), userId);
final List<PermissionState> permissionStates = uidState.getPermissionStates();
final int permissionStatesSize = permissionStates.size();
for (int i = 0; i < permissionStatesSize; i++) {
final PermissionState permissionState = permissionStates.get(i);
- final BasePermission permission = permissionState.getPermission();
- if (permissionState.isGranted()) {
- if (permissionState.isRuntime()) {
- permissionsState.grantRuntimePermission(permission, userId);
- } else {
- permissionsState.grantInstallPermission(permission);
- }
- }
- final int flags = permissionState.getFlags();
- if (flags != 0) {
- final int flagsUserId = permissionState.isRuntime() ? userId
- : UserHandle.USER_ALL;
- permissionsState.updatePermissionFlags(permission, flagsUserId, flags,
- flags);
+ final LegacyPermissionState.PermissionState legacyPermissionState =
+ new LegacyPermissionState.PermissionState(
+ permissionState.getPermission(),
+ permissionState.isGranted(), permissionState.getFlags());
+ if (permissionState.isRuntime()) {
+ legacyState.putRuntimePermissionState(legacyPermissionState,
+ userId);
+ } else {
+ legacyState.putInstallPermissionState(legacyPermissionState);
}
}
}
@@ -4913,6 +4675,54 @@ public class PermissionManagerService extends IPermissionManager.Stub {
});
}
+ @NonNull
+ private LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
+ final LegacyPermissionState legacyState = new LegacyPermissionState();
+ synchronized (mLock) {
+ final int[] userIds = mState.getUserIds();
+ for (final int userId : userIds) {
+ final UidPermissionState uidState = getUidStateLocked(appId, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
+ + userId);
+ continue;
+ }
+
+ final List<PermissionState> permissionStates = uidState.getPermissionStates();
+ final int permissionStatesSize = permissionStates.size();
+ for (int i = 0; i < permissionStatesSize; i++) {
+ final PermissionState permissionState = permissionStates.get(i);
+
+ final LegacyPermissionState.PermissionState legacyPermissionState =
+ new LegacyPermissionState.PermissionState(
+ permissionState.getPermission(), permissionState.isGranted(),
+ permissionState.getFlags());
+ if (permissionState.isRuntime()) {
+ legacyState.putRuntimePermissionState(legacyPermissionState, userId);
+ } else if (userId == UserHandle.USER_SYSTEM) {
+ legacyState.putInstallPermissionState(legacyPermissionState);
+ }
+ }
+ }
+ }
+ return legacyState;
+ }
+
+ @NonNull
+ private int[] getGidsForUid(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mLock) {
+ final UidPermissionState uidState = getUidStateLocked(appId, userId);
+ if (uidState == null) {
+ Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
+ + userId);
+ return EMPTY_INT_ARRAY;
+ }
+ return uidState.computeGids(mGlobalGids, userId);
+ }
+ }
+
private class PermissionManagerServiceInternalImpl extends PermissionManagerServiceInternal {
@Override
public void systemReady() {
@@ -4957,8 +4767,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
PermissionManagerService.this.onUserRemoved(userId);
}
@Override
- public void removePermissionsStateTEMP(int appId) {
- PermissionManagerService.this.removeAppState(appId);
+ public void removeAppIdStateTEMP(@AppIdInt int appId) {
+ PermissionManagerService.this.removeAppIdState(appId);
}
@Override
@UserIdInt
@@ -4973,16 +4783,11 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@UserIdInt int userId) {
return PermissionManagerService.this.getGrantedPermissions(packageName, userId);
}
- @Nullable
+ @NonNull
@Override
public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
return PermissionManagerService.this.getPermissionGids(permissionName, userId);
}
- @Nullable
- @Override
- public int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId) {
- return PermissionManagerService.this.getPackageGids(packageName, userId);
- }
@Override
public void grantRequestedRuntimePermissions(AndroidPackage pkg, int[] userIds,
String[] grantedPermissions, int callingUid) {
@@ -5084,8 +4889,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
BasePermission bp = mSettings.mPermissions.valueAt(i);
if (bp.perm != null && bp.perm.getProtection() == protection) {
- matchingPermissions.add(
- PackageInfoUtils.generatePermissionInfo(bp.perm, 0));
+ matchingPermissions.add(bp.generatePermissionInfo(0));
}
}
}
@@ -5106,8 +4910,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
if (bp.perm != null && (bp.perm.getProtectionFlags() & protectionFlags)
== protectionFlags) {
- matchingPermissions.add(
- PackageInfoUtils.generatePermissionInfo(bp.perm, 0));
+ matchingPermissions.add(bp.generatePermissionInfo(0));
}
}
}
@@ -5310,6 +5113,16 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
}
}
+
+ @NonNull
+ public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
+ return PermissionManagerService.this.getLegacyPermissionState(appId);
+ }
+
+ @NonNull
+ public int[] getGidsForUid(int uid) {
+ return PermissionManagerService.this.getGidsForUid(uid);
+ }
}
private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 6d9bd2a1b30d..5ea3458fcbfa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -37,8 +37,8 @@ import java.util.function.Consumer;
*
* TODO: Should be merged into PermissionManagerInternal, but currently uses internal classes.
*/
-public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal {
-
+public abstract class PermissionManagerServiceInternal extends PermissionManagerInternal
+ implements LegacyPermissionDataProvider {
/**
* Provider for package names.
*/
@@ -288,13 +288,13 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager
public abstract void onUserRemoved(@UserIdInt int userId);
/**
- * Remove the {@code PermissionsState} associated with an app ID, called the same time as the
+ * Remove the permission state associated with an app ID, called the same time as the
* removal of a {@code PackageSetitng}.
*
* TODO(zhanghai): This is a temporary method before we figure out a way to get notified of app
* ID removal via API.
*/
- public abstract void removePermissionsStateTEMP(int appId);
+ public abstract void removeAppIdStateTEMP(@AppIdInt int appId);
/**
* Update the shared user setting when a package with a shared user id is removed. The gids
@@ -324,12 +324,6 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager
@Nullable
public abstract int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId);
- /**
- * Get the GIDs computed from the permission state of a package.
- */
- @Nullable
- public abstract int[] getPackageGids(@NonNull String packageName, @UserIdInt int userId);
-
/** Retrieve the packages that have requested the given app op permission */
public abstract @Nullable String[] getAppOpPermissionPackages(
@NonNull String permName, int callingUid);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionState.java b/services/core/java/com/android/server/pm/permission/PermissionState.java
index 2ed9a50353d4..59b204f7dfff 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionState.java
@@ -17,7 +17,6 @@
package com.android.server.pm.permission;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import com.android.internal.annotations.GuardedBy;
@@ -41,13 +40,12 @@ public final class PermissionState {
@GuardedBy("mLock")
private int mFlags;
- public PermissionState(@NonNull BasePermission permission, boolean isRuntime) {
+ public PermissionState(@NonNull BasePermission permission) {
mPermission = permission;
- mRuntime = isRuntime;
}
public PermissionState(@NonNull PermissionState other) {
- this(other.mPermission, other.mRuntime);
+ this(other.mPermission);
mGranted = other.mGranted;
mFlags = other.mFlags;
@@ -63,14 +61,14 @@ public final class PermissionState {
return mPermission.getName();
}
- @Nullable
+ @NonNull
public int[] computeGids(@UserIdInt int userId) {
return mPermission.computeGids(userId);
}
public boolean isRuntime() {
synchronized (mLock) {
- return mRuntime;
+ return mPermission.isRuntime();
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
deleted file mode 100644
index 4fb2d5fc200e..000000000000
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ /dev/null
@@ -1,970 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm.permission;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * This class encapsulates the permissions for a package or a shared user.
- * <p>
- * There are two types of permissions: install (granted at installation)
- * and runtime (granted at runtime). Install permissions are granted to
- * all device users while runtime permissions are granted explicitly to
- * specific users.
- * </p>
- * <p>
- * The permissions are kept on a per device user basis. For example, an
- * application may have some runtime permissions granted under the device
- * owner but not granted under the secondary user.
- * <p>
- * This class is also responsible for keeping track of the Linux gids per
- * user for a package or a shared user. The gids are computed as a set of
- * the gids for all granted permissions' gids on a per user basis.
- * </p>
- */
-public final class PermissionsState {
-
- /** The permission operation failed. */
- public static final int PERMISSION_OPERATION_FAILURE = -1;
-
- /** The permission operation succeeded and no gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS = 0;
-
- /** The permission operation succeeded and gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
-
- private static final int[] NO_GIDS = {};
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private ArrayMap<String, PermissionData> mPermissions;
-
- private int[] mGlobalGids = NO_GIDS;
-
- @Nullable
- private SparseBooleanArray mMissing;
-
- private SparseBooleanArray mPermissionReviewRequired;
-
- public PermissionsState() {
- /* do nothing */
- }
-
- public PermissionsState(PermissionsState prototype) {
- copyFrom(prototype);
- }
-
- public int[] getGlobalGids() {
- return mGlobalGids;
- }
-
- /**
- * Sets the global gids, applicable to all users.
- *
- * @param globalGids The global gids.
- */
- public void setGlobalGids(int[] globalGids) {
- if (!ArrayUtils.isEmpty(globalGids)) {
- mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
- }
- }
-
- private static void invalidateCache() {
- PackageManager.invalidatePackageInfoCache();
- }
-
- /**
- * Initialized this instance from another one.
- *
- * @param other The other instance.
- */
- public void copyFrom(PermissionsState other) {
- if (other == this) {
- return;
- }
-
- synchronized (mLock) {
- if (mPermissions != null) {
- if (other.mPermissions == null) {
- mPermissions = null;
- } else {
- mPermissions.clear();
- }
- }
- if (other.mPermissions != null) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
- }
- final int permissionCount = other.mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String name = other.mPermissions.keyAt(i);
- PermissionData permissionData = other.mPermissions.valueAt(i);
- mPermissions.put(name, new PermissionData(permissionData));
- }
- }
- }
-
- mGlobalGids = NO_GIDS;
- if (other.mGlobalGids != NO_GIDS) {
- mGlobalGids = Arrays.copyOf(other.mGlobalGids,
- other.mGlobalGids.length);
- }
-
- if (mMissing != null) {
- if (other.mMissing == null) {
- mMissing = null;
- } else {
- mMissing.clear();
- }
- }
- if (other.mMissing != null) {
- if (mMissing == null) {
- mMissing = new SparseBooleanArray();
- }
- final int missingSize = other.mMissing.size();
- for (int i = 0; i < missingSize; i++) {
- mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i));
- }
- }
-
- if (mPermissionReviewRequired != null) {
- if (other.mPermissionReviewRequired == null) {
- mPermissionReviewRequired = null;
- } else {
- mPermissionReviewRequired.clear();
- }
- }
- if (other.mPermissionReviewRequired != null) {
- if (mPermissionReviewRequired == null) {
- mPermissionReviewRequired = new SparseBooleanArray();
- }
- final int userCount = other.mPermissionReviewRequired.size();
- for (int i = 0; i < userCount; i++) {
- final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i);
- mPermissionReviewRequired.put(other.mPermissionReviewRequired.keyAt(i),
- reviewRequired);
- }
- }
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final PermissionsState other = (PermissionsState) obj;
-
- synchronized (mLock) {
- if (mPermissions == null) {
- if (other.mPermissions != null) {
- return false;
- }
- } else if (!mPermissions.equals(other.mPermissions)) {
- return false;
- }
- }
-
- if (!Objects.equals(mMissing, other.mMissing)) {
- return false;
- }
-
- if (mPermissionReviewRequired == null) {
- if (other.mPermissionReviewRequired != null) {
- return false;
- }
- } else if (!mPermissionReviewRequired.equals(other.mPermissionReviewRequired)) {
- return false;
- }
- return Arrays.equals(mGlobalGids, other.mGlobalGids);
- }
-
- /**
- * Check whether the permissions state is missing for a user. This can happen if permission
- * state is rolled back and we'll need to generate a reasonable default state to keep the app
- * usable.
- */
- public boolean isMissing(@UserIdInt int userId) {
- return mMissing != null && mMissing.get(userId);
- }
-
- /**
- * Set whether the permissions state is missing for a user. This can happen if permission state
- * is rolled back and we'll need to generate a reasonable default state to keep the app usable.
- */
- public void setMissing(boolean missing, @UserIdInt int userId) {
- if (missing) {
- if (mMissing == null) {
- mMissing = new SparseBooleanArray();
- }
- mMissing.put(userId, true);
- } else {
- if (mMissing != null) {
- mMissing.delete(userId);
- if (mMissing.size() == 0) {
- mMissing = null;
- }
- }
- }
- }
-
- public boolean isPermissionReviewRequired(int userId) {
- return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
- }
-
- /**
- * Grant an install permission.
- *
- * @param permission The permission to grant.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
- */
- public int grantInstallPermission(BasePermission permission) {
- return grantPermission(permission, UserHandle.USER_ALL);
- }
-
- /**
- * Revoke an install permission.
- *
- * @param permission The permission to revoke.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
- */
- public int revokeInstallPermission(BasePermission permission) {
- return revokePermission(permission, UserHandle.USER_ALL);
- }
-
- /**
- * Grant a runtime permission for a given device user.
- *
- * @param permission The permission to grant.
- * @param userId The device user id.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
- */
- public int grantRuntimePermission(BasePermission permission, int userId) {
- enforceValidUserId(userId);
- if (userId == UserHandle.USER_ALL) {
- return PERMISSION_OPERATION_FAILURE;
- }
- return grantPermission(permission, userId);
- }
-
- /**
- * Revoke a runtime permission for a given device user.
- *
- * @param permission The permission to revoke.
- * @param userId The device user id.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
- */
- public int revokeRuntimePermission(BasePermission permission, int userId) {
- enforceValidUserId(userId);
- if (userId == UserHandle.USER_ALL) {
- return PERMISSION_OPERATION_FAILURE;
- }
- return revokePermission(permission, userId);
- }
-
- /**
- * Gets whether this state has a given runtime permission for a
- * given device user id.
- *
- * @param name The permission name.
- * @param userId The device user id.
- * @return Whether this state has the permission.
- */
- public boolean hasRuntimePermission(String name, int userId) {
- enforceValidUserId(userId);
- return !hasInstallPermission(name) && hasPermission(name, userId);
- }
-
- /**
- * Gets whether this state has a given install permission.
- *
- * @param name The permission name.
- * @return Whether this state has the permission.
- */
- public boolean hasInstallPermission(String name) {
- return hasPermission(name, UserHandle.USER_ALL);
- }
-
- /**
- * Gets whether the state has a given permission for the specified
- * user, regardless if this is an install or a runtime permission.
- *
- * @param name The permission name.
- * @param userId The device user id.
- * @return Whether the user has the permission.
- */
- public boolean hasPermission(String name, int userId) {
- enforceValidUserId(userId);
-
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- PermissionData permissionData = mPermissions.get(name);
-
- return permissionData != null && permissionData.isGranted(userId);
- }
-
- }
-
- /**
- * Returns whether the state has any known request for the given permission name,
- * whether or not it has been granted.
- */
- public boolean hasRequestedPermission(ArraySet<String> names) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- for (int i=names.size()-1; i>=0; i--) {
- if (mPermissions.get(names.valueAt(i)) != null) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Returns whether the state has any known request for the given permission name,
- * whether or not it has been granted.
- */
- public boolean hasRequestedPermission(String name) {
- return mPermissions != null && (mPermissions.get(name) != null);
- }
- /**
- * Gets all permissions for a given device user id regardless if they
- * are install time or runtime permissions.
- *
- * @param userId The device user id.
- * @return The permissions or an empty set.
- */
- public Set<String> getPermissions(int userId) {
- enforceValidUserId(userId);
-
- synchronized (mLock) {
- if (mPermissions == null) {
- return Collections.emptySet();
- }
-
- Set<String> permissions = new ArraySet<>(mPermissions.size());
-
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String permission = mPermissions.keyAt(i);
-
- if (hasInstallPermission(permission)) {
- permissions.add(permission);
- continue;
- }
-
- if (userId != UserHandle.USER_ALL) {
- if (hasRuntimePermission(permission, userId)) {
- permissions.add(permission);
- }
- }
- }
-
- return permissions;
- }
- }
-
- /**
- * Gets the state for an install permission or null if no such.
- *
- * @param name The permission name.
- * @return The permission state.
- */
- public PermissionState getInstallPermissionState(String name) {
- return getPermissionState(name, UserHandle.USER_ALL);
- }
-
- /**
- * Gets the state for a runtime permission or null if no such.
- *
- * @param name The permission name.
- * @param userId The device user id.
- * @return The permission state.
- */
- public PermissionState getRuntimePermissionState(String name, int userId) {
- enforceValidUserId(userId);
- return getPermissionState(name, userId);
- }
-
- /**
- * Gets all install permission states.
- *
- * @return The permission states or an empty set.
- */
- public List<PermissionState> getInstallPermissionStates() {
- return getPermissionStatesInternal(UserHandle.USER_ALL);
- }
-
- /**
- * Gets all runtime permission states.
- *
- * @return The permission states or an empty set.
- */
- public List<PermissionState> getRuntimePermissionStates(int userId) {
- enforceValidUserId(userId);
- return getPermissionStatesInternal(userId);
- }
-
- /**
- * Gets the flags for a permission regardless if it is install or
- * runtime permission.
- *
- * @param name The permission name.
- * @return The permission state or null if no such.
- */
- public int getPermissionFlags(String name, int userId) {
- PermissionState installPermState = getInstallPermissionState(name);
- if (installPermState != null) {
- return installPermState.getFlags();
- }
- PermissionState runtimePermState = getRuntimePermissionState(name, userId);
- if (runtimePermState != null) {
- return runtimePermState.getFlags();
- }
- return 0;
- }
-
- /**
- * Update the flags associated with a given permission.
- * @param permission The permission whose flags to update.
- * @param userId The user for which to update.
- * @param flagMask Mask for which flags to change.
- * @param flagValues New values for the mask flags.
- * @return Whether the permission flags changed.
- */
- public boolean updatePermissionFlags(BasePermission permission, int userId,
- int flagMask, int flagValues) {
- enforceValidUserId(userId);
-
- final boolean mayChangeFlags = flagValues != 0 || flagMask != 0;
-
- synchronized (mLock) {
- if (mPermissions == null) {
- if (!mayChangeFlags) {
- return false;
- }
- ensurePermissionData(permission);
- }
- }
-
- PermissionData permissionData = null;
- synchronized (mLock) {
- permissionData = mPermissions.get(permission.getName());
- }
-
- if (permissionData == null) {
- if (!mayChangeFlags) {
- return false;
- }
- permissionData = ensurePermissionData(permission);
- }
-
- final int oldFlags = permissionData.getFlags(userId);
-
- final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues);
- if (updated) {
- final int newFlags = permissionData.getFlags(userId);
- if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- if (mPermissionReviewRequired == null) {
- mPermissionReviewRequired = new SparseBooleanArray();
- }
- mPermissionReviewRequired.put(userId, true);
- } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
- if (mPermissionReviewRequired != null && !hasPermissionRequiringReview(userId)) {
- mPermissionReviewRequired.delete(userId);
- if (mPermissionReviewRequired.size() <= 0) {
- mPermissionReviewRequired = null;
- }
- }
- }
- }
- return updated;
- }
-
- private boolean hasPermissionRequiringReview(int userId) {
- synchronized (mLock) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- final PermissionData permission = mPermissions.valueAt(i);
- if ((permission.getFlags(userId)
- & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public boolean updatePermissionFlagsForAllPermissions(
- int userId, int flagMask, int flagValues) {
- enforceValidUserId(userId);
-
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- boolean changed = false;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionData permissionData = mPermissions.valueAt(i);
- changed |= permissionData.updateFlags(userId, flagMask, flagValues);
- }
-
- return changed;
- }
- }
-
- /**
- * Compute the Linux gids for a given device user from the permissions
- * granted to this user. Note that these are computed to avoid additional
- * state as they are rarely accessed.
- *
- * @param userId The device user id.
- * @return The gids for the device user.
- */
- public int[] computeGids(int userId) {
- enforceValidUserId(userId);
-
- int[] gids = mGlobalGids;
-
- synchronized (mLock) {
- if (mPermissions != null) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String permission = mPermissions.keyAt(i);
- if (!hasPermission(permission, userId)) {
- continue;
- }
- PermissionData permissionData = mPermissions.valueAt(i);
- final int[] permGids = permissionData.computeGids(userId);
- if (permGids != NO_GIDS) {
- gids = appendInts(gids, permGids);
- }
- }
- }
- }
-
- return gids;
- }
-
- /**
- * Compute the Linux gids for all device users from the permissions
- * granted to these users.
- *
- * @return The gids for all device users.
- */
- public int[] computeGids(int[] userIds) {
- int[] gids = mGlobalGids;
-
- for (int userId : userIds) {
- final int[] userGids = computeGids(userId);
- gids = appendInts(gids, userGids);
- }
-
- return gids;
- }
-
- /**
- * Resets the internal state of this object.
- */
- public void reset() {
- mGlobalGids = NO_GIDS;
-
- synchronized (mLock) {
- mPermissions = null;
- invalidateCache();
- }
-
- mMissing = null;
- mPermissionReviewRequired = null;
- }
-
- private PermissionState getPermissionState(String name, int userId) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return null;
- }
- PermissionData permissionData = mPermissions.get(name);
- if (permissionData == null) {
- return null;
- }
-
- return permissionData.getPermissionState(userId);
- }
- }
-
- private List<PermissionState> getPermissionStatesInternal(int userId) {
- enforceValidUserId(userId);
-
- synchronized (mLock) {
- if (mPermissions == null) {
- return Collections.emptyList();
- }
-
- List<PermissionState> permissionStates = new ArrayList<>();
-
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionData permissionData = mPermissions.valueAt(i);
-
- PermissionState permissionState = permissionData.getPermissionState(userId);
- if (permissionState != null) {
- permissionStates.add(permissionState);
- }
- }
-
- return permissionStates;
- }
- }
-
- private int grantPermission(BasePermission permission, int userId) {
- if (hasPermission(permission.getName(), userId)) {
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
- final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
-
- PermissionData permissionData = ensurePermissionData(permission);
-
- if (!permissionData.grant(userId)) {
- return PERMISSION_OPERATION_FAILURE;
- }
-
- if (hasGids) {
- final int[] newGids = computeGids(userId);
- if (oldGids.length != newGids.length) {
- return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
- }
- }
-
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- private int revokePermission(BasePermission permission, int userId) {
- final String permName = permission.getName();
- if (!hasPermission(permName, userId)) {
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
- final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
-
- PermissionData permissionData = null;
- synchronized (mLock) {
- permissionData = mPermissions.get(permName);
- }
-
- if (!permissionData.revoke(userId)) {
- return PERMISSION_OPERATION_FAILURE;
- }
-
- if (permissionData.isDefault()) {
- ensureNoPermissionData(permName);
- }
-
- if (hasGids) {
- final int[] newGids = computeGids(userId);
- if (oldGids.length != newGids.length) {
- return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
- }
- }
-
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- // TODO: fix this to use arraycopy and append all ints in one go
- private static int[] appendInts(int[] current, int[] added) {
- if (current != null && added != null) {
- for (int guid : added) {
- current = ArrayUtils.appendInt(current, guid);
- }
- }
- return current;
- }
-
- private static void enforceValidUserId(int userId) {
- if (userId != UserHandle.USER_ALL && userId < 0) {
- throw new IllegalArgumentException("Invalid userId:" + userId);
- }
- }
-
- private PermissionData ensurePermissionData(BasePermission permission) {
- final String permName = permission.getName();
-
- synchronized (mLock) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
- }
- PermissionData permissionData = mPermissions.get(permName);
- if (permissionData == null) {
- permissionData = new PermissionData(permission);
- mPermissions.put(permName, permissionData);
- }
- return permissionData;
- }
-
- }
-
- private void ensureNoPermissionData(String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return;
- }
- mPermissions.remove(name);
- if (mPermissions.isEmpty()) {
- mPermissions = null;
- }
- }
-
- }
-
- private static final class PermissionData {
-
- private final Object mLock = new Object();
-
- private final BasePermission mPerm;
- @GuardedBy("mLock")
- private SparseArray<PermissionState> mUserStates = new SparseArray<>();
-
- public PermissionData(BasePermission perm) {
- mPerm = perm;
- }
-
- public PermissionData(PermissionData other) {
- this(other.mPerm);
-
- synchronized (mLock) {
- final int otherStateCount = other.mUserStates.size();
- for (int i = 0; i < otherStateCount; i++) {
- final int otherUserId = other.mUserStates.keyAt(i);
- PermissionState otherState = other.mUserStates.valueAt(i);
- mUserStates.put(otherUserId, new PermissionState(otherState));
- }
- }
- }
-
- public int[] computeGids(int userId) {
- return mPerm.computeGids(userId);
- }
-
- public boolean isGranted(int userId) {
- synchronized (mLock) {
- if (isInstallPermission()) {
- userId = UserHandle.USER_ALL;
- }
-
- PermissionState userState = mUserStates.get(userId);
- if (userState == null) {
- return false;
- }
-
- return userState.mGranted;
- }
- }
-
- public boolean grant(int userId) {
- synchronized (mLock) {
- if (!isCompatibleUserId(userId)) {
- return false;
- }
-
- if (isGranted(userId)) {
- return false;
- }
-
- PermissionState userState = mUserStates.get(userId);
- if (userState == null) {
- userState = new PermissionState(mPerm);
- mUserStates.put(userId, userState);
- }
-
- userState.mGranted = true;
-
- invalidateCache();
- return true;
- }
- }
-
- public boolean revoke(int userId) {
- synchronized (mLock) {
- if (!isCompatibleUserId(userId)) {
- return false;
- }
-
- if (!isGranted(userId)) {
- return false;
- }
-
- PermissionState userState = mUserStates.get(userId);
- userState.mGranted = false;
-
- if (userState.isDefault()) {
- mUserStates.remove(userId);
- }
-
- invalidateCache();
- return true;
- }
- }
-
- public PermissionState getPermissionState(int userId) {
- synchronized (mLock) {
- return mUserStates.get(userId);
- }
- }
-
- public int getFlags(int userId) {
- synchronized (mLock) {
- PermissionState userState = mUserStates.get(userId);
- if (userState != null) {
- return userState.mFlags;
- }
- return 0;
- }
- }
-
- public boolean isDefault() {
- synchronized (mLock) {
- return mUserStates.size() <= 0;
- }
- }
-
- public static boolean isInstallPermissionKey(int userId) {
- return userId == UserHandle.USER_ALL;
- }
-
- public boolean updateFlags(int userId, int flagMask, int flagValues) {
- synchronized (mLock) {
- if (isInstallPermission()) {
- userId = UserHandle.USER_ALL;
- }
-
- if (!isCompatibleUserId(userId)) {
- return false;
- }
-
- final int newFlags = flagValues & flagMask;
-
- // Okay to do before the modification because we hold the lock.
- invalidateCache();
-
- PermissionState userState = mUserStates.get(userId);
- if (userState != null) {
- final int oldFlags = userState.mFlags;
- userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
- if (userState.isDefault()) {
- mUserStates.remove(userId);
- }
- return userState.mFlags != oldFlags;
- } else if (newFlags != 0) {
- userState = new PermissionState(mPerm);
- userState.mFlags = newFlags;
- mUserStates.put(userId, userState);
- return true;
- }
-
- return false;
- }
- }
-
- private boolean isCompatibleUserId(int userId) {
- return isDefault() || !(isInstallPermission() ^ isInstallPermissionKey(userId));
- }
-
- private boolean isInstallPermission() {
- return mUserStates.size() == 1
- && mUserStates.get(UserHandle.USER_ALL) != null;
- }
- }
-
- public static final class PermissionState {
- private final BasePermission mPermission;
- private boolean mGranted;
- private int mFlags;
-
- public PermissionState(BasePermission permission) {
- mPermission = permission;
- }
-
- public PermissionState(PermissionState other) {
- mPermission = other.mPermission;
- mGranted = other.mGranted;
- mFlags = other.mFlags;
- }
-
- public boolean isDefault() {
- return !mGranted && mFlags == 0;
- }
-
- public BasePermission getPermission() {
- return mPermission;
- }
-
- public String getName() {
- return mPermission.getName();
- }
-
- public boolean isGranted() {
- return mGranted;
- }
-
- public int getFlags() {
- return mFlags;
- }
- }
-}
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index 65dc320eadc2..c0d71ac26853 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -18,24 +18,21 @@
]
},
{
- "name": "CtsPermission2TestCases",
+ "name": "CtsAppSecurityHostTestCases",
"options": [
{
- "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
- },
- {
- "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
+ "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
}
]
},
{
- "name": "CtsPermissionHostTestCases"
- },
- {
- "name": "CtsAppSecurityHostTestCases",
+ "name": "CtsPermission2TestCases",
"options": [
{
- "include-filter": "android.appsecurity.cts.AppSecurityTests#rebootWithDuplicatePermission"
+ "include-filter": "android.permission2.cts.RestrictedPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest"
}
]
},
diff --git a/services/core/java/com/android/server/pm/permission/UidPermissionState.java b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
index 4c047ffd30e8..38e8955e8cdb 100644
--- a/services/core/java/com/android/server/pm/permission/UidPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/UidPermissionState.java
@@ -22,151 +22,45 @@ import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import android.util.ArraySet;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import android.util.IntArray;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Permission state for a UID.
- * <p>
- * This class is also responsible for keeping track of the Linux GIDs per
- * user for a package or a shared user. The GIDs are computed as a set of
- * the GIDs for all granted permissions' GIDs on a per user basis.
*/
public final class UidPermissionState {
- /** The permission operation failed. */
- public static final int PERMISSION_OPERATION_FAILURE = -1;
-
- /** The permission operation succeeded and no gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS = 0;
-
- /** The permission operation succeeded and gids changed. */
- public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
-
- private static final int[] NO_GIDS = {};
-
- @NonNull
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private ArrayMap<String, PermissionState> mPermissions;
-
- @NonNull
- private int[] mGlobalGids = NO_GIDS;
-
private boolean mMissing;
- private boolean mPermissionReviewRequired;
-
- public UidPermissionState() {
- /* do nothing */
- }
+ @Nullable
+ private ArrayMap<String, PermissionState> mPermissions;
- public UidPermissionState(@NonNull UidPermissionState prototype) {
- copyFrom(prototype);
- }
+ public UidPermissionState() {}
- /**
- * Gets the global gids, applicable to all users.
- */
- @NonNull
- public int[] getGlobalGids() {
- return mGlobalGids;
- }
+ public UidPermissionState(@NonNull UidPermissionState other) {
+ mMissing = other.mMissing;
- /**
- * Sets the global gids, applicable to all users.
- *
- * @param globalGids The global gids.
- */
- public void setGlobalGids(@NonNull int[] globalGids) {
- if (!ArrayUtils.isEmpty(globalGids)) {
- mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
+ if (other.mPermissions != null) {
+ mPermissions = new ArrayMap<>();
+ final int permissionsSize = other.mPermissions.size();
+ for (int i = 0; i < permissionsSize; i++) {
+ final String name = other.mPermissions.keyAt(i);
+ final PermissionState permissionState = other.mPermissions.valueAt(i);
+ mPermissions.put(name, new PermissionState(permissionState));
+ }
}
}
- static void invalidateCache() {
- PackageManager.invalidatePackageInfoCache();
- }
-
/**
- * Initialized this instance from another one.
- *
- * @param other The other instance.
+ * Reset the internal state of this object.
*/
- public void copyFrom(@NonNull UidPermissionState other) {
- if (other == this) {
- return;
- }
-
- synchronized (mLock) {
- if (mPermissions != null) {
- if (other.mPermissions == null) {
- mPermissions = null;
- } else {
- mPermissions.clear();
- }
- }
- if (other.mPermissions != null) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
- }
- final int permissionCount = other.mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String name = other.mPermissions.keyAt(i);
- PermissionState permissionState = other.mPermissions.valueAt(i);
- mPermissions.put(name, new PermissionState(permissionState));
- }
- }
- }
-
- mGlobalGids = NO_GIDS;
- if (other.mGlobalGids != NO_GIDS) {
- mGlobalGids = other.mGlobalGids.clone();
- }
-
- mMissing = other.mMissing;
-
- mPermissionReviewRequired = other.mPermissionReviewRequired;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final UidPermissionState other = (UidPermissionState) obj;
-
- synchronized (mLock) {
- if (mPermissions == null) {
- if (other.mPermissions != null) {
- return false;
- }
- } else if (!mPermissions.equals(other.mPermissions)) {
- return false;
- }
- }
-
- if (mMissing != other.mMissing) {
- return false;
- }
-
- if (mPermissionReviewRequired != other.mPermissionReviewRequired) {
- return false;
- }
- return Arrays.equals(mGlobalGids, other.mGlobalGids);
+ public void reset() {
+ mMissing = false;
+ mPermissions = null;
+ invalidateCache();
}
/**
@@ -186,389 +80,275 @@ public final class UidPermissionState {
mMissing = missing;
}
- public boolean isPermissionReviewRequired() {
- return mPermissionReviewRequired;
- }
-
/**
- * Gets whether the state has a given permission.
+ * Get whether there is a permission state for a permission.
*
- * @param name The permission name.
- * @return Whether the state has the permission.
+ * @deprecated This used to be named hasRequestedPermission() and its usage is confusing
*/
- public boolean hasPermission(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- PermissionState permissionState = mPermissions.get(name);
- return permissionState != null && permissionState.isGranted();
- }
+ @Deprecated
+ public boolean hasPermissionState(@NonNull String name) {
+ return mPermissions != null && mPermissions.containsKey(name);
}
/**
- * Gets whether the state has a given install permission.
+ * Get whether there is a permission state for any of the permissions.
*
- * @param name The permission name.
- * @return Whether the state has the install permission.
+ * @deprecated This used to be named hasRequestedPermission() and its usage is confusing
*/
- public boolean hasInstallPermission(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
+ @Deprecated
+ public boolean hasPermissionState(@NonNull ArraySet<String> names) {
+ if (mPermissions == null) {
+ return false;
+ }
+ final int namesSize = names.size();
+ for (int i = 0; i < namesSize; i++) {
+ final String name = names.valueAt(i);
+ if (mPermissions.containsKey(name)) {
+ return true;
}
- PermissionState permissionState = mPermissions.get(name);
- return permissionState != null && permissionState.isGranted()
- && !permissionState.isRuntime();
}
+ return false;
}
/**
- * Returns whether the state has any known request for the given permission name,
- * whether or not it has been granted.
+ * Gets the state for a permission or null if none.
*
- * @deprecated Not all requested permissions may be here.
+ * @param name the permission name
+ * @return the permission state
*/
- @Deprecated
- public boolean hasRequestedPermission(@NonNull ArraySet<String> names) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- for (int i = names.size() - 1; i >= 0; i--) {
- if (mPermissions.get(names.valueAt(i)) != null) {
- return true;
- }
- }
+ @Nullable
+ public PermissionState getPermissionState(@NonNull String name) {
+ if (mPermissions == null) {
+ return null;
}
-
- return false;
+ return mPermissions.get(name);
}
- /**
- * Returns whether the state has any known request for the given permission name,
- * whether or not it has been granted.
- *
- * @deprecated Not all requested permissions may be here.
- */
- @Deprecated
- public boolean hasRequestedPermission(@NonNull String name) {
- return mPermissions != null && (mPermissions.get(name) != null);
+ @NonNull
+ private PermissionState getOrCreatePermissionState(@NonNull BasePermission permission) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ final String name = permission.getName();
+ PermissionState permissionState = mPermissions.get(name);
+ if (permissionState == null) {
+ permissionState = new PermissionState(permission);
+ mPermissions.put(name, permissionState);
+ }
+ return permissionState;
}
/**
- * Gets all permissions for a given device user id regardless if they
- * are install time or runtime permissions.
+ * Get all permission states.
*
- * @return The permissions or an empty set.
+ * @return the permission states
*/
@NonNull
- public Set<String> getPermissions() {
- synchronized (mLock) {
- if (mPermissions == null) {
- return Collections.emptySet();
- }
-
- Set<String> permissions = new ArraySet<>(mPermissions.size());
-
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- String permission = mPermissions.keyAt(i);
-
- if (hasPermission(permission)) {
- permissions.add(permission);
- }
- }
-
- return permissions;
+ public List<PermissionState> getPermissionStates() {
+ if (mPermissions == null) {
+ return Collections.emptyList();
}
+ return new ArrayList<>(mPermissions.values());
}
/**
- * Gets the flags for a permission.
+ * Put a permission state.
*
- * @param name The permission name.
- * @return The permission state or null if no such.
+ * @param permission the permission
+ * @param granted whether the permission is granted
+ * @param flags the permission flags
*/
- public int getPermissionFlags(@NonNull String name) {
- PermissionState permState = getPermissionState(name);
- if (permState != null) {
- return permState.getFlags();
+ public void putPermissionState(@NonNull BasePermission permission, boolean granted, int flags) {
+ final String name = permission.getName();
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ } else {
+ mPermissions.remove(name);
+ }
+ final PermissionState permissionState = new PermissionState(permission);
+ if (granted) {
+ permissionState.grant();
}
- return 0;
+ permissionState.updateFlags(flags, flags);
+ mPermissions.put(name, permissionState);
}
/**
- * Update the flags associated with a given permission.
- * @param permission The permission whose flags to update.
- * @param flagMask Mask for which flags to change.
- * @param flagValues New values for the mask flags.
- * @return Whether the permission flags changed.
+ * Remove a permission state.
+ *
+ * @param name the permission name
+ * @return whether the permission state changed
*/
- public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask,
- int flagValues) {
- if (flagMask == 0) {
+ public boolean removePermissionState(@NonNull String name) {
+ if (mPermissions == null) {
return false;
}
-
- PermissionState permissionState = ensurePermissionState(permission);
-
- final int oldFlags = permissionState.getFlags();
-
- synchronized (mLock) {
- final boolean updated = permissionState.updateFlags(flagMask, flagValues);
- if (updated) {
- final int newFlags = permissionState.getFlags();
- if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- mPermissionReviewRequired = true;
- } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
- && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
- if (mPermissionReviewRequired && !hasPermissionRequiringReview()) {
- mPermissionReviewRequired = false;
- }
- }
- }
- return updated;
- }
- }
-
- private boolean hasPermissionRequiringReview() {
- synchronized (mLock) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- final PermissionState permission = mPermissions.valueAt(i);
- if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- return true;
- }
- }
- }
- return false;
- }
-
- public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return false;
- }
- boolean changed = false;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionState permissionState = mPermissions.valueAt(i);
- changed |= permissionState.updateFlags(flagMask, flagValues);
- }
- return changed;
+ final boolean changed = mPermissions.remove(name) != null;
+ if (changed && mPermissions.isEmpty()) {
+ mPermissions = null;
}
+ return changed;
}
/**
- * Compute the Linux gids for a given device user from the permissions
- * granted to this user. Note that these are computed to avoid additional
- * state as they are rarely accessed.
+ * Get whether a permission is granted.
*
- * @param userId The device user id.
- * @return The gids for the device user.
+ * @param name the permission name
+ * @return whether the permission is granted
*/
- @NonNull
- public int[] computeGids(@UserIdInt int userId) {
- int[] gids = mGlobalGids;
-
- synchronized (mLock) {
- if (mPermissions != null) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- PermissionState permissionState = mPermissions.valueAt(i);
- if (!permissionState.isGranted()) {
- continue;
- }
- final int[] permGids = permissionState.computeGids(userId);
- if (permGids != NO_GIDS) {
- gids = appendInts(gids, permGids);
- }
- }
- }
- }
-
- return gids;
+ public boolean isPermissionGranted(@NonNull String name) {
+ final PermissionState permissionState = getPermissionState(name);
+ return permissionState != null && permissionState.isGranted();
}
/**
- * Compute the Linux gids for all device users from the permissions
- * granted to these users.
+ * Get all the granted permissions.
*
- * @return The gids for all device users.
+ * @return the granted permissions
*/
@NonNull
- public int[] computeGids(@NonNull int[] userIds) {
- int[] gids = mGlobalGids;
-
- for (int userId : userIds) {
- final int[] userGids = computeGids(userId);
- gids = appendInts(gids, userGids);
+ public Set<String> getGrantedPermissions() {
+ if (mPermissions == null) {
+ return Collections.emptySet();
}
- return gids;
- }
-
- /**
- * Resets the internal state of this object.
- */
- public void reset() {
- mGlobalGids = NO_GIDS;
+ final Set<String> permissions = new ArraySet<>(mPermissions.size());
+ final int permissionsSize = mPermissions.size();
+ for (int i = 0; i < permissionsSize; i++) {
+ final PermissionState permissionState = mPermissions.valueAt(i);
- synchronized (mLock) {
- mPermissions = null;
- invalidateCache();
+ if (permissionState.isGranted()) {
+ permissions.add(permissionState.getName());
+ }
}
-
- mMissing = false;
- mPermissionReviewRequired = false;
+ return permissions;
}
/**
- * Gets the state for a permission or null if no such.
+ * Grant a permission.
*
- * @param name The permission name.
- * @return The permission state.
+ * @param permission the permission to grant
+ * @return whether the permission grant state changed
*/
- @Nullable
- public PermissionState getPermissionState(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return null;
- }
- return mPermissions.get(name);
- }
+ public boolean grantPermission(@NonNull BasePermission permission) {
+ final PermissionState permissionState = getOrCreatePermissionState(permission);
+ return permissionState.grant();
}
/**
- * Gets all permission states.
+ * Revoke a permission.
*
- * @return The permission states or an empty set.
+ * @param permission the permission to revoke
+ * @return whether the permission grant state changed
*/
- @NonNull
- public List<PermissionState> getPermissionStates() {
- synchronized (mLock) {
- if (mPermissions == null) {
- return Collections.emptyList();
- }
- return new ArrayList<>(mPermissions.values());
+ public boolean revokePermission(@NonNull BasePermission permission) {
+ final String name = permission.getName();
+ final PermissionState permissionState = getPermissionState(name);
+ if (permissionState == null) {
+ return false;
}
- }
-
- /**
- * Put a permission state.
- */
- public void putPermissionState(@NonNull BasePermission permission, boolean isRuntime,
- boolean isGranted, int flags) {
- synchronized (mLock) {
- ensureNoPermissionState(permission.name);
- PermissionState permissionState = ensurePermissionState(permission, isRuntime);
- if (isGranted) {
- permissionState.grant();
- }
- permissionState.updateFlags(flags, flags);
- if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
- mPermissionReviewRequired = true;
- }
+ final boolean changed = permissionState.revoke();
+ if (changed && permissionState.isDefault()) {
+ removePermissionState(name);
}
+ return changed;
}
/**
- * Grant a permission.
+ * Get the flags for a permission.
*
- * @param permission The permission to grant.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
+ * @param name the permission name
+ * @return the permission flags
*/
- public int grantPermission(@NonNull BasePermission permission) {
- if (hasPermission(permission.getName())) {
- return PERMISSION_OPERATION_SUCCESS;
- }
-
- PermissionState permissionState = ensurePermissionState(permission);
-
- if (!permissionState.grant()) {
- return PERMISSION_OPERATION_FAILURE;
+ public int getPermissionFlags(@NonNull String name) {
+ final PermissionState permissionState = getPermissionState(name);
+ if (permissionState == null) {
+ return 0;
}
-
- return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
- : PERMISSION_OPERATION_SUCCESS;
+ return permissionState.getFlags();
}
/**
- * Revoke a permission.
+ * Update the flags for a permission.
*
- * @param permission The permission to revoke.
- * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
- * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
- * #PERMISSION_OPERATION_FAILURE}.
+ * @param permission the permission name
+ * @param flagMask the mask for the flags
+ * @param flagValues the new values for the masked flags
+ * @return whether the permission flags changed
*/
- public int revokePermission(@NonNull BasePermission permission) {
- final String permissionName = permission.getName();
- if (!hasPermission(permissionName)) {
- return PERMISSION_OPERATION_SUCCESS;
+ public boolean updatePermissionFlags(@NonNull BasePermission permission, int flagMask,
+ int flagValues) {
+ if (flagMask == 0) {
+ return false;
}
-
- PermissionState permissionState;
- synchronized (mLock) {
- permissionState = mPermissions.get(permissionName);
+ final PermissionState permissionState = getOrCreatePermissionState(permission);
+ final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+ if (changed && permissionState.isDefault()) {
+ removePermissionState(permission.name);
}
+ return changed;
+ }
- if (!permissionState.revoke()) {
- return PERMISSION_OPERATION_FAILURE;
+ public boolean updatePermissionFlagsForAllPermissions(int flagMask, int flagValues) {
+ if (flagMask == 0) {
+ return false;
}
-
- if (permissionState.isDefault()) {
- ensureNoPermissionState(permissionName);
+ if (mPermissions == null) {
+ return false;
}
-
- return permission.hasGids() ? PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED
- : PERMISSION_OPERATION_SUCCESS;
- }
-
- // TODO: fix this to use arraycopy and append all ints in one go
- private static int[] appendInts(int[] current, int[] added) {
- if (current != null && added != null) {
- for (int guid : added) {
- current = ArrayUtils.appendInt(current, guid);
+ boolean anyChanged = false;
+ for (int i = mPermissions.size() - 1; i >= 0; i--) {
+ final PermissionState permissionState = mPermissions.valueAt(i);
+ final boolean changed = permissionState.updateFlags(flagMask, flagValues);
+ if (changed && permissionState.isDefault()) {
+ mPermissions.removeAt(i);
}
+ anyChanged |= changed;
}
- return current;
+ return anyChanged;
}
- @NonNull
- private PermissionState ensurePermissionState(@NonNull BasePermission permission) {
- return ensurePermissionState(permission, permission.isRuntime());
+ public boolean isPermissionReviewRequired() {
+ if (mPermissions == null) {
+ return false;
+ }
+ final int permissionsSize = mPermissions.size();
+ for (int i = 0; i < permissionsSize; i++) {
+ final PermissionState permission = mPermissions.valueAt(i);
+ if ((permission.getFlags() & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+ return true;
+ }
+ }
+ return false;
}
+ /**
+ * Compute the Linux GIDs from the permissions granted to a user.
+ *
+ * @param userId the user ID
+ * @return the GIDs for the user
+ */
@NonNull
- private PermissionState ensurePermissionState(@NonNull BasePermission permission,
- boolean isRuntime) {
- final String permissionName = permission.getName();
- synchronized (mLock) {
- if (mPermissions == null) {
- mPermissions = new ArrayMap<>();
+ public int[] computeGids(@NonNull int[] globalGids, @UserIdInt int userId) {
+ IntArray gids = IntArray.wrap(globalGids);
+ if (mPermissions == null) {
+ return gids.toArray();
+ }
+ final int permissionsSize = mPermissions.size();
+ for (int i = 0; i < permissionsSize; i++) {
+ PermissionState permissionState = mPermissions.valueAt(i);
+ if (!permissionState.isGranted()) {
+ continue;
}
- PermissionState permissionState = mPermissions.get(permissionName);
- if (permissionState == null) {
- permissionState = new PermissionState(permission, isRuntime);
- mPermissions.put(permissionName, permissionState);
+ final int[] permissionGids = permissionState.computeGids(userId);
+ if (permissionGids.length != 0) {
+ gids.addAll(permissionGids);
}
- return permissionState;
}
+ return gids.toArray();
}
- private void ensureNoPermissionState(@NonNull String name) {
- synchronized (mLock) {
- if (mPermissions == null) {
- return;
- }
- mPermissions.remove(name);
- if (mPermissions.isEmpty()) {
- mPermissions = null;
- }
- }
+ static void invalidateCache() {
+ PackageManager.invalidatePackageInfoCache();
}
}
diff --git a/services/core/java/com/android/server/pm/permission/UserPermissionState.java b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
index 7f55cb161e40..2c741cfd6364 100644
--- a/services/core/java/com/android/server/pm/permission/UserPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/UserPermissionState.java
@@ -23,8 +23,6 @@ import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
-
/**
* Permission state for a user.
*/
@@ -33,71 +31,52 @@ public final class UserPermissionState {
* Whether the install permissions have been granted to a package, so that no install
* permissions should be added to it unless the package is upgraded.
*/
- @GuardedBy("mLock")
@NonNull
private final ArraySet<String> mInstallPermissionsFixed = new ArraySet<>();
/**
* Maps from app ID to {@link UidPermissionState}.
*/
- @GuardedBy("mLock")
@NonNull
private final SparseArray<UidPermissionState> mUidStates = new SparseArray<>();
- @NonNull
- private final Object mLock;
-
- public UserPermissionState(@NonNull Object lock) {
- mLock = lock;
- }
-
public boolean areInstallPermissionsFixed(@NonNull String packageName) {
- synchronized (mLock) {
- return mInstallPermissionsFixed.contains(packageName);
- }
+ return mInstallPermissionsFixed.contains(packageName);
}
public void setInstallPermissionsFixed(@NonNull String packageName, boolean fixed) {
- synchronized (mLock) {
- if (fixed) {
- mInstallPermissionsFixed.add(packageName);
- } else {
- mInstallPermissionsFixed.remove(packageName);
- }
+ if (fixed) {
+ mInstallPermissionsFixed.add(packageName);
+ } else {
+ mInstallPermissionsFixed.remove(packageName);
}
}
@Nullable
public UidPermissionState getUidState(@AppIdInt int appId) {
checkAppId(appId);
- synchronized (mLock) {
- return mUidStates.get(appId);
- }
+ return mUidStates.get(appId);
}
@NonNull
public UidPermissionState getOrCreateUidState(@AppIdInt int appId) {
checkAppId(appId);
- synchronized (mLock) {
- UidPermissionState uidState = mUidStates.get(appId);
- if (uidState == null) {
- uidState = new UidPermissionState();
- mUidStates.put(appId, uidState);
- }
- return uidState;
+ UidPermissionState uidState = mUidStates.get(appId);
+ if (uidState == null) {
+ uidState = new UidPermissionState();
+ mUidStates.put(appId, uidState);
}
+ return uidState;
}
public void removeUidState(@AppIdInt int appId) {
checkAppId(appId);
- synchronized (mLock) {
- mUidStates.delete(appId);
- }
+ mUidStates.delete(appId);
}
private void checkAppId(@AppIdInt int appId) {
if (UserHandle.getUserId(appId) != 0) {
- throw new IllegalArgumentException(appId + " is not an app ID");
+ throw new IllegalArgumentException("Invalid app ID " + appId);
}
}
}
diff --git a/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
new file mode 100644
index 000000000000..54f618327da8
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DeviceStatePolicyImpl.java
@@ -0,0 +1,44 @@
+/*
+ * 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.policy;
+
+import android.annotation.NonNull;
+
+import com.android.server.devicestate.DeviceStatePolicy;
+import com.android.server.devicestate.DeviceStateProvider;
+
+/**
+ * Default empty implementation of {@link DeviceStatePolicy}.
+ *
+ * @see DeviceStateProviderImpl
+ */
+public final class DeviceStatePolicyImpl implements DeviceStatePolicy {
+ private final DeviceStateProvider mProvider;
+
+ public DeviceStatePolicyImpl() {
+ mProvider = new DeviceStateProviderImpl();
+ }
+
+ public DeviceStateProvider getDeviceStateProvider() {
+ return mProvider;
+ }
+
+ @Override
+ public void configureDeviceForState(int state, @NonNull Runnable onComplete) {
+ onComplete.run();
+ }
+}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
new file mode 100644
index 000000000000..85ab0bc12cae
--- /dev/null
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -0,0 +1,45 @@
+/*
+ * 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.policy;
+
+import android.annotation.Nullable;
+
+import com.android.server.devicestate.DeviceStateProvider;
+
+/**
+ * Default implementation of {@link DeviceStateProvider}. Currently only supports
+ * {@link #DEFAULT_DEVICE_STATE}.
+ *
+ * @see DeviceStatePolicyImpl
+ */
+final class DeviceStateProviderImpl implements DeviceStateProvider {
+ private static final int DEFAULT_DEVICE_STATE = 0;
+
+ @Nullable
+ private Listener mListener = null;
+
+ @Override
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new RuntimeException("Provider already has a listener set.");
+ }
+
+ mListener = listener;
+ mListener.onSupportedDeviceStatesChanged(new int[]{ DEFAULT_DEVICE_STATE });
+ mListener.onStateChanged(DEFAULT_DEVICE_STATE);
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4b9f87be8a00..d4e83bce7e7c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1561,6 +1561,17 @@ public class PhoneWindowManager implements WindowManagerPolicy {
startActivityAsUser(intent, UserHandle.CURRENT);
}
+ private void toggleNotificationPanel() {
+ IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ try {
+ statusBarService.togglePanel();
+ } catch (RemoteException e) {
+ // do nothing.
+ }
+ }
+ }
+
private void showPictureInPictureMenu(KeyEvent event) {
if (DEBUG_INPUT) Log.d(TAG, "showPictureInPictureMenu event=" + event);
mHandler.removeMessages(MSG_SHOW_PICTURE_IN_PICTURE_MENU);
@@ -1705,14 +1716,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
launchAssistAction(null, deviceId);
break;
case LONG_PRESS_HOME_NOTIFICATION_PANEL:
- IStatusBarService statusBarService = getStatusBarService();
- if (statusBarService != null) {
- try {
- statusBarService.togglePanel();
- } catch (RemoteException e) {
- // do nothing.
- }
- }
+ toggleNotificationPanel();
break;
default:
Log.w(TAG, "Undefined long press on home behavior: "
@@ -2816,6 +2820,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
msg.sendToTarget();
}
return -1;
+ } else if (keyCode == KeyEvent.KEYCODE_NOTIFICATION) {
+ if (!down) {
+ toggleNotificationPanel();
+ }
+ return -1;
}
// Toggle Caps Lock on META-ALT.
@@ -4507,7 +4516,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Called on the DisplayManager's DisplayPowerController thread.
@Override
- public void screenTurnedOff() {
+ public void screenTurnedOff(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+
if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off...");
updateScreenOffSleepToken(true);
@@ -4530,7 +4543,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Called on the DisplayManager's DisplayPowerController thread.
@Override
- public void screenTurningOn(final ScreenOnListener screenOnListener) {
+ public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+
if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turning on...");
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */);
@@ -4553,7 +4570,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Called on the DisplayManager's DisplayPowerController thread.
@Override
- public void screenTurnedOn() {
+ public void screenTurnedOn(int displayId) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+
synchronized (mLock) {
if (mKeyguardDelegate != null) {
mKeyguardDelegate.onScreenTurnedOn();
@@ -4563,7 +4584,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public void screenTurningOff(ScreenOffListener screenOffListener) {
+ public void screenTurningOff(int displayId, ScreenOffListener screenOffListener) {
+ if (displayId != DEFAULT_DISPLAY) {
+ return;
+ }
+
mWindowManagerFuncs.screenTurningOff(screenOffListener);
synchronized (mLock) {
if (mKeyguardDelegate != null) {
@@ -4825,8 +4850,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
startedWakingUp(ON_BECAUSE_OF_UNKNOWN);
finishedWakingUp(ON_BECAUSE_OF_UNKNOWN);
- screenTurningOn(null);
- screenTurnedOn();
+ screenTurningOn(DEFAULT_DISPLAY, null);
+ screenTurnedOn(DEFAULT_DISPLAY);
}
@Override
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b96d65cb7433..0d8d3470ec65 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -831,7 +831,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
public void finishedGoingToSleep(int why);
/**
- * Called when the device is about to turn on the screen to show content.
+ * Called when the display is about to turn on to show content.
* When waking up, this method will be called once after the call to wakingUp().
* When dozing, the method will be called sometime after the call to goingToSleep() and
* may be called repeatedly in the case where the screen is pulsing on and off.
@@ -839,13 +839,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* Must call back on the listener to tell it when the higher-level system
* is ready for the screen to go on (i.e. the lock screen is shown).
*/
- public void screenTurningOn(ScreenOnListener screenOnListener);
+ public void screenTurningOn(int displayId, ScreenOnListener screenOnListener);
/**
- * Called when the device has actually turned on the screen, i.e. the display power state has
- * been set to ON and the screen is unblocked.
+ * Called when the display has actually turned on, i.e. the display power state has been set to
+ * ON and the screen is unblocked.
*/
- public void screenTurnedOn();
+ public void screenTurnedOn(int displayId);
/**
* Called when the display would like to be turned off. This gives policy a chance to do some
@@ -854,12 +854,12 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @param screenOffListener Must be called to tell that the display power state can actually be
* changed now after policy has done its work.
*/
- public void screenTurningOff(ScreenOffListener screenOffListener);
+ public void screenTurningOff(int displayId, ScreenOffListener screenOffListener);
/**
- * Called when the device has turned the screen off.
+ * Called when the display has turned off.
*/
- public void screenTurnedOff();
+ public void screenTurnedOff(int displayId);
public interface ScreenOnListener {
void onScreenOn();
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index cbdfc56d6aa4..06edd19a376a 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -18,14 +18,12 @@ package com.android.server.power;
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
-import android.Manifest;
import android.app.ActivityManager;
import android.app.SynchronousUserSwitchObserver;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.PowerManager;
@@ -124,9 +122,6 @@ public class AttentionDetector {
protected WindowManagerInternal mWindowManager;
@VisibleForTesting
- protected PackageManager mPackageManager;
-
- @VisibleForTesting
protected ContentResolver mContentResolver;
/**
@@ -164,7 +159,6 @@ public class AttentionDetector {
public void systemReady(Context context) {
mContext = context;
updateEnabledFromSettings(context);
- mPackageManager = context.getPackageManager();
mContentResolver = context.getContentResolver();
mAttentionManager = LocalServices.getService(AttentionManagerInternal.class);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
@@ -192,14 +186,11 @@ public class AttentionDetector {
public long updateUserActivity(long nextScreenDimming, long dimDurationMillis) {
if (nextScreenDimming == mLastActedOnNextScreenDimming
|| !mIsSettingEnabled
+ || !isAttentionServiceSupported()
|| mWindowManager.isKeyguardShowingAndNotOccluded()) {
return nextScreenDimming;
}
- if (!isAttentionServiceSupported() || !serviceHasSufficientPermissions()) {
- return nextScreenDimming;
- }
-
final long now = SystemClock.uptimeMillis();
final long whenToCheck = nextScreenDimming - getPreDimCheckDurationMillis();
final long whenToStopExtending = mLastUserActivityTime + getMaxExtensionMillis();
@@ -300,18 +291,6 @@ public class AttentionDetector {
return mAttentionManager != null && mAttentionManager.isAttentionServiceSupported();
}
- /**
- * Returns {@code true} if the attention service has sufficient permissions, disables the
- * depending features otherwise.
- */
- @VisibleForTesting
- boolean serviceHasSufficientPermissions() {
- final String attentionPackage = mPackageManager.getAttentionServicePackageName();
- return attentionPackage != null && mPackageManager.checkPermission(
- Manifest.permission.CAMERA, attentionPackage)
- == PackageManager.PERMISSION_GRANTED;
- }
-
public void dump(PrintWriter pw) {
pw.println("AttentionDetector:");
pw.println(" mIsSettingEnabled=" + mIsSettingEnabled);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6044ee18614a..60da8e5c7b70 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5408,6 +5408,22 @@ public final class PowerManagerService extends SystemService
}
@Override // Binder call
+ public boolean isAmbientDisplaySuppressedForTokenByApp(@NonNull String token, int appUid) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_DREAM_STATE, null);
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_DREAM_SUPPRESSION, null);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isAmbientDisplayAvailable()
+ && mAmbientDisplaySuppressionController.isSuppressed(token, appUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public boolean isAmbientDisplaySuppressed() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_DREAM_STATE, null);
diff --git a/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING
new file mode 100644
index 000000000000..17dba7df3f1f
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsLocationCoarseTestCases"
+ },
+ {
+ "name": "CtsLocationFineTestCases"
+ },
+ {
+ "name": "CtsLocationNoneTestCases"
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.location"}
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index fd609c14276c..81883f3012e9 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -16,6 +16,7 @@
package com.android.server.powerstats;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.Environment;
@@ -48,8 +49,11 @@ public class PowerStatsService extends SystemService {
private Context mContext;
private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+ @Nullable
private PowerStatsLogger mPowerStatsLogger;
+ @Nullable
private BatteryTrigger mBatteryTrigger;
+ @Nullable
private TimerTrigger mTimerTrigger;
@VisibleForTesting
@@ -88,7 +92,11 @@ public class PowerStatsService extends SystemService {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
if (args.length > 0 && "--proto".equals(args[0])) {
- mPowerStatsLogger.writeToFile(fd);
+ if (mPowerStatsLogger == null) {
+ Log.e(TAG, "PowerStats HAL is not initialized. No data available.");
+ } else {
+ mPowerStatsLogger.writeToFile(fd);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java
index a291cef5b39e..e7b1b4e4a687 100644
--- a/services/core/java/com/android/server/role/RoleManagerService.java
+++ b/services/core/java/com/android/server/role/RoleManagerService.java
@@ -660,7 +660,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
@Override
public String getDefaultSmsPackage(int userId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return CollectionUtils.firstOrNull(
getRoleHoldersAsUser(RoleManager.ROLE_SMS, userId));
@@ -699,7 +699,7 @@ public class RoleManagerService extends SystemService implements RoleUserState.C
}
private int getUidForPackage(String packageName) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return getContext().getPackageManager().getApplicationInfo(packageName,
PackageManager.MATCH_ANY_USER).uid;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index c07b94d649eb..ba1401d7469e 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -27,7 +27,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
@@ -44,13 +43,11 @@ import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
-import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.pm.ApexManager;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.BufferedReader;
import java.io.File;
@@ -342,14 +339,10 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
private boolean isModule(String packageName) {
// Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
// querying PackageManager.
- PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- AndroidPackage apkPackage = pmi.getPackage(packageName);
- if (apkPackage != null) {
- String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
- apkPackage);
- if (apexPackageName != null) {
- packageName = apexPackageName;
- }
+ String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+ packageName);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
}
PackageManager pm = mContext.getPackageManager();
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index a4fa745c4896..44a63367dab5 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -25,6 +25,7 @@ import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.content.rollback.RollbackInfo;
import android.os.UserHandle;
+import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -37,6 +38,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
@@ -66,8 +68,6 @@ class RollbackStore {
//
// * XXX, YYY are the rollbackIds for the corresponding rollbacks.
// * rollback.json contains all relevant metadata for the rollback.
- //
- // TODO: Use AtomicFile for all the .json files?
private final File mRollbackDataDir;
RollbackStore(File rollbackDataDir) {
@@ -259,6 +259,8 @@ class RollbackStore {
* Saves the given rollback to persistent storage.
*/
static void saveRollback(Rollback rollback) {
+ FileOutputStream fos = null;
+ AtomicFile file = new AtomicFile(new File(rollback.getBackupDir(), "rollback.json"));
try {
JSONObject dataJson = new JSONObject();
dataJson.put("info", rollbackInfoToJson(rollback.info));
@@ -272,11 +274,16 @@ class RollbackStore {
dataJson.putOpt(
"extensionVersions", extensionVersionsToJson(rollback.getExtensionVersions()));
- PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
+ fos = file.startWrite();
+ PrintWriter pw = new PrintWriter(fos);
pw.println(dataJson.toString());
pw.close();
+ file.finishWrite(fos);
} catch (JSONException | IOException e) {
Slog.e(TAG, "Unable to save rollback for: " + rollback.info.getRollbackId(), e);
+ if (fos != null) {
+ file.failWrite(fos);
+ }
}
}
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index ca7f0366df60..6e1e979b1bac 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -234,7 +234,7 @@ public class Searchables {
List<ResolveInfo> searchList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
searchList = queryIntentActivities(intent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index 2a74b3d23829..ee9694f90216 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -281,7 +281,7 @@ public class SliceManagerService extends ISliceManager.Stub {
String providerPkg = getProviderPkg(grantUri, providerUser);
mPermissions.grantSliceAccess(pkg, userId, providerPkg, providerUser, grantUri);
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mContext.getContentResolver().notifyChange(uri, null);
} finally {
@@ -402,7 +402,7 @@ public class SliceManagerService extends ISliceManager.Stub {
}
private String getProviderPkg(Uri uri, int user) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
String providerName = getUriWithoutUserId(uri).getAuthority();
ProviderInfo provider = mContext.getPackageManager().resolveContentProviderAsUser(
@@ -438,7 +438,7 @@ public class SliceManagerService extends ISliceManager.Stub {
}
private boolean hasFullSliceAccess(String pkg, int userId) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
boolean ret = isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
|| isGrantedFullAccess(pkg, userId);
diff --git a/services/core/java/com/android/server/slice/SliceShellCommand.java b/services/core/java/com/android/server/slice/SliceShellCommand.java
index 9137a3bcbf2b..bdc8bbd13509 100644
--- a/services/core/java/com/android/server/slice/SliceShellCommand.java
+++ b/services/core/java/com/android/server/slice/SliceShellCommand.java
@@ -69,7 +69,7 @@ public class SliceShellCommand extends ShellCommand {
return -1;
}
Context context = mService.getContext();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 1d31285ed9c7..de8823c4b7f3 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -40,7 +40,10 @@ import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundModelType;
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
import android.os.HidlMemoryUtil;
+import android.os.ParcelFileDescriptor;
+import java.io.FileDescriptor;
+import java.io.IOException;
import java.util.regex.Matcher;
/**
@@ -196,8 +199,18 @@ class ConversionUtil {
hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
- hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(aidlModel.data,
- aidlModel.dataSize);
+
+ // Extract a dup of the underlying FileDescriptor out of aidlModel.data without changing
+ // the original.
+ FileDescriptor fd = new FileDescriptor();
+ try {
+ ParcelFileDescriptor dup = aidlModel.data.dup();
+ fd.setInt$(dup.detachFd());
+ hidlModel.data = HidlMemoryUtil.fileDescriptorToHidlMemory(fd, aidlModel.dataSize);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
return hidlModel;
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
index 5def7621c148..a90053a23dea 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
@@ -17,11 +17,28 @@
package com.android.server.soundtrigger_middleware;
import android.media.ICaptureStateListener;
-import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
/**
- * This interface unifies ISoundTriggerMiddlewareService with ICaptureStateListener.
+ * This interface unifies methods from ISoundTriggerMiddlewareService and ICaptureStateListener.
+ *
+ * The ISoundTriggerMiddlewareService have been modified to exclude identity information and the
+ * RemoteException signature, both of which are only relevant at the service boundary layer.
*/
-public interface ISoundTriggerMiddlewareInternal extends ISoundTriggerMiddlewareService,
- ICaptureStateListener {
+public interface ISoundTriggerMiddlewareInternal extends ICaptureStateListener {
+ /**
+ * Query the available modules and their capabilities.
+ */
+ public SoundTriggerModuleDescriptor[] listModules();
+
+ /**
+ * Attach to one of the available modules.
+ *
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ public ISoundTriggerModule attach(int handle,
+ ISoundTriggerCallback callback);
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 761858ccd238..eced8940947a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -21,6 +21,7 @@ import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
import android.hardware.soundtrigger.V2_3.ModelParameterRange;
import android.hardware.soundtrigger.V2_3.Properties;
import android.hardware.soundtrigger.V2_3.RecognitionConfig;
+import android.media.soundtrigger_middleware.Status;
import android.os.DeadObjectException;
import android.os.IHwBinder;
import android.os.RemoteException;
@@ -195,10 +196,10 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 {
if (e.getCause() instanceof DeadObjectException) {
// Server is dead, no need to reboot.
Log.e(TAG, "HAL died");
- } else {
- Log.e(TAG, "Exception caught from HAL, rebooting HAL");
- rebootHal();
+ throw new RecoverableException(Status.DEAD_OBJECT);
}
+ Log.e(TAG, "Exception caught from HAL, rebooting HAL");
+ rebootHal();
throw e;
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index 8b6ed1ff5081..2ef0759719fc 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -18,8 +18,9 @@ package com.android.server.soundtrigger_middleware;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
-import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
import android.media.soundtrigger_middleware.ModelParameterRange;
import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
@@ -28,17 +29,15 @@ import android.media.soundtrigger_middleware.RecognitionConfig;
import android.media.soundtrigger_middleware.RecognitionEvent;
import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
-import android.os.Binder;
import android.os.IBinder;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
-import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
+import java.util.Objects;
/**
* An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
@@ -71,7 +70,8 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
}
@Override
- public @NonNull SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
try {
SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
logReturn("listModules", result);
@@ -83,12 +83,13 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
}
@Override
- public @NonNull ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback)
- throws RemoteException {
+ public @NonNull
+ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback) {
try {
- ISoundTriggerModule result = mDelegate.attach(handle, new CallbackLogging(callback));
+ ModuleLogging result = new ModuleLogging(callback);
+ result.attach(mDelegate.attach(handle, result.getCallbackWrapper()));
logReturn("attach", result, handle, callback);
- return new ModuleLogging(result);
+ return result;
} catch (Exception e) {
logException("attach", e, handle, callback);
throw e;
@@ -106,7 +107,8 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
}
}
- @Override public IBinder asBinder() {
+ @Override
+ public IBinder asBinder() {
throw new UnsupportedOperationException(
"This implementation is not inteded to be used directly with Binder.");
}
@@ -118,94 +120,33 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
}
private void logException(String methodName, Exception ex, Object... args) {
- logExceptionWithObject(this, methodName, ex, args);
+ logExceptionWithObject(this, IdentityContext.get(), methodName, ex, args);
}
private void logReturn(String methodName, Object retVal, Object... args) {
- logReturnWithObject(this, methodName, retVal, args);
+ logReturnWithObject(this, IdentityContext.get(), methodName, retVal, args);
}
private void logVoidReturn(String methodName, Object... args) {
- logVoidReturnWithObject(this, methodName, args);
+ logVoidReturnWithObject(this, IdentityContext.get(), methodName, args);
}
- private class CallbackLogging implements ISoundTriggerCallback {
- private final ISoundTriggerCallback mDelegate;
-
- private CallbackLogging(ISoundTriggerCallback delegate) {
- mDelegate = delegate;
- }
-
- @Override
- public void onRecognition(int modelHandle, RecognitionEvent event) throws RemoteException {
- try {
- mDelegate.onRecognition(modelHandle, event);
- logVoidReturn("onRecognition", modelHandle, event);
- } catch (Exception e) {
- logException("onRecognition", e, modelHandle, event);
- throw e;
- }
- }
-
- @Override
- public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
- throws RemoteException {
- try {
- mDelegate.onPhraseRecognition(modelHandle, event);
- logVoidReturn("onPhraseRecognition", modelHandle, event);
- } catch (Exception e) {
- logException("onPhraseRecognition", e, modelHandle, event);
- throw e;
- }
- }
-
- @Override
- public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
- try {
- mDelegate.onRecognitionAvailabilityChange(available);
- logVoidReturn("onRecognitionAvailabilityChange", available);
- } catch (Exception e) {
- logException("onRecognitionAvailabilityChange", e, available);
- throw e;
- }
- }
-
- @Override
- public void onModuleDied() throws RemoteException {
- try {
- mDelegate.onModuleDied();
- logVoidReturn("onModuleDied");
- } catch (Exception e) {
- logException("onModuleDied", e);
- throw e;
- }
- }
-
- private void logException(String methodName, Exception ex, Object... args) {
- logExceptionWithObject(this, methodName, ex, args);
- }
+ private class ModuleLogging implements ISoundTriggerModule {
+ private ISoundTriggerModule mDelegate;
+ private final @NonNull CallbackLogging mCallbackWrapper;
+ private final @NonNull Identity mOriginatorIdentity;
- private void logVoidReturn(String methodName, Object... args) {
- logVoidReturnWithObject(this, methodName, args);
+ ModuleLogging(@NonNull ISoundTriggerCallback callback) {
+ mCallbackWrapper = new CallbackLogging(callback);
+ mOriginatorIdentity = IdentityContext.getNonNull();
}
- @Override
- public IBinder asBinder() {
- return mDelegate.asBinder();
- }
-
- // Override toString() in order to have the delegate's ID in it.
- @Override
- public String toString() {
- return mDelegate.toString();
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
}
- }
-
- private class ModuleLogging implements ISoundTriggerModule {
- private final ISoundTriggerModule mDelegate;
- private ModuleLogging(ISoundTriggerModule delegate) {
- mDelegate = delegate;
+ ISoundTriggerCallback getCallbackWrapper() {
+ return mCallbackWrapper;
}
@Override
@@ -334,19 +275,92 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
// Override toString() in order to have the delegate's ID in it.
@Override
public String toString() {
- return mDelegate.toString();
+ return Objects.toString(mDelegate);
}
private void logException(String methodName, Exception ex, Object... args) {
- logExceptionWithObject(this, methodName, ex, args);
+ logExceptionWithObject(this, mOriginatorIdentity, methodName, ex, args);
}
private void logReturn(String methodName, Object retVal, Object... args) {
- logReturnWithObject(this, methodName, retVal, args);
+ logReturnWithObject(this, mOriginatorIdentity, methodName, retVal, args);
}
private void logVoidReturn(String methodName, Object... args) {
- logVoidReturnWithObject(this, methodName, args);
+ logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
+ }
+
+ private class CallbackLogging implements ISoundTriggerCallback {
+ private final ISoundTriggerCallback mCallbackDelegate;
+
+ private CallbackLogging(ISoundTriggerCallback delegate) {
+ mCallbackDelegate = delegate;
+ }
+
+ @Override
+ public void onRecognition(int modelHandle, RecognitionEvent event)
+ throws RemoteException {
+ try {
+ mCallbackDelegate.onRecognition(modelHandle, event);
+ logVoidReturn("onRecognition", modelHandle, event);
+ } catch (Exception e) {
+ logException("onRecognition", e, modelHandle, event);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ try {
+ mCallbackDelegate.onPhraseRecognition(modelHandle, event);
+ logVoidReturn("onPhraseRecognition", modelHandle, event);
+ } catch (Exception e) {
+ logException("onPhraseRecognition", e, modelHandle, event);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ try {
+ mCallbackDelegate.onRecognitionAvailabilityChange(available);
+ logVoidReturn("onRecognitionAvailabilityChange", available);
+ } catch (Exception e) {
+ logException("onRecognitionAvailabilityChange", e, available);
+ throw e;
+ }
+ }
+
+ @Override
+ public void onModuleDied() throws RemoteException {
+ try {
+ mCallbackDelegate.onModuleDied();
+ logVoidReturn("onModuleDied");
+ } catch (Exception e) {
+ logException("onModuleDied", e);
+ throw e;
+ }
+ }
+
+ private void logException(String methodName, Exception ex, Object... args) {
+ logExceptionWithObject(this, mOriginatorIdentity, methodName, ex, args);
+ }
+
+ private void logVoidReturn(String methodName, Object... args) {
+ logVoidReturnWithObject(this, mOriginatorIdentity, methodName, args);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mCallbackDelegate.asBinder();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return Objects.toString(mCallbackDelegate);
+ }
}
}
@@ -386,34 +400,37 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
return builder.toString();
}
- private void logReturnWithObject(@NonNull Object object, String methodName,
+ private void logReturnWithObject(@NonNull Object object, @Nullable Identity originatorIdentity,
+ String methodName,
@Nullable Object retVal,
@NonNull Object[] args) {
- final String message = String.format("%s[this=%s, caller=%d/%d](%s) -> %s", methodName,
+ final String message = String.format("%s[this=%s, client=%s](%s) -> %s", methodName,
object,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ printObject(originatorIdentity),
printArgs(args),
printObject(retVal));
Log.i(TAG, message);
appendMessage(message);
}
- private void logVoidReturnWithObject(@NonNull Object object, @NonNull String methodName,
+ private void logVoidReturnWithObject(@NonNull Object object,
+ @Nullable Identity originatorIdentity, @NonNull String methodName,
@NonNull Object[] args) {
- final String message = String.format("%s[this=%s, caller=%d/%d](%s)", methodName,
+ final String message = String.format("%s[this=%s, client=%s](%s)", methodName,
object,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ printObject(originatorIdentity),
printArgs(args));
Log.i(TAG, message);
appendMessage(message);
}
- private void logExceptionWithObject(@NonNull Object object, @NonNull String methodName,
+ private void logExceptionWithObject(@NonNull Object object,
+ @Nullable Identity originatorIdentity, @NonNull String methodName,
@NonNull Exception ex,
Object[] args) {
- final String message = String.format("%s[this=%s, caller=%d/%d](%s) threw", methodName,
+ final String message = String.format("%s[this=%s, client=%s](%s) threw", methodName,
object,
- Binder.getCallingUid(), Binder.getCallingPid(),
+ printObject(originatorIdentity),
printArgs(args));
Log.e(TAG, message, ex);
appendMessage(message + " " + ex.toString());
@@ -429,7 +446,8 @@ public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInt
}
}
- @Override public void dump(PrintWriter pw) {
+ @Override
+ public void dump(PrintWriter pw) {
pw.println();
pw.println("=========================================");
pw.println("Last events");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
new file mode 100644
index 000000000000..51b00faa28d2
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -0,0 +1,351 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.RECORD_AUDIO;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.permission.PermissionUtil;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces permissions.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow a similar
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * enforcePermissions*(...);
+ * return mDelegate.method(arg);
+ * }
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddlewareInternal, Dumpable {
+ private static final String TAG = "SoundTriggerMiddlewarePermission";
+
+ private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
+ private final @NonNull Context mContext;
+
+ public SoundTriggerMiddlewarePermission(
+ @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ Identity identity = getIdentity();
+ enforcePermissionsForPreflight(identity);
+ return mDelegate.listModules();
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle,
+ @NonNull ISoundTriggerCallback callback) {
+ Identity identity = getIdentity();
+ enforcePermissionsForPreflight(identity);
+ ModuleWrapper wrapper = new ModuleWrapper(identity, callback);
+ return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper()));
+ }
+
+ @Override
+ public void setCaptureState(boolean active) throws RemoteException {
+ // This is an internal call. No permissions needed.
+ mDelegate.setCaptureState(active);
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return Objects.toString(mDelegate);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+
+ /**
+ * Get the identity context, or throws an InternalServerError if it has not been established.
+ *
+ * @return The identity.
+ */
+ private static @NonNull
+ Identity getIdentity() {
+ return IdentityContext.getNonNull();
+ }
+
+ /**
+ * Throws a {@link SecurityException} if originator permanently doesn't have the given
+ * permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * originator temporarily doesn't have the right permissions to use this service.
+ */
+ private void enforcePermissionsForPreflight(@NonNull Identity identity) {
+ enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO);
+ enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD);
+ }
+
+ /**
+ * Throws a {@link SecurityException} iff the originator has permission to receive data.
+ */
+ void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) {
+ enforcePermissionForDataDelivery(mContext, identity, RECORD_AUDIO, reason);
+ enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD,
+ reason);
+ }
+
+ /**
+ * Throws a {@link SecurityException} iff the given identity has given permission to receive
+ * data.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ * @param reason The reason why we're requesting the permission, for auditing purposes.
+ */
+ private static void enforcePermissionForDataDelivery(@NonNull Context context,
+ @NonNull Identity identity,
+ @NonNull String permission, @NonNull String reason) {
+ // TODO(ytai): We're temporarily ignoring proc state until we have a proper permission that
+ // represents being able to use the microphone in the background. Otherwise, some of our
+ // existing use-cases would break.
+ final int status = PermissionUtil.checkPermissionForDataDeliveryIgnoreProcState(context,
+ identity, permission, reason);
+ if (status != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ String.format("Failed to obtain permission %s for identity %s", permission,
+ ObjectPrinter.print(identity, true, 16)));
+ }
+ }
+
+ /**
+ * Throws a {@link SecurityException} if originator permanently doesn't have the given
+ * permission, or a {@link ServiceSpecificException} with a {@link
+ * Status#TEMPORARY_PERMISSION_DENIED} if caller originator doesn't have the given permission.
+ *
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ */
+ private static void enforcePermissionForPreflight(@NonNull Context context,
+ @NonNull Identity identity, @NonNull String permission) {
+ final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
+ permission);
+ switch (status) {
+ case PermissionChecker.PERMISSION_GRANTED:
+ return;
+ case PermissionChecker.PERMISSION_HARD_DENIED:
+ throw new SecurityException(
+ String.format("Failed to obtain permission %s for identity %s", permission,
+ ObjectPrinter.print(identity, true, 16)));
+ case PermissionChecker.PERMISSION_SOFT_DENIED:
+ throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+ String.format("Failed to obtain permission %s for identity %s", permission,
+ ObjectPrinter.print(identity, true, 16)));
+ default:
+ throw new RuntimeException("Unexpected perimission check result.");
+ }
+ }
+
+
+ @Override
+ public void dump(PrintWriter pw) {
+ if (mDelegate instanceof Dumpable) {
+ ((Dumpable) mDelegate).dump(pw);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleWrapper extends ISoundTriggerModule.Stub {
+ private ISoundTriggerModule mDelegate;
+ private final @NonNull Identity mOriginatorIdentity;
+ private final @NonNull CallbackWrapper mCallbackWrapper;
+
+ ModuleWrapper(@NonNull Identity originatorIdentity,
+ @NonNull ISoundTriggerCallback callback) {
+ mOriginatorIdentity = originatorIdentity;
+ mCallbackWrapper = new CallbackWrapper(callback);
+ }
+
+ ModuleWrapper attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ return this;
+ }
+
+ ISoundTriggerCallback getCallbackWrapper() {
+ return mCallbackWrapper;
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) throws RemoteException {
+ enforcePermissions();
+ return mDelegate.loadModel(model);
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) throws RemoteException {
+ enforcePermissions();
+ return mDelegate.loadPhraseModel(model);
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) throws RemoteException {
+ // Unloading a model does not require special permissions. Having a handle to the
+ // session is sufficient.
+ mDelegate.unloadModel(modelHandle);
+
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config)
+ throws RemoteException {
+ enforcePermissions();
+ mDelegate.startRecognition(modelHandle, config);
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) throws RemoteException {
+ // Stopping a model does not require special permissions. Having a handle to the
+ // session is sufficient.
+ mDelegate.stopRecognition(modelHandle);
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) throws RemoteException {
+ enforcePermissions();
+ mDelegate.forceRecognitionEvent(modelHandle);
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ enforcePermissions();
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ enforcePermissions();
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
+ throws RemoteException {
+ enforcePermissions();
+ return mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ }
+
+ @Override
+ public void detach() throws RemoteException {
+ // Detaching does not require special permissions. Having a handle to the session is
+ // sufficient.
+ mDelegate.detach();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return Objects.toString(mDelegate);
+ }
+
+ private void enforcePermissions() {
+ enforcePermissionsForPreflight(mOriginatorIdentity);
+ }
+
+ private class CallbackWrapper implements ISoundTriggerCallback {
+ private final ISoundTriggerCallback mDelegate;
+
+ private CallbackWrapper(ISoundTriggerCallback delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void onRecognition(int modelHandle, RecognitionEvent event)
+ throws RemoteException {
+ enforcePermissions("Sound trigger recognition.");
+ mDelegate.onRecognition(modelHandle, event);
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ enforcePermissions("Sound trigger phrase recognition.");
+ mDelegate.onPhraseRecognition(modelHandle, event);
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ mDelegate.onRecognitionAvailabilityChange(available);
+ }
+
+ @Override
+ public void onModuleDied() throws RemoteException {
+ mDelegate.onModuleDied();
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mDelegate.asBinder();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+
+ private void enforcePermissions(String reason) {
+ enforcePermissionsForDataDelivery(mOriginatorIdentity, reason);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 4b464d2d3e04..db7a575b08e2 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -16,9 +16,15 @@
package com.android.server.soundtrigger_middleware;
+import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
+
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.Identity;
+import android.media.permission.PermissionUtil;
+import android.media.permission.SafeCloseable;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -62,15 +68,17 @@ import java.util.Objects;
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
static private final String TAG = "SoundTriggerMiddlewareService";
- @NonNull
- private final ISoundTriggerMiddlewareInternal mDelegate;
+ private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
+ private final @NonNull Context mContext;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
* Users should access this class via {@link Lifecycle}.
*/
- private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate) {
+ private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate,
+ @NonNull Context context) {
mDelegate = Objects.requireNonNull(delegate);
+ mContext = context;
new ExternalCaptureStateTracker(active -> {
try {
mDelegate.setCaptureState(active);
@@ -81,24 +89,58 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic
}
@Override
- public @NonNull
- SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
- return mDelegate.listModules();
+ public SoundTriggerModuleDescriptor[] listModulesAsOriginator(Identity identity) {
+ try (SafeCloseable ignored = establishIdentityDirect(identity)) {
+ return mDelegate.listModules();
+ }
}
@Override
- public @NonNull
- ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback)
- throws RemoteException {
- return new ModuleService(mDelegate.attach(handle, callback));
+ public SoundTriggerModuleDescriptor[] listModulesAsMiddleman(Identity middlemanIdentity,
+ Identity originatorIdentity) {
+ try (SafeCloseable ignored = establishIdentityIndirect(middlemanIdentity,
+ originatorIdentity)) {
+ return mDelegate.listModules();
+ }
+ }
+
+ @Override
+ public ISoundTriggerModule attachAsOriginator(int handle, Identity identity,
+ ISoundTriggerCallback callback) {
+ try (SafeCloseable ignored = establishIdentityDirect(Objects.requireNonNull(identity))) {
+ return new ModuleService(mDelegate.attach(handle, callback));
+ }
}
- @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ @Override
+ public ISoundTriggerModule attachAsMiddleman(int handle, Identity middlemanIdentity,
+ Identity originatorIdentity, ISoundTriggerCallback callback) {
+ try (SafeCloseable ignored = establishIdentityIndirect(
+ Objects.requireNonNull(middlemanIdentity),
+ Objects.requireNonNull(originatorIdentity))) {
+ return new ModuleService(mDelegate.attach(handle, callback));
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
if (mDelegate instanceof Dumpable) {
((Dumpable) mDelegate).dump(fout);
}
}
+ private @NonNull
+ SafeCloseable establishIdentityIndirect(Identity middlemanIdentity,
+ Identity originatorIdentity) {
+ return PermissionUtil.establishIdentityIndirect(mContext, SOUNDTRIGGER_DELEGATE_IDENTITY,
+ middlemanIdentity, originatorIdentity);
+ }
+
+ private @NonNull
+ SafeCloseable establishIdentityDirect(Identity originatorIdentity) {
+ return PermissionUtil.establishIdentityDirect(originatorIdentity);
+ }
+
private final static class ModuleService extends ISoundTriggerModule.Stub {
private final ISoundTriggerModule mDelegate;
@@ -108,55 +150,75 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic
@Override
public int loadModel(SoundModel model) throws RemoteException {
- return mDelegate.loadModel(model);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return mDelegate.loadModel(model);
+ }
}
@Override
public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
- return mDelegate.loadPhraseModel(model);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return mDelegate.loadPhraseModel(model);
+ }
}
@Override
public void unloadModel(int modelHandle) throws RemoteException {
- mDelegate.unloadModel(modelHandle);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.unloadModel(modelHandle);
+ }
}
@Override
public void startRecognition(int modelHandle, RecognitionConfig config)
throws RemoteException {
- mDelegate.startRecognition(modelHandle, config);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.startRecognition(modelHandle, config);
+ }
}
@Override
public void stopRecognition(int modelHandle) throws RemoteException {
- mDelegate.stopRecognition(modelHandle);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.stopRecognition(modelHandle);
+ }
}
@Override
public void forceRecognitionEvent(int modelHandle) throws RemoteException {
- mDelegate.forceRecognitionEvent(modelHandle);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ }
}
@Override
public void setModelParameter(int modelHandle, int modelParam, int value)
throws RemoteException {
- mDelegate.setModelParameter(modelHandle, modelParam, value);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ }
}
@Override
public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
- return mDelegate.getModelParameter(modelHandle, modelParam);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ }
}
@Override
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
throws RemoteException {
- return mDelegate.queryModelParameterSupport(modelHandle, modelParam);
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ return mDelegate.queryModelParameterSupport(modelHandle, modelParam);
+ }
}
@Override
public void detach() throws RemoteException {
- mDelegate.detach();
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ mDelegate.detach();
+ }
}
}
@@ -182,10 +244,11 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic
publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
new SoundTriggerMiddlewareService(
new SoundTriggerMiddlewareLogging(
- new SoundTriggerMiddlewareValidation(
- new SoundTriggerMiddlewareImpl(factories,
- new AudioSessionProviderImpl()),
- getContext()))));
+ new SoundTriggerMiddlewarePermission(
+ new SoundTriggerMiddlewareValidation(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl())),
+ getContext())), getContext()));
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 5d25d2cb554d..95a30c7f0278 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -16,11 +16,10 @@
package com.android.server.soundtrigger_middleware;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
-import android.content.PermissionChecker;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -53,9 +52,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
- * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces permissions and
- * correct usage by the client, as well as makes sure that exceptions representing a server
- * malfunction do not get sent to the client.
+ * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces correct usage by
+ * the client, as well as makes sure that exceptions representing a server malfunction get sent to
+ * the client in a consistent manner, which cannot be confused with a client fault.
* <p>
* This is intended to extract the non-business logic out of the underlying implementation and thus
* make it easier to maintain each one of those separate aspects. A design trade-off is being made
@@ -70,8 +69,6 @@ import java.util.concurrent.atomic.AtomicReference;
* pattern:
* <code><pre>
* @Override public T method(S arg) {
- * // Permission check.
- * checkPermissions();
* // Input validation.
* ValidationUtil.validateS(arg);
* synchronized (this) {
@@ -95,9 +92,8 @@ import java.util.concurrent.atomic.AtomicReference;
* with client-server separation.
* <p>
* <b>Exception handling approach:</b><br>
- * We make sure all client faults (permissions, argument and state validation) happen first, and
- * would throw {@link SecurityException}, {@link IllegalArgumentException}/
- * {@link NullPointerException} or {@link
+ * We make sure all client faults (argument and state validation) happen first, and
+ * would throw {@link IllegalArgumentException}/{@link NullPointerException} or {@link
* IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
* will get sent back to the client.<br>
* Once this is done, any subsequent fault is considered either a recoverable (expected) or
@@ -116,11 +112,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
ALIVE,
DETACHED,
DEAD
- };
+ }
private class ModuleState {
final @NonNull SoundTriggerModuleProperties properties;
- Set<ModuleService> sessions = new HashSet<>();
+ Set<Session> sessions = new HashSet<>();
private ModuleState(@NonNull SoundTriggerModuleProperties properties) {
this.properties = properties;
@@ -130,13 +126,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
private AtomicReference<Boolean> mCaptureState = new AtomicReference<>();
private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
- private final @NonNull Context mContext;
private Map<Integer, ModuleState> mModules;
public SoundTriggerMiddlewareValidation(
- @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) {
+ @NonNull ISoundTriggerMiddlewareInternal delegate) {
mDelegate = delegate;
- mContext = context;
}
/**
@@ -169,8 +163,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public @NonNull
SoundTriggerModuleDescriptor[] listModules() {
- // Permission check.
- checkPermissions();
// Input validation (always valid).
synchronized (this) {
@@ -179,9 +171,22 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// From here on, every exception isn't client's fault.
try {
SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
- mModules = new HashMap<>(result.length);
- for (SoundTriggerModuleDescriptor desc : result) {
- mModules.put(desc.handle, new ModuleState(desc.properties));
+ if (mModules == null) {
+ mModules = new HashMap<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModules.put(desc.handle, new ModuleState(desc.properties));
+ }
+ } else {
+ if (result.length != mModules.size()) {
+ throw new RuntimeException(
+ "listModules must always return the same result.");
+ }
+ for (SoundTriggerModuleDescriptor desc : result) {
+ if (!mModules.containsKey(desc.handle)) {
+ throw new RuntimeException(
+ "listModules must always return the same result.");
+ }
+ }
}
return result;
} catch (Exception e) {
@@ -193,8 +198,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public @NonNull ISoundTriggerModule attach(int handle,
@NonNull ISoundTriggerCallback callback) {
- // Permission check.
- checkPermissions();
// Input validation.
Objects.requireNonNull(callback);
Objects.requireNonNull(callback.asBinder());
@@ -211,10 +214,9 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// From here on, every exception isn't client's fault.
try {
- ModuleService moduleService =
- new ModuleService(handle, callback);
- moduleService.attach(mDelegate.attach(handle, moduleService));
- return moduleService;
+ Session session = new Session(handle, callback);
+ session.attach(mDelegate.attach(handle, session.getCallbackWrapper()));
+ return session;
} catch (Exception e) {
throw handleException(e);
}
@@ -244,40 +246,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
return mDelegate.toString();
}
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the right permissions to use this service.
- */
- void checkPermissions() {
- enforcePermission(Manifest.permission.RECORD_AUDIO);
- enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
- }
-
- /**
- * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
- * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
- * caller temporarily doesn't have the given permission.
- *
- * @param permission The permission to check.
- */
- void enforcePermission(String permission) {
- final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
- permission);
- switch (status) {
- case PermissionChecker.PERMISSION_GRANTED:
- return;
- case PermissionChecker.PERMISSION_HARD_DENIED:
- throw new SecurityException(
- String.format("Caller must have the %s permission.", permission));
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
- String.format("Caller must have the %s permission.", permission));
- default:
- throw new RuntimeException("Unexpected perimission check result.");
- }
- }
-
@Override
public IBinder asBinder() {
throw new UnsupportedOperationException(
@@ -297,7 +265,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
pw.printf("Module %d\n%s\n", handle,
ObjectPrinter.print(module.properties, true, 16));
pw.println("=========================================");
- for (ModuleService session : module.sessions) {
+ for (Session session : module.sessions) {
session.dump(pw);
}
}
@@ -327,7 +295,14 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
/** Model is loaded, recognition is inactive. */
LOADED,
/** Model is loaded, recognition is active. */
- ACTIVE
+ ACTIVE,
+ /**
+ * Model is active as far as the client is concerned, but loaded as far as the
+ * layers are concerned. This condition occurs when a recognition event that indicates
+ * the recognition for this model arrived from the underlying layer, but had not been
+ * delivered to the caller (most commonly, for permission reasons).
+ */
+ INTERCEPTED,
}
/** Activity state. */
@@ -400,9 +375,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
* A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
* mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
*/
- private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
- IBinder.DeathRecipient {
- private final ISoundTriggerCallback mCallback;
+ private class Session extends ISoundTriggerModule.Stub {
private ISoundTriggerModule mDelegate;
// While generally all the fields of this class must be changed under a lock, an exception
// is made for the specific case of changing a model state from ACTIVE to LOADED, which
@@ -413,15 +386,17 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
ConcurrentMap<Integer, ModelState> mLoadedModels = new ConcurrentHashMap<>();
private final int mHandle;
private ModuleStatus mState = ModuleStatus.ALIVE;
+ private final CallbackWrapper mCallbackWrapper;
+ private final Identity mOriginatorIdentity;
- ModuleService(int handle, @NonNull ISoundTriggerCallback callback) {
- mCallback = callback;
+ Session(int handle, @NonNull ISoundTriggerCallback callback) {
+ mCallbackWrapper = new CallbackWrapper(callback);
mHandle = handle;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ mOriginatorIdentity = IdentityContext.get();
+ }
+
+ ISoundTriggerCallback getCallbackWrapper() {
+ return mCallbackWrapper;
}
void attach(@NonNull ISoundTriggerModule delegate) {
@@ -431,8 +406,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public int loadModel(@NonNull SoundModel model) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validateGenericModel(model);
@@ -455,8 +428,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validatePhraseModel(model);
@@ -479,8 +450,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void unloadModel(int modelHandle) {
- // Permission check.
- checkPermissions();
// Input validation (always valid).
synchronized (SoundTriggerMiddlewareValidation.this) {
@@ -495,7 +464,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
}
if (modelState.getActivityState() != ModelState.Activity.LOADED) {
throw new IllegalStateException("Model with handle: " + modelHandle
- + " has invalid state for unloading: " + modelState.getActivityState());
+ + " has invalid state for unloading");
}
// From here on, every exception isn't client's fault.
@@ -510,8 +479,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validateRecognitionConfig(config);
@@ -527,8 +494,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
}
if (modelState.getActivityState() != ModelState.Activity.LOADED) {
throw new IllegalStateException("Model with handle: " + modelHandle
- + " has invalid state for starting recognition: "
- + modelState.getActivityState());
+ + " has invalid state for starting recognition");
}
// From here on, every exception isn't client's fault.
@@ -547,8 +513,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void stopRecognition(int modelHandle) {
- // Permission check.
- checkPermissions();
// Input validation (always valid).
synchronized (SoundTriggerMiddlewareValidation.this) {
@@ -565,7 +529,12 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// From here on, every exception isn't client's fault.
try {
- mDelegate.stopRecognition(modelHandle);
+ // If the activity state is LOADED or INTERCEPTED, we skip delegating the
+ // command, but still consider the call valid. In either case, the resulting
+ // state is LOADED.
+ if (modelState.getActivityState() == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(modelHandle);
+ }
modelState.setActivityState(ModelState.Activity.LOADED);
} catch (Exception e) {
throw handleException(e);
@@ -575,8 +544,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void forceRecognitionEvent(int modelHandle) {
- // Permission check.
- checkPermissions();
// Input validation (always valid).
synchronized (SoundTriggerMiddlewareValidation.this) {
@@ -593,7 +560,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// From here on, every exception isn't client's fault.
try {
- mDelegate.forceRecognitionEvent(modelHandle);
+ // If the activity state is LOADED or INTERCEPTED, we skip delegating the
+ // command, but still consider the call valid.
+ if (modelState.getActivityState() == ModelState.Activity.ACTIVE) {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -602,8 +573,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void setModelParameter(int modelHandle, int modelParam, int value) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validateModelParameter(modelParam);
@@ -630,8 +599,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public int getModelParameter(int modelHandle, int modelParam) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validateModelParameter(modelParam);
@@ -659,8 +626,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
@Nullable
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
- // Permission check.
- checkPermissions();
// Input validation.
ValidationUtil.validateModelParameter(modelParam);
@@ -689,8 +654,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
@Override
public void detach() {
- // Permission check.
- checkPermissions();
// Input validation (always valid).
synchronized (SoundTriggerMiddlewareValidation.this) {
@@ -714,14 +677,14 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// Override toString() in order to have the delegate's ID in it.
@Override
public String toString() {
- return Objects.toString(mDelegate.toString());
+ return Objects.toString(mDelegate);
}
private void detachInternal() {
try {
mDelegate.detach();
mState = ModuleStatus.DETACHED;
- mCallback.asBinder().unlinkToDeath(this, 0);
+ mCallbackWrapper.detached();
mModules.get(mHandle).sessions.remove(this);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -730,7 +693,10 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
void dump(PrintWriter pw) {
if (mState == ModuleStatus.ALIVE) {
- pw.printf("Loaded models for session %s (handle, active)", toString());
+ pw.println("-------------------------------");
+ pw.printf("Session %s, client: %s\n", toString(),
+ ObjectPrinter.print(mOriginatorIdentity, true, 16));
+ pw.printf("Loaded models (handle, active, description):", toString());
pw.println();
pw.println("-------------------------------");
for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
@@ -741,16 +707,31 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
pw.print(entry.getValue().description);
pw.println();
}
+ pw.println();
} else {
pw.printf("Session %s is dead", toString());
pw.println();
}
}
- ////////////////////////////////////////////////////////////////////////////////////////////
- // Callbacks
+ class CallbackWrapper implements ISoundTriggerCallback,
+ IBinder.DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
- @Override
+ CallbackWrapper(ISoundTriggerCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void detached() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
// We cannot obtain a lock on SoundTriggerMiddlewareValidation.this, since this call
// might be coming from the audio server (via setCaptureState()) while it is holding
@@ -759,18 +740,21 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// To avoid this problem, we use an atomic model activity state. There is a risk of the
// model not being in the mLoadedModels map here, since it might have been stopped /
// unloaded while the event was in flight.
- if (event.status != RecognitionStatus.FORCED) {
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState != null) {
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState != null) {
+ if (event.status != RecognitionStatus.FORCED) {
modelState.setActivityState(ModelState.Activity.LOADED);
}
- }
- try {
- mCallback.onRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (Exception e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ if (event.status != RecognitionStatus.FORCED) {
+ modelState.setActivityState(ModelState.Activity.INTERCEPTED);
+ }
+ }
}
}
@@ -783,18 +767,21 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
// To avoid this problem, we use an atomic model activity state. There is a risk of the
// model not being in the mLoadedModels map here, since it might have been stopped /
// unloaded while the event was in flight.
- if (event.common.status != RecognitionStatus.FORCED) {
- ModelState modelState = mLoadedModels.get(modelHandle);
- if (modelState != null) {
- modelState.setActivityState(ModelState.Activity.LOADED);
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState != null) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ modelState.setActivityState(ModelState.Activity.LOADED);
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (Exception e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ if (event.common.status != RecognitionStatus.FORCED) {
+ modelState.setActivityState(ModelState.Activity.INTERCEPTED);
+ }
}
- }
- try {
- mCallback.onPhraseRecognition(modelHandle, event);
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
}
}
@@ -810,41 +797,53 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware
}
}
- @Override
- public void onModuleDied() {
- synchronized (SoundTriggerMiddlewareValidation.this) {
- mState = ModuleStatus.DEAD;
- }
- // Trigger the callback outside of the lock to avoid deadlocks.
- try {
- mCallback.onModuleDied();
- } catch (RemoteException e) {
- // Dead client will be handled by binderDied() - no need to handle here.
- // In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback exception.", e);
+ @Override
+ public void onModuleDied() {
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ mState = ModuleStatus.DEAD;
+ }
+ // Trigger the callback outside of the lock to avoid deadlocks.
+ try {
+ mCallback.onModuleDied();
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
+ }
}
- }
-
- @Override
- public void binderDied() {
- // This is called whenever our client process dies.
- synchronized (SoundTriggerMiddlewareValidation.this) {
- try {
- // Gracefully stop all active recognitions and unload the models.
- for (Map.Entry<Integer, ModelState> entry :
- mLoadedModels.entrySet()) {
- // Idempotent call, no harm in calling even for models that are already
- // stopped.
- mDelegate.stopRecognition(entry.getKey());
- mDelegate.unloadModel(entry.getKey());
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (SoundTriggerMiddlewareValidation.this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry :
+ mLoadedModels.entrySet()) {
+ if (entry.getValue().getActivityState()
+ == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
}
- // Detach.
- detachInternal();
- } catch (Exception e) {
- throw handleException(e);
}
}
+
+ @Override
+ public IBinder asBinder() {
+ return mCallback.asBinder();
+ }
+
+ // Override toString() in order to have the delegate's ID in it.
+ @Override
+ public String toString() {
+ return Objects.toString(mDelegate);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 5a587cc9764c..02d978dfdf99 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -184,11 +184,13 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
@Override
public void serviceDied(long cookie) {
Log.w(TAG, String.format("Underlying HAL driver died."));
- List<ISoundTriggerCallback> callbacks = new ArrayList<>(mActiveSessions.size());
+ List<ISoundTriggerCallback> callbacks;
synchronized (this) {
+ callbacks = new ArrayList<>(mActiveSessions.size());
for (Session session : mActiveSessions) {
callbacks.add(session.moduleDied());
}
+ mActiveSessions.clear();
reset();
}
// Trigger the callbacks outside of the lock to avoid deadlocks.
@@ -431,7 +433,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
*/
private ISoundTriggerCallback moduleDied() {
ISoundTriggerCallback callback = mCallback;
- removeSession(this);
mCallback = null;
return callback;
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index c871a5e9647f..f43a4cec6080 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -1574,7 +1574,7 @@ public class StatsPullAtomService extends SystemService {
}
int pullWifiActivityInfoLocked(int atomTag, List<StatsEvent> pulledData) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
mWifiManager.getWifiActivityEnergyInfoAsync(
@@ -1622,7 +1622,7 @@ public class StatsPullAtomService extends SystemService {
}
int pullModemActivityInfoLocked(int atomTag, List<StatsEvent> pulledData) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
mTelephony.requestModemActivityInfo(modemReceiver);
@@ -1630,13 +1630,14 @@ public class StatsPullAtomService extends SystemService {
if (modemInfo == null) {
return StatsManager.PULL_SKIP;
}
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, modemInfo.getTimestamp(),
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
+ modemInfo.getTimestampMillis(),
modemInfo.getSleepTimeMillis(), modemInfo.getIdleTimeMillis(),
- modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis(),
- modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis(),
- modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis(),
- modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis(),
- modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis(),
+ modemInfo.getTransmitDurationMillisAtPowerLevel(0),
+ modemInfo.getTransmitDurationMillisAtPowerLevel(1),
+ modemInfo.getTransmitDurationMillisAtPowerLevel(2),
+ modemInfo.getTransmitDurationMillisAtPowerLevel(3),
+ modemInfo.getTransmitDurationMillisAtPowerLevel(4),
modemInfo.getReceiveTimeMillis(),
-1 /*`energy_used` field name deprecated, use -1 to indicate as unused.*/));
} finally {
@@ -2723,7 +2724,7 @@ public class StatsPullAtomService extends SystemService {
// Add a RoleHolder atom for each package that holds a role.
int pullRoleHolderLocked(int atomTag, List<StatsEvent> pulledData) {
- long callingToken = Binder.clearCallingIdentity();
+ final long callingToken = Binder.clearCallingIdentity();
try {
PackageManager pm = mContext.getPackageManager();
RoleManagerInternal rmi = LocalServices.getService(RoleManagerInternal.class);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index e9215f9793d2..ebfffec1774e 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -86,6 +86,13 @@ public interface StatusBarManagerInternal {
void toggleSplitScreen();
void appTransitionFinished(int displayId);
+ /**
+ * Notifies the status bar that a Emergency Action launch gesture has been detected.
+ *
+ * TODO (b/169175022) Update method name and docs when feature name is locked.
+ */
+ void onEmergencyActionLaunchGestureDetected();
+
void toggleRecentApps();
void setCurrentUser(int newUserId);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index e99377a3f1de..130254c96162 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -56,6 +56,7 @@ import android.view.WindowInsetsController.Appearance;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.os.TransferPipe;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
@@ -264,6 +265,23 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
+ /**
+ * Notifies the status bar that a Emergency Action launch gesture has been detected.
+ *
+ * TODO (b/169175022) Update method name and docs when feature name is locked.
+ */
+ @Override
+ public void onEmergencyActionLaunchGestureDetected() {
+ if (SPEW) Slog.d(TAG, "Launching emergency action");
+ if (mBar != null) {
+ try {
+ mBar.onEmergencyActionLaunchGestureDetected();
+ } catch (RemoteException e) {
+ if (SPEW) Slog.d(TAG, "Failed to launch emergency action");
+ }
+ }
+ }
+
@Override
public void topAppWindowChanged(int displayId, boolean isFullscreen, boolean isImmersive) {
StatusBarManagerService.this.topAppWindowChanged(displayId, isFullscreen, isImmersive);
@@ -1158,7 +1176,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onPanelRevealed(boolean clearNotificationEffects, int numItems) {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onPanelRevealed(clearNotificationEffects, numItems);
} finally {
@@ -1169,7 +1187,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void clearNotificationEffects() throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.clearEffects();
} finally {
@@ -1180,7 +1198,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onPanelHidden() throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onPanelHidden();
} finally {
@@ -1196,7 +1214,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
String reason = PowerManager.SHUTDOWN_USER_REQUESTED;
ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.prepareForPossibleShutdown();
// ShutdownThread displays UI, so give it a UI context.
@@ -1217,7 +1235,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
? PowerManager.REBOOT_SAFE_MODE
: PowerManager.SHUTDOWN_USER_REQUESTED;
ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.prepareForPossibleShutdown();
mHandler.post(() -> {
@@ -1236,7 +1254,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onGlobalActionsShown() {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
if (mGlobalActionListener == null) return;
mGlobalActionListener.onGlobalActionsShown();
@@ -1248,7 +1266,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onGlobalActionsHidden() {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
if (mGlobalActionListener == null) return;
mGlobalActionListener.onGlobalActionsDismissed();
@@ -1262,7 +1280,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationClick(callingUid, callingPid, key, nv);
} finally {
@@ -1277,7 +1295,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationActionClick(callingUid, callingPid, key,
actionIndex, action, nv, generatedByAssistant);
@@ -1292,7 +1310,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
// WARNING: this will call back into us to do the remove. Don't hold any locks.
mNotificationDelegate.onNotificationError(callingUid, callingPid,
@@ -1310,7 +1328,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId,
key, dismissalSurface, dismissalSentiment, nv);
@@ -1324,7 +1342,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
NotificationVisibility[] newlyVisibleKeys, NotificationVisibility[] noLongerVisibleKeys)
throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationVisibilityChanged(
newlyVisibleKeys, noLongerVisibleKeys);
@@ -1337,7 +1355,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
public void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded,
int location) throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationExpansionChanged(
key, userAction, expanded, location);
@@ -1349,7 +1367,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onNotificationDirectReplied(String key) throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationDirectReplied(key);
} finally {
@@ -1361,7 +1379,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount,
int smartActionCount, boolean generatedByAssistant, boolean editBeforeSending) {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationSmartSuggestionsAdded(key, smartReplyCount,
smartActionCount, generatedByAssistant, editBeforeSending);
@@ -1375,7 +1393,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
String key, int replyIndex, CharSequence reply, int notificationLocation,
boolean modifiedBeforeSending) throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex, reply,
notificationLocation, modifiedBeforeSending);
@@ -1387,7 +1405,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onNotificationSettingsViewed(String key) throws RemoteException {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationSettingsViewed(key);
} finally {
@@ -1400,7 +1418,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
enforceStatusBarService();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onClearAll(callingUid, callingPid, userId);
} finally {
@@ -1411,7 +1429,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onNotificationBubbleChanged(String key, boolean isBubble, int flags) {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationBubbleChanged(key, isBubble, flags);
} finally {
@@ -1422,7 +1440,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed) {
enforceStatusBarService();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed);
} finally {
@@ -1446,7 +1464,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
String packageName) {
enforceStatusBarService();
int callingUid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.grantInlineReplyUriPermission(key, uri, user, packageName,
callingUid);
@@ -1459,7 +1477,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
public void clearInlineReplyUriPermissions(String key) {
enforceStatusBarService();
int callingUid = Binder.getCallingUid();
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.clearInlineReplyUriPermissions(key, callingUid);
} finally {
@@ -1511,6 +1529,23 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
return mContext.getResources().getStringArray(R.array.config_statusBarIcons);
}
+ /** @hide */
+ public void passThroughShellCommand(String[] args, FileDescriptor fd) {
+ enforceStatusBarOrShell();
+ if (mBar == null) return;
+
+ try (TransferPipe tp = new TransferPipe()) {
+ // Sending the command to the remote, which needs to execute async to avoid blocking
+ // See Binder#dumpAsync() for inspiration
+ tp.setBufferPrefix(" ");
+ mBar.passThroughShellCommand(args, tp.getWriteFd());
+ // Times out after 5s
+ tp.go(fd);
+ } catch (Throwable t) {
+ Slog.e(TAG, "Error sending command to IStatusBar", t);
+ }
+ }
+
// ================================================================================
// Can be called from any thread
// ================================================================================
diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
index a79c19f7bc17..617182290d39 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java
@@ -46,7 +46,8 @@ public class StatusBarShellCommand extends ShellCommand {
@Override
public int onCommand(String cmd) {
if (cmd == null) {
- return handleDefaultCommands(cmd);
+ onHelp();
+ return 1;
}
try {
switch (cmd) {
@@ -74,8 +75,16 @@ public class StatusBarShellCommand extends ShellCommand {
return runSendDisableFlag();
case "tracing":
return runTracing();
+ // Handle everything that would be handled in `handleDefaultCommand()` explicitly,
+ // so the default can be to pass all args to StatusBar
+ case "-h":
+ case "help":
+ onHelp();
+ return 0;
+ case "dump":
+ return super.handleDefaultCommands(cmd);
default:
- return handleDefaultCommands(cmd);
+ return runPassArgsToStatusBar();
}
} catch (RemoteException e) {
final PrintWriter pw = getOutPrintWriter();
@@ -187,6 +196,11 @@ public class StatusBarShellCommand extends ShellCommand {
return 0;
}
+ private int runPassArgsToStatusBar() {
+ mInterface.passThroughShellCommand(getAllArgs(), getOutFileDescriptor());
+ return 0;
+ }
+
private int runTracing() {
switch (getNextArg()) {
case "start":
@@ -250,6 +264,12 @@ public class StatusBarShellCommand extends ShellCommand {
pw.println(" tracing (start | stop)");
pw.println(" Start or stop SystemUI tracing");
pw.println("");
+ pw.println(" NOTE: any command not listed here will be passed through to IStatusBar");
+ pw.println("");
+ pw.println(" Commands implemented in SystemUI:");
+ pw.flush();
+ // Sending null args to systemui will print help
+ mInterface.passThroughShellCommand(new String[] {}, getOutFileDescriptor());
}
/**
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 3406bd99c883..59cebf769a4e 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -84,6 +84,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
new ContentObserver(handler) {
+ @Override
public void onChange(boolean selfChange) {
timeDetectorService.handleAutoTimeDetectionChanged();
}
@@ -113,7 +114,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub {
enforceSuggestManualTimePermission();
Objects.requireNonNull(timeSignal);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
return mTimeDetectorStrategy.suggestManualTime(timeSignal);
} finally {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index fe0e82e66093..9c18aadb79ec 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -572,7 +572,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
*/
@VisibleForTesting
@Nullable
- public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
+ public synchronized NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
return findLatestValidNetworkSuggestion();
}
@@ -590,7 +590,7 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
*/
@VisibleForTesting
@Nullable
- public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ public synchronized NetworkTimeSuggestion getLatestNetworkSuggestion() {
return mLastNetworkSuggestion.get();
}
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index b68c54fc6365..1f73977444f8 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -16,29 +16,33 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.os.UserHandle;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
* Holds all configuration values that affect time zone behavior and some associated logic, e.g.
* {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link
- * #createCapabilities()}.
+ * #createCapabilitiesAndConfig()}.
*/
public final class ConfigurationInternal {
private final @UserIdInt int mUserId;
private final boolean mUserConfigAllowed;
private final boolean mAutoDetectionSupported;
+ private final boolean mGeoDetectionSupported;
private final boolean mAutoDetectionEnabled;
private final boolean mLocationEnabled;
private final boolean mGeoDetectionEnabled;
@@ -47,9 +51,13 @@ public final class ConfigurationInternal {
mUserId = builder.mUserId;
mUserConfigAllowed = builder.mUserConfigAllowed;
mAutoDetectionSupported = builder.mAutoDetectionSupported;
+ mGeoDetectionSupported = builder.mGeoDetectionSupported;
mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
mLocationEnabled = builder.mLocationEnabled;
mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+ // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported
+ // cannot be true if mAutoDetectionSupported == false
+ Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported);
}
/** Returns the ID of the user this configuration is associated with. */
@@ -68,11 +76,16 @@ public final class ConfigurationInternal {
return mUserConfigAllowed;
}
- /** Returns true if the device supports some form of auto time zone detection. */
+ /** Returns true if the device supports any form of auto time zone detection. */
public boolean isAutoDetectionSupported() {
return mAutoDetectionSupported;
}
+ /** Returns true if the device supports geolocation time zone detection. */
+ public boolean isGeoDetectionSupported() {
+ return mGeoDetectionSupported;
+ }
+
/** Returns the value of the auto time zone detection enabled setting. */
public boolean getAutoDetectionEnabledSetting() {
return mAutoDetectionEnabled;
@@ -100,44 +113,51 @@ public final class ConfigurationInternal {
* distinct from the raw setting value.
*/
public boolean getGeoDetectionEnabledBehavior() {
- if (getAutoDetectionEnabledBehavior()) {
- return mLocationEnabled && mGeoDetectionEnabled;
- }
- return false;
+ return getAutoDetectionEnabledBehavior()
+ && isGeoDetectionSupported()
+ && isLocationEnabled()
+ && getGeoDetectionEnabledSetting();
}
- /** Creates a {@link TimeZoneCapabilities} object using the configuration values. */
- public TimeZoneCapabilities createCapabilities() {
- TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder()
- .setConfiguration(asConfiguration());
+ /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
+ public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig() {
+ return new TimeZoneCapabilitiesAndConfig(asCapabilities(), asConfiguration());
+ }
+
+ @NonNull
+ private TimeZoneCapabilities asCapabilities() {
+ UserHandle userHandle = UserHandle.of(mUserId);
+ TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
boolean allowConfigDateTime = isUserConfigAllowed();
// Automatic time zone detection is only supported on devices if there is a telephony
// network available or geolocation time zone detection is possible.
- boolean deviceHasTimeZoneDetection = isAutoDetectionSupported();
+ boolean deviceHasAutoTimeZoneDetection = isAutoDetectionSupported();
final int configureAutoDetectionEnabledCapability;
- if (!deviceHasTimeZoneDetection) {
+ if (!deviceHasAutoTimeZoneDetection) {
configureAutoDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
} else if (!allowConfigDateTime) {
configureAutoDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
} else {
configureAutoDetectionEnabledCapability = CAPABILITY_POSSESSED;
}
- builder.setConfigureAutoDetectionEnabled(configureAutoDetectionEnabledCapability);
+ builder.setConfigureAutoDetectionEnabledCapability(configureAutoDetectionEnabledCapability);
+ boolean deviceHasLocationTimeZoneDetection = isGeoDetectionSupported();
final int configureGeolocationDetectionEnabledCapability;
- if (!deviceHasTimeZoneDetection) {
+ if (!deviceHasLocationTimeZoneDetection) {
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
} else if (!allowConfigDateTime) {
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_ALLOWED;
- } else if (!isLocationEnabled()) {
+ } else if (!mAutoDetectionEnabled || !isLocationEnabled()) {
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
} else {
configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
}
- builder.setConfigureGeoDetectionEnabled(configureGeolocationDetectionEnabledCapability);
+ builder.setConfigureGeoDetectionEnabledCapability(
+ configureGeolocationDetectionEnabledCapability);
// The ability to make manual time zone suggestions can also be restricted by policy. With
// the current logic above, this could lead to a situation where a device hardware does not
@@ -151,14 +171,14 @@ public final class ConfigurationInternal {
} else {
suggestManualTimeZoneCapability = CAPABILITY_POSSESSED;
}
- builder.setSuggestManualTimeZone(suggestManualTimeZoneCapability);
+ builder.setSuggestManualTimeZoneCapability(suggestManualTimeZoneCapability);
return builder.build();
}
/** Returns a {@link TimeZoneConfiguration} from the configuration values. */
- public TimeZoneConfiguration asConfiguration() {
- return new TimeZoneConfiguration.Builder(mUserId)
+ private TimeZoneConfiguration asConfiguration() {
+ return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
.setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
.build();
@@ -171,10 +191,10 @@ public final class ConfigurationInternal {
*/
public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
Builder builder = new Builder(this);
- if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_AUTO_DETECTION_ENABLED)) {
+ if (newConfiguration.hasIsAutoDetectionEnabled()) {
builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
}
- if (newConfiguration.hasSetting(TimeZoneConfiguration.SETTING_GEO_DETECTION_ENABLED)) {
+ if (newConfiguration.hasIsGeoDetectionEnabled()) {
builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
}
return builder.build();
@@ -192,6 +212,7 @@ public final class ConfigurationInternal {
return mUserId == that.mUserId
&& mUserConfigAllowed == that.mUserConfigAllowed
&& mAutoDetectionSupported == that.mAutoDetectionSupported
+ && mGeoDetectionSupported == that.mGeoDetectionSupported
&& mAutoDetectionEnabled == that.mAutoDetectionEnabled
&& mLocationEnabled == that.mLocationEnabled
&& mGeoDetectionEnabled == that.mGeoDetectionEnabled;
@@ -200,7 +221,8 @@ public final class ConfigurationInternal {
@Override
public int hashCode() {
return Objects.hash(mUserId, mUserConfigAllowed, mAutoDetectionSupported,
- mAutoDetectionEnabled, mLocationEnabled, mGeoDetectionEnabled);
+ mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled,
+ mGeoDetectionEnabled);
}
@Override
@@ -209,6 +231,7 @@ public final class ConfigurationInternal {
+ "mUserId=" + mUserId
+ ", mUserConfigAllowed=" + mUserConfigAllowed
+ ", mAutoDetectionSupported=" + mAutoDetectionSupported
+ + ", mGeoDetectionSupported=" + mGeoDetectionSupported
+ ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
+ ", mLocationEnabled=" + mLocationEnabled
+ ", mGeoDetectionEnabled=" + mGeoDetectionEnabled
@@ -221,8 +244,10 @@ public final class ConfigurationInternal {
public static class Builder {
private final @UserIdInt int mUserId;
+
private boolean mUserConfigAllowed;
private boolean mAutoDetectionSupported;
+ private boolean mGeoDetectionSupported;
private boolean mAutoDetectionEnabled;
private boolean mLocationEnabled;
private boolean mGeoDetectionEnabled;
@@ -241,6 +266,7 @@ public final class ConfigurationInternal {
this.mUserId = toCopy.mUserId;
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
this.mAutoDetectionSupported = toCopy.mAutoDetectionSupported;
+ this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
this.mLocationEnabled = toCopy.mLocationEnabled;
this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
@@ -255,7 +281,7 @@ public final class ConfigurationInternal {
}
/**
- * Sets whether automatic time zone detection is supported on this device.
+ * Sets whether any form of automatic time zone detection is supported on this device.
*/
public Builder setAutoDetectionSupported(boolean supported) {
mAutoDetectionSupported = supported;
@@ -263,6 +289,14 @@ public final class ConfigurationInternal {
}
/**
+ * Sets whether geolocation time zone detection is supported on this device.
+ */
+ public Builder setGeoDetectionSupported(boolean supported) {
+ mGeoDetectionSupported = supported;
+ return this;
+ }
+
+ /**
* Sets the value of the automatic time zone detection enabled setting for this device.
*/
public Builder setAutoDetectionEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index 6a12b7c8f9a5..941be0ede1b9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -23,7 +23,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
-import android.app.timezonedetector.TimeZoneConfiguration;
+import android.app.time.TimeZoneConfiguration;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -87,6 +87,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
new ContentObserver(mHandler) {
+ @Override
public void onChange(boolean selfChange) {
handleConfigChangeOnHandlerThread();
}
@@ -97,6 +98,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
Settings.Secure.getUriFor(Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED),
true,
new ContentObserver(mHandler) {
+ @Override
public void onChange(boolean selfChange) {
handleConfigChangeOnHandlerThread();
}
@@ -117,13 +119,13 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
@Override
public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
- boolean geoDetectionEnabled = mGeoDetectionFeatureEnabled && isGeoDetectionEnabled(userId);
return new ConfigurationInternal.Builder(userId)
.setUserConfigAllowed(isUserConfigAllowed(userId))
.setAutoDetectionSupported(isAutoDetectionSupported())
+ .setGeoDetectionSupported(isGeoDetectionSupported())
.setAutoDetectionEnabled(isAutoDetectionEnabled())
.setLocationEnabled(isLocationEnabled(userId))
- .setGeoDetectionEnabled(geoDetectionEnabled)
+ .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
.build();
}
@@ -157,7 +159,7 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
}
@Override
- public void storeConfiguration(TimeZoneConfiguration configuration) {
+ public void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration) {
Objects.requireNonNull(configuration);
// Avoid writing the auto detection enabled setting for devices that do not support auto
@@ -168,8 +170,11 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled();
setAutoDetectionEnabled(autoDetectionEnabled);
- if (mGeoDetectionFeatureEnabled) {
- final int userId = configuration.getUserId();
+ // Avoid writing the geo detection enabled setting for devices that do not support geo
+ // time zone detection: if we wrote it down then we'd set the value explicitly, which
+ // would prevent detecting "default" later. That might influence what happens on later
+ // releases that support geo detection on the same hardware.
+ if (isGeoDetectionSupported()) {
final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
setGeoDetectionEnabled(userId, geoTzDetectionEnabled);
}
@@ -182,7 +187,11 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
}
private boolean isAutoDetectionSupported() {
- return deviceHasTelephonyNetwork() || mGeoDetectionFeatureEnabled;
+ return deviceHasTelephonyNetwork() || isGeoDetectionSupported();
+ }
+
+ private boolean isGeoDetectionSupported() {
+ return mGeoDetectionFeatureEnabled;
}
private boolean isAutoDetectionEnabled() {
@@ -198,10 +207,10 @@ public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrat
}
private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
- final boolean locationEnabled = isLocationEnabled(userId);
+ final boolean geoDetectionEnabledByDefault = false;
return Settings.Secure.getIntForUser(mCr,
Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
- locationEnabled ? 1 : 0 /* defaultValue */, userId) != 0;
+ (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
}
private void setGeoDetectionEnabled(@UserIdInt int userId, boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 73322a6987df..68a086da5037 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,18 +18,20 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ITimeZoneDetectorService;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
import android.util.Slog;
@@ -41,7 +43,6 @@ import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Objects;
/**
@@ -58,11 +59,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
private static final String TAG = "TimeZoneDetectorService";
/**
- * A compile time constant "feature switch" for enabling / disabling location-based time zone
- * detection on Android. If this is {@code false}, there should be few / little changes in
- * behavior with previous releases and little overhead associated with geolocation components.
+ * A "feature switch" for enabling / disabling location-based time zone detection. If this is
+ * {@code false}, there should be few / little changes in behavior with previous releases and
+ * little overhead associated with geolocation components.
+ * TODO(b/151304765) Remove this when the feature is on for all.
*/
- public static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED = false;
+ public static final boolean GEOLOCATION_TIME_ZONE_DETECTION_ENABLED =
+ SystemProperties.getBoolean(
+ "persist.sys.location_time_zone_detection_feature_enabled", false);
/**
* Handles the service lifecycle for {@link TimeZoneDetectorService} and
@@ -109,10 +113,14 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@NonNull
private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
- @GuardedBy("mConfigurationListeners")
+ /**
+ * Holds the listeners. The key is the {@link IBinder} associated with the listener, the value
+ * is the listener itself.
+ */
+ @GuardedBy("mListeners")
@NonNull
- private final ArrayList<ITimeZoneConfigurationListener> mConfigurationListeners =
- new ArrayList<>();
+ private final ArrayMap<IBinder, ITimeZoneDetectorListener> mListeners =
+ new ArrayMap<>();
private static TimeZoneDetectorService create(
@NonNull Context context, @NonNull Handler handler,
@@ -133,20 +141,22 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
- // Wire up a change listener so that ITimeZoneConfigurationListeners can be notified when
+ // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
// the configuration changes for any reason.
mTimeZoneDetectorStrategy.addConfigChangeListener(this::handleConfigurationChanged);
}
@Override
@NonNull
- public TimeZoneCapabilities getCapabilities() {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig() {
+ enforceManageTimeZoneDetectorPermission();
int userId = mCallerIdentityInjector.getCallingUserId();
- long token = mCallerIdentityInjector.clearCallingIdentity();
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.getConfigurationInternal(userId).createCapabilities();
+ ConfigurationInternal configurationInternal =
+ mTimeZoneDetectorStrategy.getConfigurationInternal(userId);
+ return configurationInternal.createCapabilitiesAndConfig();
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
@@ -154,37 +164,34 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
@Override
public boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(configuration);
int callingUserId = mCallerIdentityInjector.getCallingUserId();
- if (callingUserId != configuration.getUserId()) {
- return false;
- }
-
- long token = mCallerIdentityInjector.clearCallingIdentity();
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
- return mTimeZoneDetectorStrategy.updateConfiguration(configuration);
+ return mTimeZoneDetectorStrategy.updateConfiguration(callingUserId, configuration);
} finally {
mCallerIdentityInjector.restoreCallingIdentity(token);
}
}
@Override
- public void addConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public void addListener(@NonNull ITimeZoneDetectorListener listener) {
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(listener);
- synchronized (mConfigurationListeners) {
- if (mConfigurationListeners.contains(listener)) {
+ synchronized (mListeners) {
+ IBinder listenerBinder = listener.asBinder();
+ if (mListeners.containsKey(listenerBinder)) {
return;
}
try {
// Ensure the reference to the listener will be removed if the client process dies.
- listener.asBinder().linkToDeath(this, 0 /* flags */);
+ listenerBinder.linkToDeath(this, 0 /* flags */);
// Only add the listener if we can linkToDeath().
- mConfigurationListeners.add(listener);
+ mListeners.put(listenerBinder, listener);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to linkToDeath() for listener=" + listener, e);
}
@@ -192,21 +199,22 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
@Override
- public void removeConfigurationListener(@NonNull ITimeZoneConfigurationListener listener) {
- enforceManageTimeZoneDetectorConfigurationPermission();
+ public void removeListener(@NonNull ITimeZoneDetectorListener listener) {
+ enforceManageTimeZoneDetectorPermission();
Objects.requireNonNull(listener);
- synchronized (mConfigurationListeners) {
+ synchronized (mListeners) {
+ IBinder listenerBinder = listener.asBinder();
boolean removedListener = false;
- if (mConfigurationListeners.remove(listener)) {
+ if (mListeners.remove(listenerBinder) != null) {
// Stop listening for the client process to die.
- listener.asBinder().unlinkToDeath(this, 0 /* flags */);
+ listenerBinder.unlinkToDeath(this, 0 /* flags */);
removedListener = true;
}
if (!removedListener) {
Slog.w(TAG, "Client asked to remove listener=" + listener
+ ", but no listeners were removed."
- + " mConfigurationListeners=" + mConfigurationListeners);
+ + " mListeners=" + mListeners);
}
}
}
@@ -218,19 +226,18 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
/**
- * Called when one of the ITimeZoneConfigurationListener processes dies before calling
- * {@link #removeConfigurationListener(ITimeZoneConfigurationListener)}.
+ * Called when one of the ITimeZoneDetectorListener processes dies before calling
+ * {@link #removeListener(ITimeZoneDetectorListener)}.
*/
@Override
public void binderDied(IBinder who) {
- synchronized (mConfigurationListeners) {
+ synchronized (mListeners) {
boolean removedListener = false;
- final int listenerCount = mConfigurationListeners.size();
+ final int listenerCount = mListeners.size();
for (int listenerIndex = listenerCount - 1; listenerIndex >= 0; listenerIndex--) {
- ITimeZoneConfigurationListener listener =
- mConfigurationListeners.get(listenerIndex);
- if (listener.asBinder().equals(who)) {
- mConfigurationListeners.remove(listenerIndex);
+ IBinder listenerBinder = mListeners.keyAt(listenerIndex);
+ if (listenerBinder.equals(who)) {
+ mListeners.removeAt(listenerIndex);
removedListener = true;
break;
}
@@ -238,7 +245,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
if (!removedListener) {
Slog.w(TAG, "Notified of binder death for who=" + who
+ ", but did not remove any listeners."
- + " mConfigurationListeners=" + mConfigurationListeners);
+ + " mConfigurationListeners=" + mListeners);
}
}
}
@@ -247,11 +254,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
// Configuration has changed, but each user may have a different view of the configuration.
// It's possible that this will cause unnecessary notifications but that shouldn't be a
// problem.
- synchronized (mConfigurationListeners) {
- final int listenerCount = mConfigurationListeners.size();
+ synchronized (mListeners) {
+ final int listenerCount = mListeners.size();
for (int listenerIndex = 0; listenerIndex < listenerCount; listenerIndex++) {
- ITimeZoneConfigurationListener listener =
- mConfigurationListeners.get(listenerIndex);
+ ITimeZoneDetectorListener listener = mListeners.valueAt(listenerIndex);
try {
listener.onChange();
} catch (RemoteException e) {
@@ -276,7 +282,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
Objects.requireNonNull(timeZoneSuggestion);
int userId = mCallerIdentityInjector.getCallingUserId();
- long token = mCallerIdentityInjector.clearCallingIdentity();
+ final long token = mCallerIdentityInjector.clearCallingIdentity();
try {
return mTimeZoneDetectorStrategy.suggestManualTimeZone(userId, timeZoneSuggestion);
} finally {
@@ -302,11 +308,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
ipw.flush();
}
- private void enforceManageTimeZoneDetectorConfigurationPermission() {
- // TODO Switch to a dedicated MANAGE_TIME_AND_ZONE_CONFIGURATION permission.
+ private void enforceManageTimeZoneDetectorPermission() {
mContext.enforceCallingPermission(
- android.Manifest.permission.WRITE_SECURE_SETTINGS,
- "manage time and time zone configuration");
+ android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION,
+ "manage time and time zone detection");
}
private void enforceSuggestGeolocationTimeZonePermission() {
@@ -333,7 +338,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new TimeZoneDetectorShellCommand(this)).exec(
+ new TimeZoneDetectorShellCommand(this).exec(
this, in, out, err, args, callback, resultReceiver);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index f944c5638fa9..0b1d6d71ea7b 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,9 +17,9 @@ package com.android.server.timezonedetector;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
/**
@@ -96,7 +96,8 @@ public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container {
* <p>This method returns {@code true} if the configuration was changed,
* {@code false} otherwise.
*/
- boolean updateConfiguration(@NonNull TimeZoneConfiguration configuration);
+ boolean updateConfiguration(
+ @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration);
/**
* Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 8a42b18b514f..781668bebbc9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -15,20 +15,21 @@
*/
package com.android.server.timezonedetector;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.os.Handler;
import android.util.IndentingPrintWriter;
@@ -93,7 +94,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* All checks about user capabilities must be done by the caller and
* {@link TimeZoneConfiguration#isComplete()} must be {@code true}.
*/
- void storeConfiguration(TimeZoneConfiguration newConfiguration);
+ void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration);
}
private static final String LOG_TAG = "TimeZoneDetectorStrategy";
@@ -240,27 +241,26 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
@Override
- public synchronized boolean updateConfiguration(
+ public synchronized boolean updateConfiguration(@UserIdInt int userId,
@NonNull TimeZoneConfiguration requestedConfiguration) {
Objects.requireNonNull(requestedConfiguration);
- int userId = requestedConfiguration.getUserId();
- TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ getConfigurationInternal(userId).createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
- // Create a new configuration builder, and copy across the mutable properties users are
- // able to modify. Other properties are therefore ignored.
final TimeZoneConfiguration newConfiguration =
- capabilities.applyUpdate(requestedConfiguration);
+ capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
if (newConfiguration == null) {
- // The changes could not be made due to
+ // The changes could not be made because the user's capabilities do not allow it.
return false;
}
// Store the configuration / notify as needed. This will cause the mCallback to invoke
// handleConfigChanged() asynchronously.
- mCallback.storeConfiguration(newConfiguration);
+ mCallback.storeConfiguration(userId, newConfiguration);
- TimeZoneConfiguration oldConfiguration = capabilities.getConfiguration();
String logMsg = "Configuration changed:"
+ " oldConfiguration=" + oldConfiguration
+ ", newConfiguration=" + newConfiguration;
@@ -313,8 +313,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
String timeZoneId = suggestion.getZoneId();
String cause = "Manual time suggestion received: suggestion=" + suggestion;
- TimeZoneCapabilities capabilities = getConfigurationInternal(userId).createCapabilities();
- if (capabilities.getSuggestManualTimeZone() != CAPABILITY_POSSESSED) {
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ getConfigurationInternal(userId).createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ if (capabilities.getSuggestManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
+ ", capabilities=" + capabilities
+ ", timeZoneId=" + timeZoneId
@@ -605,7 +607,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
- ipw.println("[Capabilities=" + configuration.createCapabilities() + "]");
+ ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]");
ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+ mCallback.isDeviceTimeZoneInitialized());
ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
@@ -644,7 +646,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
- public GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
+ public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
return mLatestGeoLocationSuggestion.get();
}
@@ -652,7 +654,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@VisibleForTesting
- public static class QualifiedTelephonyTimeZoneSuggestion {
+ public static final class QualifiedTelephonyTimeZoneSuggestion {
@VisibleForTesting
public final TelephonyTimeZoneSuggestion suggestion;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 89b108c24630..0e8fd8fa9f8a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1125,7 +1125,7 @@ public class TrustManagerService extends SystemService {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceLocked", null);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
userId = resolveProfileParent(userId);
@@ -1141,7 +1141,7 @@ public class TrustManagerService extends SystemService {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceSecure", null);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
userId = resolveProfileParent(userId);
@@ -1328,7 +1328,7 @@ public class TrustManagerService extends SystemService {
}
private int resolveProfileParent(int userId) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
UserInfo parent = mUserManager.getProfileParent(userId);
if (parent != null) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 8ccbbdd2265c..149dbd00b0a8 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -22,7 +22,9 @@ import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -33,6 +35,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -51,6 +54,7 @@ import android.media.tv.ITvInputService;
import android.media.tv.ITvInputServiceCallback;
import android.media.tv.ITvInputSession;
import android.media.tv.ITvInputSessionCallback;
+import android.media.tv.TunedInfo;
import android.media.tv.TvContentRating;
import android.media.tv.TvContentRatingSystemInfo;
import android.media.tv.TvContract;
@@ -73,18 +77,19 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputChannel;
import android.view.Surface;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import java.io.File;
import java.io.FileDescriptor;
@@ -108,6 +113,9 @@ public final class TvInputManagerService extends SystemService {
private static final boolean DEBUG = false;
private static final String TAG = "TvInputManagerService";
private static final String DVB_DIRECTORY = "/dev/dvb";
+ private static final int APP_TAG_SELF = TunedInfo.APP_TAG_SELF;
+ private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS =
+ "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS";
// There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
// another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
@@ -136,6 +144,8 @@ public final class TvInputManagerService extends SystemService {
private final WatchLogHandler mWatchLogHandler;
+ private final ActivityManager mActivityManager;
+
public TvInputManagerService(Context context) {
super(context);
@@ -144,6 +154,9 @@ public final class TvInputManagerService extends SystemService {
IoThread.get().getLooper());
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
+ mActivityManager =
+ (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
+
synchronized (mLock) {
getOrCreateUserStateLocked(mCurrentUserId);
}
@@ -686,14 +699,17 @@ public final class TvInputManagerService extends SystemService {
SessionState sessionState = null;
try {
sessionState = getSessionStateLocked(sessionToken, callingUid, userId);
+ UserState userState = getOrCreateUserStateLocked(userId);
if (sessionState.session != null) {
- UserState userState = getOrCreateUserStateLocked(userId);
if (sessionToken == userState.mainSessionToken) {
setMainLocked(sessionToken, false, callingUid, userId);
}
sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
sessionState.session.release();
}
+ sessionState.isCurrent = false;
+ sessionState.currentChannel = null;
+ notifyCurrentChannelInfosUpdatedLocked(userState);
} catch (RemoteException | SessionNotFoundException e) {
Slog.e(TAG, "error in releaseSession", e);
} finally {
@@ -834,6 +850,25 @@ public final class TvInputManagerService extends SystemService {
}
}
+ private void notifyCurrentChannelInfosUpdatedLocked(UserState userState) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyCurrentChannelInfosUpdatedLocked");
+ }
+ int n = userState.mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ ITvInputManagerCallback callback = userState.mCallbacks.getBroadcastItem(i);
+ Pair<Integer, Integer> pidUid = userState.callbackPidUidMap.get(callback);
+ List<TunedInfo> infos = getCurrentTunedInfosInternalLocked(
+ userState, pidUid.first, pidUid.second);
+ callback.onCurrentTunedInfosUpdated(infos);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report updated current channel infos to callback", e);
+ }
+ }
+ userState.mCallbacks.finishBroadcast();
+ }
+
private void updateTvInputInfoLocked(UserState userState, TvInputInfo inputInfo) {
if (DEBUG) {
Slog.d(TAG, "updateTvInputInfoLocked(inputInfo=" + inputInfo + ")");
@@ -1030,14 +1065,19 @@ public final class TvInputManagerService extends SystemService {
@Override
public void registerCallback(final ITvInputManagerCallback callback, int userId) {
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, "registerCallback");
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "registerCallback");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
if (!userState.mCallbacks.register(callback)) {
Slog.e(TAG, "client process has already died");
+ } else {
+ userState.callbackPidUidMap.put(
+ callback, Pair.create(callingPid, callingUid));
}
}
} finally {
@@ -1054,6 +1094,7 @@ public final class TvInputManagerService extends SystemService {
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
userState.mCallbacks.unregister(callback);
+ userState.callbackPidUidMap.remove(callback);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1386,21 +1427,26 @@ public final class TvInputManagerService extends SystemService {
@Override
public void tune(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
- userId, "tune");
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, "tune");
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
try {
getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(
channelUri, params);
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState != null) {
+ sessionState.isCurrent = true;
+ sessionState.currentChannel = channelUri;
+ notifyCurrentChannelInfosUpdatedLocked(userState);
+ }
if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
// Do not log the watch history for passthrough inputs.
return;
}
- UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (sessionState.isRecordingSession) {
return;
}
@@ -2049,6 +2095,18 @@ public final class TvInputManagerService extends SystemService {
return clientPid;
}
+ @Override
+ public List<TunedInfo> getCurrentTunedInfos(@UserIdInt int userId) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "getTvCurrentChannelInfos");
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ return getCurrentTunedInfosInternalLocked(userState, callingPid, callingUid);
+ }
+ }
+
/**
* Add a hardware device in the TvInputHardwareManager for CTS testing
* purpose.
@@ -2219,6 +2277,70 @@ public final class TvInputManagerService extends SystemService {
}
}
+ private List<TunedInfo> getCurrentTunedInfosInternalLocked(
+ UserState userState, int callingPid, int callingUid) {
+ List<TunedInfo> channelInfos = new ArrayList<>();
+ boolean watchedProgramsAccess = hasAccessWatchedProgramsPermission(callingPid, callingUid);
+ for (SessionState state : userState.sessionStateMap.values()) {
+ if (state.isCurrent) {
+ Integer appTag;
+ int appType;
+ if (state.callingUid == callingUid) {
+ appTag = APP_TAG_SELF;
+ appType = TunedInfo.APP_TYPE_SELF;
+ } else {
+ appTag = userState.mAppTagMap.get(state.callingUid);
+ if (appTag == null) {
+ appTag = userState.mNextAppTag++;
+ userState.mAppTagMap.put(state.callingUid, appTag);
+ }
+ appType = isSystemApp(state.componentName.getPackageName())
+ ? TunedInfo.APP_TYPE_SYSTEM
+ : TunedInfo.APP_TYPE_NON_SYSTEM;
+ }
+ channelInfos.add(new TunedInfo(
+ state.inputId,
+ watchedProgramsAccess ? state.currentChannel : null,
+ state.isRecordingSession,
+ isForeground(state.callingPid),
+ appType,
+ appTag));
+ }
+ }
+ return channelInfos;
+ }
+
+ private boolean isForeground(int pid) {
+ if (mActivityManager == null) {
+ return false;
+ }
+ List<RunningAppProcessInfo> appProcesses = mActivityManager.getRunningAppProcesses();
+ if (appProcesses == null) {
+ return false;
+ }
+ for (RunningAppProcessInfo appProcess : appProcesses) {
+ if (appProcess.pid == pid
+ && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasAccessWatchedProgramsPermission(int callingPid, int callingUid) {
+ return mContext.checkPermission(PERMISSION_ACCESS_WATCHED_PROGRAMS, callingPid, callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean isSystemApp(String pkg) {
+ try {
+ return (mContext.getPackageManager().getApplicationInfo(pkg, 0).flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
private static final class UserState {
// A mapping from the TV input id to its TvInputState.
private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -2243,6 +2365,9 @@ public final class TvInputManagerService extends SystemService {
private final RemoteCallbackList<ITvInputManagerCallback> mCallbacks =
new RemoteCallbackList<ITvInputManagerCallback>();
+ private final Map<ITvInputManagerCallback, Pair<Integer, Integer>> callbackPidUidMap =
+ new HashMap<>();
+
// The token of a "main" TV input session.
private IBinder mainSessionToken = null;
@@ -2250,6 +2375,11 @@ public final class TvInputManagerService extends SystemService {
// service.
private final PersistentDataStore persistentDataStore;
+ @GuardedBy("mLock")
+ private final Map<Integer, Integer> mAppTagMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private int mNextAppTag = 1;
+
private UserState(Context context, int userId) {
persistentDataStore = new PersistentDataStore(context, userId);
}
@@ -2336,6 +2466,9 @@ public final class TvInputManagerService extends SystemService {
// Not null if this session represents an external device connected to a hardware TV input.
private IBinder hardwareSessionToken;
+ private boolean isCurrent = false;
+ private Uri currentChannel = null;
+
private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
int callingPid, int userId, String sessionId) {
@@ -2584,6 +2717,10 @@ public final class TvInputManagerService extends SystemService {
if (mSessionState.session == null || mSessionState.client == null) {
return;
}
+ mSessionState.isCurrent = true;
+ mSessionState.currentChannel = channelUri;
+ UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
+ notifyCurrentChannelInfosUpdatedLocked(userState);
try {
// TODO: Consider adding this channel change in the watch log. When we do
// that, how we can protect the watch log from malicious tv inputs should
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 39f6c597508c..f6acc64bdaa0 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -115,7 +115,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private static final String TAG = "UriGrantsManagerService";
// Maximum number of persisted Uri grants a package is allowed
private static final int MAX_PERSISTED_URI_GRANTS = 512;
- private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+ private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
private final Object mLock = new Object();
private final H mH;
diff --git a/services/core/java/com/android/server/utils/XmlName.java b/services/core/java/com/android/server/utils/XmlName.java
new file mode 100644
index 000000000000..c0e22daaa32e
--- /dev/null
+++ b/services/core/java/com/android/server/utils/XmlName.java
@@ -0,0 +1,31 @@
+/*
+ * 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.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specify the XML name for the annotated field or persistence class.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({ ElementType.FIELD, ElementType.TYPE })
+public @interface XmlName {
+ String value();
+}
diff --git a/services/core/java/com/android/server/utils/XmlPersistence.java b/services/core/java/com/android/server/utils/XmlPersistence.java
new file mode 100644
index 000000000000..8900a9d4783e
--- /dev/null
+++ b/services/core/java/com/android/server/utils/XmlPersistence.java
@@ -0,0 +1,36 @@
+/*
+ * 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.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Generate XML persistence for the annotated class.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface XmlPersistence {
+ /**
+ * Name for the generated XML persistence class.
+ * <p>
+ * The name defaults to the target class name appended with {@code Persistence} if unspecified.
+ */
+ String value();
+}
diff --git a/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java b/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java
new file mode 100644
index 000000000000..c160647760cb
--- /dev/null
+++ b/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java
@@ -0,0 +1,338 @@
+/*
+ * 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.utils.eventlog;
+
+import android.os.SystemClock;
+import android.util.TimeUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ConcurrentModificationException;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An in-memory event log to support historical event information.
+ */
+public abstract class LocalEventLog implements Iterable<String> {
+
+ private interface Log {
+ // true if this is a filler element that should not be queried
+ boolean isFiller();
+ long getTimeDeltaMs();
+ String getLogString();
+ }
+
+ private static final class FillerEvent implements Log {
+
+ static final long MAX_TIME_DELTA = (1L << 32) - 1;
+
+ private final int mTimeDelta;
+
+ FillerEvent(long timeDelta) {
+ Preconditions.checkArgument(timeDelta >= 0);
+ mTimeDelta = (int) timeDelta;
+ }
+
+ @Override
+ public boolean isFiller() {
+ return true;
+ }
+
+ @Override
+ public long getTimeDeltaMs() {
+ return Integer.toUnsignedLong(mTimeDelta);
+ }
+
+ @Override
+ public String getLogString() {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * An abstraction of a log event to be implemented by subclasses.
+ */
+ public abstract static class LogEvent implements Log {
+
+ static final long MAX_TIME_DELTA = (1L << 32) - 1;
+
+ private final int mTimeDelta;
+
+ protected LogEvent(long timeDelta) {
+ Preconditions.checkArgument(timeDelta >= 0);
+ mTimeDelta = (int) timeDelta;
+ }
+
+ @Override
+ public final boolean isFiller() {
+ return false;
+ }
+
+ @Override
+ public final long getTimeDeltaMs() {
+ return Integer.toUnsignedLong(mTimeDelta);
+ }
+ }
+
+ // circular buffer of log entries
+ private final Log[] mLog;
+ private int mLogSize;
+ private int mLogEndIndex;
+ private int mModificationCount;
+
+ // invalid if log is empty
+ private long mStartRealtimeMs;
+ private long mLastLogRealtimeMs;
+
+ public LocalEventLog(int size) {
+ mLog = new Log[size];
+ mLogSize = 0;
+ mLogEndIndex = 0;
+ mModificationCount = 0;
+
+ mStartRealtimeMs = -1;
+ mLastLogRealtimeMs = -1;
+ }
+
+ /**
+ * Should be overridden by subclasses to return a new immutable log event for the given
+ * arguments (as passed into {@link #addLogEvent(int, Object...)}.
+ */
+ protected abstract LogEvent createLogEvent(long timeDelta, int event, Object... args);
+
+ /**
+ * May be optionally overridden by subclasses if they wish to change how log event time is
+ * formatted.
+ */
+ protected String getTimePrefix(long timeMs) {
+ return TimeUtils.logTimeOfDay(timeMs) + ": ";
+ }
+
+ /**
+ * Call to add a new log event at the current time. The arguments provided here will be passed
+ * into {@link #createLogEvent(long, int, Object...)} in addition to a time delta, and should be
+ * used to construct an appropriate {@link LogEvent} object.
+ */
+ public void addLogEvent(int event, Object... args) {
+ long timeMs = SystemClock.elapsedRealtime();
+
+ // calculate delta
+ long delta = 0;
+ if (!isEmpty()) {
+ delta = timeMs - mLastLogRealtimeMs;
+
+ // if the delta is invalid, or if the delta is great enough using filler elements would
+ // result in an empty log anyways, just clear the log and continue, otherwise insert
+ // filler elements until we have a reasonable delta
+ if (delta < 0 || (delta / FillerEvent.MAX_TIME_DELTA) >= mLog.length - 1) {
+ clear();
+ delta = 0;
+ } else {
+ while (delta >= LogEvent.MAX_TIME_DELTA) {
+ long timeDelta = Math.min(FillerEvent.MAX_TIME_DELTA, delta);
+ addLogEventInternal(new FillerEvent(timeDelta));
+ delta -= timeDelta;
+ }
+ }
+ }
+
+ // for first log entry, set initial times
+ if (isEmpty()) {
+ mStartRealtimeMs = timeMs;
+ mLastLogRealtimeMs = mStartRealtimeMs;
+ }
+
+ addLogEventInternal(createLogEvent(delta, event, args));
+ }
+
+ private void addLogEventInternal(Log event) {
+ Preconditions.checkState(mStartRealtimeMs != -1 && mLastLogRealtimeMs != -1);
+
+ if (mLogSize == mLog.length) {
+ // if log is full, size will remain the same, but update the start time
+ mStartRealtimeMs += event.getTimeDeltaMs();
+ } else {
+ // otherwise add an item
+ mLogSize++;
+ }
+
+ // set log and increment end index
+ mLog[mLogEndIndex] = event;
+ mLogEndIndex = incrementIndex(mLogEndIndex);
+ mLastLogRealtimeMs = mLastLogRealtimeMs + event.getTimeDeltaMs();
+
+ mModificationCount++;
+ }
+
+ /** Clears the log of all entries. */
+ public void clear() {
+ mLogEndIndex = 0;
+ mLogSize = 0;
+ mModificationCount++;
+
+ mStartRealtimeMs = -1;
+ mLastLogRealtimeMs = -1;
+ }
+
+ // checks if the log is empty (if empty, times are invalid)
+ private boolean isEmpty() {
+ return mLogSize == 0;
+ }
+
+ @Override
+ public ListIterator<String> iterator() {
+ return new LogIterator();
+ }
+
+ // returns the index of the first element
+ private int startIndex() {
+ return wrapIndex(mLogEndIndex - mLogSize);
+ }
+
+ // returns the index after this one
+ private int incrementIndex(int index) {
+ return wrapIndex(index + 1);
+ }
+
+ // returns the index before this one
+ private int decrementIndex(int index) {
+ return wrapIndex(index - 1);
+ }
+
+ // rolls over the given index if necessary
+ private int wrapIndex(int index) {
+ // java modulo will keep negative sign, we need to rollover
+ return (index % mLog.length + mLog.length) % mLog.length;
+ }
+
+ private class LogIterator implements ListIterator<String> {
+
+ private final int mModificationGuard;
+
+ private final long mSystemTimeDeltaMs;
+
+ private long mCurrentRealtimeMs;
+ private int mIndex;
+ private int mCount;
+
+ LogIterator() {
+ mModificationGuard = mModificationCount;
+ mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime();
+ mCurrentRealtimeMs = mStartRealtimeMs;
+ mIndex = startIndex();
+ mCount = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mCount < mLogSize;
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return mCount > 0;
+ }
+
+ @Override
+ // return then increment
+ public String next() {
+ checkModifications();
+
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ Log log = mLog[mIndex];
+ long nextDeltaMs = log.getTimeDeltaMs();
+ long realtimeMs = mCurrentRealtimeMs + nextDeltaMs;
+
+ // calculate next index, skipping filler events
+ do {
+ mCurrentRealtimeMs += nextDeltaMs;
+ mIndex = incrementIndex(mIndex);
+ if (++mCount < mLogSize) {
+ nextDeltaMs = mLog[mIndex].getTimeDeltaMs();
+ }
+ } while (mCount < mLogSize && mLog[mIndex].isFiller());
+
+ return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
+ }
+
+ @Override
+ // decrement then return
+ public String previous() {
+ checkModifications();
+
+ Log log;
+ long currentDeltaMs;
+ long realtimeMs;
+
+ // calculate previous index, skipping filler events with MAX_TIME_DELTA
+ do {
+ if (!hasPrevious()) {
+ throw new NoSuchElementException();
+ }
+
+ mIndex = decrementIndex(mIndex);
+ mCount--;
+
+ log = mLog[mIndex];
+ realtimeMs = mCurrentRealtimeMs;
+
+ if (mCount > 0) {
+ currentDeltaMs = log.getTimeDeltaMs();
+ mCurrentRealtimeMs -= currentDeltaMs;
+ }
+ } while (mCount >= 0 && log.isFiller());
+
+ return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString();
+ }
+
+ private void checkModifications() {
+ if (mModificationGuard != mModificationCount) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ @Override
+ public int nextIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int previousIndex() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void add(String s) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void set(String s) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/vcn/OWNERS b/services/core/java/com/android/server/vcn/OWNERS
new file mode 100644
index 000000000000..33b9f0f75f81
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+benedictwong@google.com
+ckesting@google.com
+evitayan@google.com
+nharold@google.com
+jchalard@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 3f2b5c231dca..a713e5b13667 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -295,6 +295,7 @@ class Vr2dDisplay {
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5f4d46cabdc0..8b0963b9f535 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1444,7 +1444,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void engineShown(IWallpaperEngine engine) {
synchronized (mLock) {
if (mReply != null) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mReply.sendResult(null);
} catch (RemoteException e) {
@@ -2009,7 +2009,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public boolean hasNamedWallpaper(String name) {
synchronized (mLock) {
List<UserInfo> users;
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).getUsers();
} finally {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 90a153be8800..ee46ce13ee73 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -171,7 +171,7 @@ public class WebViewUpdateService extends SystemService {
return;
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
WebViewUpdateService.this.mImpl.notifyRelroCreationCompleted();
} finally {
@@ -232,7 +232,7 @@ public class WebViewUpdateService extends SystemService {
throw new SecurityException(msg);
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
return WebViewUpdateService.this.mImpl.changeProviderAndSetting(
newProvider);
@@ -285,7 +285,7 @@ public class WebViewUpdateService extends SystemService {
throw new SecurityException(msg);
}
- long callingId = Binder.clearCallingIdentity();
+ final long callingId = Binder.clearCallingIdentity();
try {
WebViewUpdateService.this.mImpl.enableMultiProcess(enable);
} finally {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 54812a16baf9..6b81b2f3c708 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -402,6 +402,13 @@ class ActivityMetricsLogger {
return -1;
}
}
+
+ PackageOptimizationInfo getPackageOptimizationInfo(ArtManagerInternal artManagerInternal) {
+ return artManagerInternal == null || launchedActivityAppRecordRequiredAbi == null
+ ? PackageOptimizationInfo.createWithNoInfo()
+ : artManagerInternal.getPackageOptimizationInfo(applicationInfo,
+ launchedActivityAppRecordRequiredAbi, launchedActivityName);
+ }
}
ActivityMetricsLogger(ActivityStackSupervisor supervisor, Looper looper) {
@@ -864,14 +871,8 @@ class ActivityMetricsLogger {
info.bindApplicationDelayMs);
}
builder.addTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, info.windowsDrawnDelayMs);
- final ArtManagerInternal artManagerInternal = getArtManagerInternal();
final PackageOptimizationInfo packageOptimizationInfo =
- (artManagerInternal == null) || (info.launchedActivityAppRecordRequiredAbi == null)
- ? PackageOptimizationInfo.createWithNoInfo()
- : artManagerInternal.getPackageOptimizationInfo(
- info.applicationInfo,
- info.launchedActivityAppRecordRequiredAbi,
- info.launchedActivityName);
+ info.getPackageOptimizationInfo(getArtManagerInternal());
builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_REASON,
packageOptimizationInfo.getCompilationReason());
builder.addTaggedData(PACKAGE_OPTIMIZATION_COMPILATION_FILTER,
@@ -1019,6 +1020,8 @@ class ActivityMetricsLogger {
builder.addTaggedData(APP_TRANSITION_PROCESS_RUNNING,
info.mProcessRunning ? 1 : 0);
mMetricsLogger.write(builder);
+ final PackageOptimizationInfo packageOptimizationInfo =
+ infoSnapshot.getPackageOptimizationInfo(getArtManagerInternal());
FrameworkStatsLog.write(
FrameworkStatsLog.APP_START_FULLY_DRAWN,
info.mLastLaunchedActivity.info.applicationInfo.uid,
@@ -1029,6 +1032,8 @@ class ActivityMetricsLogger {
info.mLastLaunchedActivity.info.name,
info.mProcessRunning,
startupTimeMs,
+ packageOptimizationInfo.getCompilationReason(),
+ packageOptimizationInfo.getCompilationFilter(),
info.mSourceType,
info.mSourceEventDelayMs);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 854992ab83cf..3b2f4b48fca1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -116,6 +116,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -150,10 +151,7 @@ import static com.android.server.wm.ActivityRecordProto.WINDOW_TOKEN;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SAVED_STATE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
@@ -1143,16 +1141,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
boolean scheduleTopResumedActivityChanged(boolean onTop) {
if (!attachedToProcess()) {
- if (DEBUG_STATES) {
- Slog.w(TAG, "Can't report activity position update - client not running"
- + ", activityRecord=" + this);
- }
+ ProtoLog.w(WM_DEBUG_STATES,
+ "Can't report activity position update - client not running, "
+ + "activityRecord=%s", this);
return false;
}
try {
- if (DEBUG_STATES) {
- Slog.v(TAG, "Sending position change to " + this + ", onTop: " + onTop);
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Sending position change to %s, onTop: %b",
+ this, onTop);
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
TopResumedActivityChangeItem.obtain(onTop));
@@ -1318,6 +1314,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return;
}
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
@@ -2530,10 +2528,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*/
@FinishRequest int finishIfPossible(int resultCode, Intent resultData,
NeededUriGrants resultGrants, String reason, boolean oomAdj) {
- if (DEBUG_RESULTS || DEBUG_STATES) {
- Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode
- + ", data=" + resultData + ", reason=" + reason);
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Finishing activity r=%s, result=%d, data=%s, "
+ + "reason=%s", this, resultCode, resultData, reason);
if (finishing) {
Slog.w(TAG, "Duplicate finish request for r=" + this);
@@ -2615,12 +2611,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
setVisibility(false);
if (stack.mPausingActivity == null) {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish needs to pause: " + this);
+ ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this);
if (DEBUG_USER_LEAVING) {
Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
}
stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */);
+ null /* resuming */, "finish");
}
if (endTask) {
@@ -2662,7 +2658,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED;
} else {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + this);
+ ProtoLog.v(WM_DEBUG_STATES, "Finish waiting for pause of: %s", this);
}
return FINISH_RESULT_REQUESTED;
@@ -2714,7 +2710,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// activities be updated, they may be seen by users.
ensureVisibility = true;
} else if (mStackSupervisor.getKeyguardController().isKeyguardLocked()
- && stack.topActivityOccludesKeyguard()) {
+ && mStackSupervisor.getKeyguardController().topActivityOccludesKeyguard(this)) {
// Ensure activity visibilities and update lockscreen occluded/dismiss state when
// finishing the top activity that occluded keyguard. So that, the
// ActivityStack#mTopActivityOccludesKeyguard can be updated and the activity below
@@ -2821,7 +2817,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*/
@VisibleForTesting
boolean addToFinishingAndWaitForIdle() {
- if (DEBUG_STATES) Slog.v(TAG, "Enqueueing pending finish: " + this);
+ ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending finish: %s", this);
callServiceTrackeronActivityStatechange(FINISHING, true);
setState(FINISHING, "addToFinishingAndWaitForIdle");
if (!mStackSupervisor.mFinishingActivities.contains(this)) {
@@ -2850,10 +2846,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
if (isState(DESTROYING, DESTROYED)) {
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "activity " + this + " already destroying."
- + "skipping request with reason:" + reason);
- }
+ ProtoLog.v(WM_DEBUG_STATES, "activity %s already destroying, skipping "
+ + "request with reason:%s", this, reason);
return false;
}
@@ -2894,17 +2888,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// the list yet. Otherwise, we can just immediately put it in the destroyed state since
// we are not removing it from the list.
if (finishing && !skipDestroy) {
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Moving to DESTROYING: " + this + " (destroy requested)");
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYING: %s (destroy requested)", this);
callServiceTrackeronActivityStatechange(DESTROYING, true);
setState(DESTROYING,
"destroyActivityLocked. finishing and not skipping destroy");
mAtmService.mH.postDelayed(mDestroyTimeoutRunnable, DESTROY_TIMEOUT);
} else {
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (destroy skipped)");
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYED: %s "
+ + "(destroy skipped)", this);
callServiceTrackeronActivityStatechange(DESTROYED, true);
setState(DESTROYED,
"destroyActivityLocked. not finishing or skipping destroy");
@@ -2917,7 +2908,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
removeFromHistory(reason + " hadNoApp");
removedFromHistory = true;
} else {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (no app)");
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYED: %s (no app)", this);
callServiceTrackeronActivityStatechange(DESTROYED, true);
setState(DESTROYED, "destroyActivityLocked. not finishing and had no app");
}
@@ -2953,9 +2944,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
takeFromHistory();
removeTimeouts();
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (removed from history)");
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to DESTROYED: %s (removed from history)",
+ this);
callServiceTrackeronActivityStatechange(DESTROYED, true);
setState(DESTROYED, "removeFromHistory");
if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this);
@@ -3254,8 +3244,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mTaskSnapshotController.onAppRemoved(this);
mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
+ mStackSupervisor.mStoppingActivities.remove(this);
waitingToShow = false;
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
// Defer removal of this activity when either a child is animating, or app transition is on
// going. App transition animation might be applied on the parent stack not on the activity,
// but the actual frame buffer is associated with the activity, so we have to keep the
@@ -3267,6 +3260,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
} else if (getDisplayContent().mAppTransition.isTransitionSet()) {
getDisplayContent().mClosingApps.add(this);
delayed = true;
+ } else if (mAtmService.getTransitionController().inTransition()) {
+ delayed = true;
}
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
@@ -4098,6 +4093,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return mVisible;
}
+ @Override
+ boolean isVisibleRequested() {
+ return mVisibleRequested;
+ }
+
void setVisible(boolean visible) {
if (visible != mVisible) {
mVisible = visible;
@@ -4228,6 +4228,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
transferStartingWindowFromHiddenAboveTokenIfNeeded();
}
+ // TODO(b/169035022): move to a more-appropriate place.
+ mAtmService.getTransitionController().collect(this);
+ if (!visible && mAtmService.getTransitionController().inTransition()) {
+ return;
+ }
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
// Note that we ignore display frozen since we want the opening / closing transition type
@@ -4461,12 +4466,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
void setState(ActivityState state, String reason) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "State movement: " + this + " from:" + getState()
- + " to:" + state + " reason:" + reason);
+ ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
+ this, getState(), state, reason);
if (state == mState) {
// No need to do anything if state doesn't change.
- if (DEBUG_STATES) Slog.v(TAG_STATES, "State unchanged from:" + state);
+ ProtoLog.v(WM_DEBUG_STATES, "State unchanged from:%s", state);
return;
}
@@ -4489,6 +4494,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
detachChildren();
}
+ if (app != null) {
+ app.invalidateOomScoreReferenceState(false /* computeNow */);
+ }
switch (state) {
case RESUMED:
@@ -4721,15 +4729,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(this);
}
- private void updateVisibleIgnoringKeyguard(boolean behindFullscreenActivity) {
- // Check whether activity should be visible without Keyguard influence
- visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind)
- && okToShowLocked();
- }
-
/** @return {@code true} if this activity should be made visible. */
private boolean shouldBeVisible(boolean behindFullscreenActivity, boolean ignoringKeyguard) {
- updateVisibleIgnoringKeyguard(behindFullscreenActivity);
+ updateVisibilityIgnoringKeyguard(behindFullscreenActivity);
if (ignoringKeyguard) {
return visibleIgnoringKeyguard;
@@ -4763,20 +4765,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return mStackSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
- void updateVisibility(boolean behindFullscreenActivity) {
- updateVisibleIgnoringKeyguard(behindFullscreenActivity);
- final Task task = getRootTask();
- if (task == null || !visibleIgnoringKeyguard) {
- return;
- }
- // Now check whether it's really visible depending on Keyguard state, and update
- // {@link ActivityStack} internal states.
- // Inform the method if this activity is the top activity of this stack, but exclude the
- // case where this is the top activity in a pinned stack.
- final boolean isTop = this == task.getTopNonFinishingActivity();
- final boolean isTopNotPinnedStack = task.isAttached()
- && task.getDisplayArea().isTopNotFinishNotPinnedStack(task);
- task.updateKeyguardVisibility(this, isTop && isTopNotPinnedStack);
+ void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) {
+ visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind)
+ && okToShowLocked();
}
boolean shouldBeVisible() {
@@ -4868,7 +4859,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (deferHidingClient) {
getRootTask().startPausingLocked(
mStackSupervisor.mUserLeaving /* userLeaving */,
- false /* uiSleeping */, null /* resuming */);
+ false /* uiSleeping */, null /* resuming */, "makeInvisible");
break;
}
case INITIALIZING:
@@ -5036,7 +5027,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
static void activityResumedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (DEBUG_SAVED_STATE) Slog.i(TAG_STATES, "Resumed activity; dropping state of: " + r);
+ ProtoLog.i(WM_DEBUG_STATES, "Resumed activity; dropping state of: %s", r);
if (r == null) {
// If an app reports resumed after a long delay, the record on server side might have
// been removed (e.g. destroy timeout), so the token could be null.
@@ -5112,8 +5103,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
void activityPaused(boolean timeout) {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE,
- "Activity paused: token=" + appToken + ", timeout=" + timeout);
+ ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", appToken,
+ timeout);
final Task stack = getStack();
@@ -5121,8 +5112,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
removePauseTimeout();
if (stack.mPausingActivity == this) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + this
- + (timeout ? " (due to timeout)" : " (pause complete)"));
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this,
+ (timeout ? "(due to timeout)" : " (pause complete)"));
mAtmService.deferWindowLayout();
try {
stack.completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
@@ -5138,8 +5129,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
callServiceTrackeronActivityStatechange(PAUSED, true);
setState(PAUSED, "activityPausedLocked");
if (finishing) {
- if (DEBUG_PAUSE) Slog.v(TAG,
- "Executing finish of failed to pause activity: " + this);
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Executing finish of failed to pause activity: %s", this);
completeFinishing("activityPausedLocked");
}
}
@@ -5156,7 +5147,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
void schedulePauseTimeout() {
pauseTime = SystemClock.uptimeMillis();
mAtmService.mH.postDelayed(mPauseTimeoutRunnable, PAUSE_TIMEOUT);
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
+ ProtoLog.v(WM_DEBUG_STATES, "Waiting for pause to complete...");
}
private void removePauseTimeout() {
@@ -5186,17 +5177,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (isNoHistory()) {
if (!finishing) {
if (!stack.shouldSleepActivities()) {
- if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this);
+ ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s", this);
if (finishIfPossible("stop-no-history", false /* oomAdj */)
!= FINISH_RESULT_CANCELLED) {
resumeKeyDispatchingLocked();
return;
}
} else {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "Not finishing noHistory " + this
- + " on stop because we're just sleeping");
- }
+ ProtoLog.d(WM_DEBUG_STATES, "Not finishing noHistory %s on stop "
+ + "because we're just sleeping", this);
}
}
}
@@ -5207,10 +5196,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
resumeKeyDispatchingLocked();
try {
stopped = false;
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (stop requested)");
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);
callServiceTrackeronActivityStatechange(STOPPING, true);
+
setState(STOPPING, "stopIfPossible");
getRootTask().onARStopTriggered(this);
if (DEBUG_VISIBILITY) {
@@ -5231,7 +5219,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
Slog.w(TAG, "Exception thrown during pause", e);
// Just in case, assume it to be stopped.
stopped = true;
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + this);
+ ProtoLog.v(WM_DEBUG_STATES, "Stop failed; moving to STOPPED: %s", this);
callServiceTrackeronActivityStatechange(STOPPED, true);
setState(STOPPED, "stopIfPossible");
if (deferRelaunchUntilPaused) {
@@ -5261,9 +5249,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
launchCount = 0;
updateTaskDescription(description);
}
- if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + mIcicle);
+ ProtoLog.i(WM_DEBUG_STATES, "Saving icicle of %s: %s", this, mIcicle);
if (!stopped) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)");
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPED: %s (stop complete)", this);
removeStopTimeout();
stopped = true;
if (isStopping) {
@@ -5300,10 +5288,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE
|| (isRootOfTask() && stack.getChildCount() <= 1);
if (scheduleIdle || forceIdle) {
- if (DEBUG_PAUSE) {
- Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" + forceIdle
- + "immediate=" + !idleDelayed);
- }
+ ProtoLog.v(WM_DEBUG_STATES,
+ "Scheduling idle now: forceIdle=%b immediate=%b", forceIdle, !idleDelayed);
+
if (!idleDelayed) {
mStackSupervisor.scheduleIdle();
} else {
@@ -7241,9 +7228,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
} else {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
this);
- if (DEBUG_STATES && !mVisibleRequested) {
- Slog.v(TAG_STATES, "Config is relaunching invisible activity " + this
- + " called by " + Debug.getCallers(4));
+ if (!mVisibleRequested) {
+ ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
+ + "activity %s called by %s", this, Debug.getCallers(4));
}
relaunchActivityLocked(preserveWindow);
}
@@ -7376,9 +7363,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
startFreezingScreenLocked(0);
try {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
- "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + this
- + " callers=" + Debug.getCallers(6));
+ ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
+ (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
forceNewConfig = false;
startRelaunching();
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
@@ -7401,13 +7387,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// request resume if this activity is currently resumed, which implies we aren't
// sleeping.
} catch (RemoteException e) {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
+ ProtoLog.i(WM_DEBUG_STATES, "Relaunch failed %s", e);
}
if (andResume) {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "Resumed after relaunch " + this);
- }
+ ProtoLog.d(WM_DEBUG_STATES, "Resumed after relaunch %s", this);
results = null;
newIntents = null;
mAtmService.getAppWarningsLocked().onResumeActivity(this);
@@ -7981,14 +7965,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final Rect insets = new Rect();
mainWindow.getContentInsets(insets);
InsetUtils.addInsets(insets, getLetterboxInsets());
+
return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
record.mAdapter.mCapturedLeash, !fillsParent(),
- mainWindow.mWinAnimator.mLastClipRect, insets,
+ new Rect(), insets,
getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
record.mAdapter.mStackBounds, task.getWindowConfiguration(),
false /*isNotInRecents*/,
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
- record.mStartBounds);
+ record.mStartBounds, task.getPictureInPictureParams());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 8769404d52ab..0de802030a85 100755
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -44,15 +44,14 @@ import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_IDLE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
@@ -66,7 +65,6 @@ import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISO
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
-import static com.android.server.wm.RootWindowContainer.TAG_STATES;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.Task.ActivityState.DESTROYED;
@@ -138,6 +136,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.TransferPipe;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -766,9 +765,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// While there are activities pausing we skipping starting any new activities until
// pauses are complete. NOTE: that we also do this for activities that are starting in
// the paused state because they will first be resumed then paused on the client side.
- if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
- "realStartActivityLocked: Skipping start of r=" + r
- + " some activities pausing...");
+ ProtoLog.v(WM_DEBUG_STATES,
+ "realStartActivityLocked: Skipping start of r=%s some activities pausing...",
+ r);
return false;
}
@@ -776,6 +775,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
final Task rootTask = task.getRootTask();
beginDeferResume();
+ // The LaunchActivityItem also contains process configuration, so the configuration change
+ // from WindowProcessController#setProcess can be deferred. The major reason is that if
+ // the activity has FixedRotationAdjustments, it needs to be applied with configuration.
+ // In general, this reduces a binder transaction if process configuration is changed.
+ proc.pauseConfigurationDispatch();
try {
r.startFreezingScreenLocked(proc, 0);
@@ -869,9 +873,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// Because we could be starting an Activity in the system process this may not go
// across a Binder interface which would create a new Configuration. Consequently
// we have to always create a new Configuration here.
-
+ final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
final MergedConfiguration mergedConfiguration = new MergedConfiguration(
- proc.getConfiguration(), r.getMergedOverrideConfiguration());
+ procConfig, r.getMergedOverrideConfiguration());
r.setLastReportedConfiguration(mergedConfiguration);
logIfTransactionTooLarge(r.intent, r.getSavedState());
@@ -905,6 +909,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// Schedule transaction.
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
+ if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
+ // If the seq is increased, there should be something changed (e.g. registered
+ // activity configuration).
+ proc.setLastReportedConfiguration(procConfig);
+ }
if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
&& mService.mHasHeavyWeightFeature) {
// This may be a heavy-weight process! Note that the package manager will ensure
@@ -939,6 +948,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
} finally {
endDeferResume();
+ proc.resumeConfigurationDispatch();
}
r.launchFailed = false;
@@ -953,8 +963,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
// current icicle and other state.
- if (DEBUG_STATES) Slog.v(TAG_STATES,
- "Moving to PAUSED: " + r + " (starting in paused state)");
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s "
+ + "(starting in paused state)", r);
r.setState(PAUSED, "realStartActivityLocked");
mRootWindowContainer.executeAppTransitionForAllDisplay();
}
@@ -1109,11 +1119,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
/** Check if caller is allowed to launch activities on specified display. */
boolean isCallerAllowedToLaunchOnDisplay(int callingPid, int callingUid, int launchDisplayId,
ActivityInfo aInfo) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check: displayId=" + launchDisplayId
- + " callingPid=" + callingPid + " callingUid=" + callingUid);
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: displayId=%d callingPid=%d "
+ + "callingUid=%d", launchDisplayId, callingPid, callingUid);
if (callingPid == -1 && callingUid == -1) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check: no caller info, skip check");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: no caller info, skip check");
return true;
}
@@ -1129,8 +1139,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
final int startAnyPerm = mService.checkPermission(INTERNAL_SYSTEM_WINDOW, callingPid,
callingUid);
if (startAnyPerm == PERMISSION_GRANTED) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " allow launch any on display");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch any on display");
return true;
}
@@ -1142,36 +1151,36 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// Limit launching on untrusted displays because their contents can be read from Surface
// by apps that created them.
if ((aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " disallow launch on virtual display for not-embedded activity.");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: disallow launch on "
+ + "virtual display for not-embedded activity.");
return false;
}
// Check if the caller is allowed to embed activities from other apps.
if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
== PERMISSION_DENIED && !uidPresentOnDisplay) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " disallow activity embedding without permission.");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: disallow activity "
+ + "embedding without permission.");
return false;
}
}
if (!displayContent.isPrivate()) {
// Anyone can launch on a public display.
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " allow launch on public display");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch on public "
+ + "display");
return true;
}
// Check if the caller is the owner of the display.
if (display.getOwnerUid() == callingUid) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " allow launch for owner of the display");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch for owner of the"
+ + " display");
return true;
}
if (uidPresentOnDisplay) {
- if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
- + " allow launch for caller present on the display");
+ ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch for caller "
+ + "present on the display");
return true;
}
@@ -1518,13 +1527,13 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
}
- private void removeStackInSurfaceTransaction(Task stack) {
- if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
- removePinnedStackInSurfaceTransaction(stack);
+ private void removeRootTaskInSurfaceTransaction(Task rootTask) {
+ if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ removePinnedStackInSurfaceTransaction(rootTask);
} else {
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
- stack.forAllLeafTasks(c, true /* traverseTopToBottom */);
+ rootTask.forAllLeafTasks(c, true /* traverseTopToBottom */);
c.recycle();
}
}
@@ -1534,12 +1543,12 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
/**
- * Removes the stack associated with the given {@param stack}. If the {@param stack} is the
- * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
- * instead moved back onto the fullscreen stack.
+ * Removes the root task associated with the given {@param task}. If the {@param task} is the
+ * pinned task, then its child tasks are not explicitly removed when the root task is
+ * destroyed, but instead moved back onto the TaskDisplayArea.
*/
- void removeStack(Task stack) {
- mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stack));
+ void removeRootTask(Task task) {
+ mWindowManager.inSurfaceTransaction(() -> removeRootTaskInSurfaceTransaction(task));
}
/**
@@ -1922,9 +1931,10 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord s = mStoppingActivities.get(i);
final boolean animating = s.isAnimating(TRANSITION | PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
- if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
- + " animating=" + animating + " finishing=" + s.finishing);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+ || mService.getTransitionController().inTransition(s);
+ ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ + "finishing=%s", s, s.nowVisible, animating, s.finishing);
if (!animating || mService.mShuttingDown) {
if (!processPausingActivities && s.isState(PAUSING)) {
// Defer processing pausing activities in this iteration and reschedule
@@ -1934,7 +1944,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
continue;
}
- if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);
+ ProtoLog.v(WM_DEBUG_STATES, "Ready to stop: %s", s);
if (readyToStopActivities == null) {
readyToStopActivities = new ArrayList<>();
}
@@ -2151,6 +2161,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// Update the current top activity.
mTopResumedActivity = topStack.mResumedActivity;
scheduleTopResumedActivityStateIfNeeded();
+
+ mService.updateTopApp(mTopResumedActivity);
}
/** Schedule top resumed state change if previous top activity already reported back. */
@@ -2168,7 +2180,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
msg.obj = r;
r.topResumedStateLossTime = SystemClock.uptimeMillis();
mHandler.sendMessageDelayed(msg, TOP_RESUMED_STATE_LOSS_TIMEOUT);
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Waiting for top state to be released by " + r);
+ ProtoLog.v(WM_DEBUG_STATES, "Waiting for top state to be released by %s", r);
}
/**
@@ -2176,10 +2188,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
* activity if needed.
*/
void handleTopResumedStateReleased(boolean timeout) {
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Top resumed state released "
- + (timeout ? " (due to timeout)" : " (transition complete)"));
- }
+ ProtoLog.v(WM_DEBUG_STATES, "Top resumed state released %s",
+ (timeout ? "(due to timeout)" : "(transition complete)"));
+
mHandler.removeMessages(TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG);
if (!mTopResumedActivityWaitingForPrev) {
// Top resumed activity state loss already handled.
@@ -2396,18 +2407,14 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
/** Starts a batch of visibility updates. */
void beginActivityVisibilityUpdate() {
+ if (mVisibilityTransactionDepth == 0) {
+ getKeyguardController().updateVisibility();
+ }
mVisibilityTransactionDepth++;
}
/** Ends a batch of visibility updates. */
- void endActivityVisibilityUpdate(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
- if (mVisibilityTransactionDepth == 1) {
- getKeyguardController().visibilitiesUpdated();
- // commit visibility to activities
- mRootWindowContainer.commitActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
- }
+ void endActivityVisibilityUpdate() {
mVisibilityTransactionDepth--;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5ea2d15a8c6f..9e4c77258414 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -57,14 +57,13 @@ import static android.os.Process.INVALID_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS;
@@ -704,11 +703,15 @@ class ActivityStarter {
mService.updateConfigurationLocked(mRequest.globalConfig, null, false);
}
+ // The original options may have additional info about metrics. The mOptions is not
+ // used here because it may be cleared in setTargetStackIfNeeded.
+ final ActivityOptions originalOptions = mRequest.activityOptions != null
+ ? mRequest.activityOptions.getOriginalOptions() : null;
// Notify ActivityMetricsLogger that the activity has launched.
// ActivityMetricsLogger will then wait for the windows to be drawn and populate
// WaitResult.
mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, res,
- mLastStartActivityRecord, mOptions);
+ mLastStartActivityRecord, originalOptions);
return getExternalResult(mRequest.waitResult == null ? res
: waitForResult(res, mLastStartActivityRecord));
}
@@ -1359,6 +1362,12 @@ class ActivityStarter {
}
return false;
}
+ // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
+ if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
+ Slog.w(TAG, "Background activity start for " + callingPackage
+ + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
+ return false;
+ }
// If we don't have callerApp at this point, no caller was provided to startActivity().
// That's the case for PendingIntent-based starts, since the creator's process might not be
// up and alive. If that's the case, we retrieve the WindowProcessController for the send()
@@ -1395,12 +1404,6 @@ class ActivityStarter {
}
}
}
- // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
- if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
- Slog.w(TAG, "Background activity start for " + callingPackage
- + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
- return false;
- }
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
@@ -1497,9 +1500,10 @@ class ActivityStarter {
// anyone interested in this piece of information.
final Task homeStack = targetTask.getDisplayArea().getRootHomeTask();
final boolean homeTaskVisible = homeStack != null && homeStack.shouldBeVisible(null);
+ final ActivityRecord top = targetTask.getTopNonFinishingActivity();
+ final boolean visible = top != null && top.isVisible();
mService.getTaskChangeNotificationController().notifyActivityRestartAttempt(
- targetTask.getTaskInfo(), homeTaskVisible, clearedTask,
- targetTask.getTopNonFinishingActivity().isVisible());
+ targetTask.getTaskInfo(), homeTaskVisible, clearedTask, visible);
}
}
@@ -1914,10 +1918,8 @@ class ActivityStarter {
// if that is the case, so this is it! And for paranoia, make sure we have
// correctly resumed the top activity.
if (!mMovedToFront && mDoResume) {
- if (DEBUG_TASKS) {
- Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
- + " from " + targetTaskTop);
- }
+ ProtoLog.d(WM_DEBUG_TASKS, "Bring to front target: %s from %s", mTargetStack,
+ targetTaskTop);
mTargetStack.moveToFront("intentActivityFound");
}
resumeTargetStackIfNeeded();
@@ -2569,10 +2571,8 @@ class ActivityStarter {
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
- if (DEBUG_TASKS) {
- Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
- + " in new task " + mStartActivity.getTask());
- }
+ ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s",
+ mStartActivity, mStartActivity.getTask());
if (taskToAffiliate != null) {
mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
index 3c562a6472b2..b5675a903144 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
@@ -43,14 +43,10 @@ public class ActivityTaskManagerDebugConfig {
// Enable all debug log categories for activities.
private static final boolean DEBUG_ALL_ACTIVITIES = DEBUG_ALL || false;
- static final boolean DEBUG_PAUSE = DEBUG_ALL || false;
static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
- static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_STACK = DEBUG_ALL || false;
- static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
public static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
- static final boolean DEBUG_TASKS = DEBUG_ALL || false;
static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
static final boolean DEBUG_APP = DEBUG_ALL_ACTIVITIES || false;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 2dc22ecfc022..96740128879b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -283,7 +283,7 @@ public abstract class ActivityTaskManagerInternal {
/**
* Cancels any currently running recents animation.
*/
- public abstract void cancelRecentsAnimation(boolean restoreHomeStackPosition);
+ public abstract void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition);
/**
* This enforces {@code func} can only be called if either the caller is Recents activity or
@@ -324,7 +324,6 @@ public abstract class ActivityTaskManagerInternal {
public abstract void onProcessRemoved(String name, int uid);
public abstract void onCleanUpApplicationRecord(WindowProcessController proc);
public abstract int getTopProcessState();
- public abstract boolean isHeavyWeightProcess(WindowProcessController proc);
public abstract void clearHeavyWeightProcessIfEquals(WindowProcessController proc);
public abstract void finishHeavyWeightApp();
@@ -499,9 +498,6 @@ public abstract class ActivityTaskManagerInternal {
/** @return the process for the top-most resumed activity in the system. */
public abstract WindowProcessController getTopApp();
- /** Generate oom-score-adjustment rank for all tasks in the system based on z-order. */
- public abstract void rankTaskLayersIfNeeded();
-
/** Destroy all activities. */
public abstract void scheduleDestroyAllActivities(String reason);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 820b7d43dbd9..7398b91e4681 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -70,6 +70,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
@@ -96,9 +97,7 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS;
@@ -123,7 +122,6 @@ import static com.android.server.wm.Task.ActivityState.DESTROYED;
import static com.android.server.wm.Task.ActivityState.DESTROYING;
import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
-import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -325,14 +323,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** Hardware-reported OpenGLES version. */
final int GL_ES_VERSION;
- public static final String DUMP_ACTIVITIES_CMD = "activities" ;
- public static final String DUMP_ACTIVITIES_SHORT_CMD = "a" ;
- public static final String DUMP_LASTANR_CMD = "lastanr" ;
- public static final String DUMP_LASTANR_TRACES_CMD = "lastanr-traces" ;
- public static final String DUMP_STARTER_CMD = "starter" ;
- public static final String DUMP_CONTAINERS_CMD = "containers" ;
- public static final String DUMP_RECENTS_CMD = "recents" ;
- public static final String DUMP_RECENTS_SHORT_CMD = "r" ;
+ public static final String DUMP_ACTIVITIES_CMD = "activities";
+ public static final String DUMP_ACTIVITIES_SHORT_CMD = "a";
+ public static final String DUMP_LASTANR_CMD = "lastanr";
+ public static final String DUMP_LASTANR_TRACES_CMD = "lastanr-traces";
+ public static final String DUMP_STARTER_CMD = "starter";
+ public static final String DUMP_CONTAINERS_CMD = "containers";
+ public static final String DUMP_RECENTS_CMD = "recents";
+ public static final String DUMP_RECENTS_SHORT_CMD = "r";
/** This activity is not being relaunched, or being relaunched for a non-resize reason. */
public static final int RELAUNCH_REASON_NONE = 0;
@@ -388,16 +386,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** All processes we currently have running mapped by pid and uid */
final WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
/** This is the process holding what we currently consider to be the "home" activity. */
- WindowProcessController mHomeProcess;
+ volatile WindowProcessController mHomeProcess;
/** The currently running heavy-weight process, if any. */
- WindowProcessController mHeavyWeightProcess = null;
+ volatile WindowProcessController mHeavyWeightProcess;
boolean mHasHeavyWeightFeature;
boolean mHasLeanbackFeature;
+ /** The process of the top most activity. */
+ volatile WindowProcessController mTopApp;
/**
* This is the process holding the activity the user last visited that is in a different process
* from the one they are currently in.
*/
- WindowProcessController mPreviousProcess;
+ volatile WindowProcessController mPreviousProcess;
/** The time at which the previous process was last visible. */
long mPreviousProcessVisibleTime;
@@ -595,7 +595,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Whether mSleeping can quickly toggled between true/false without the device actually
* display changing states is undefined.
*/
- private boolean mSleeping = false;
+ private volatile boolean mSleeping;
/**
* The mDreaming state is set by the {@link DreamManagerService} when it receives a request to
@@ -608,14 +608,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* The process state used for processes that are running the top activities.
* This changes between TOP and TOP_SLEEPING to following mSleeping.
*/
- int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+ volatile int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
LAYOUT_REASON_CONFIG_CHANGED,
LAYOUT_REASON_VISIBILITY_CHANGED,
})
- @interface LayoutReason {}
+ @interface LayoutReason {
+ }
+
static final int LAYOUT_REASON_CONFIG_CHANGED = 0x1;
static final int LAYOUT_REASON_VISIBILITY_CHANGED = 0x2;
@@ -654,7 +656,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*
* @see #updateResumedAppTrace
*/
- private @Nullable ActivityRecord mTracedResumedActivity;
+ @Nullable
+ private ActivityRecord mTracedResumedActivity;
/** If non-null, we are tracking the time the user spends in the currently focused app. */
AppTimeTracker mCurAppTimeTracker;
@@ -667,7 +670,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*/
CompatModePackages mCompatModePackages;
- private FontScaleSettingObserver mFontScaleSettingObserver;
+ private SettingObserver mSettingsObserver;
WindowOrganizerController mWindowOrganizerController;
TaskOrganizerController mTaskOrganizerController;
@@ -677,16 +680,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private int mDeviceOwnerUid = Process.INVALID_UID;
- private final class FontScaleSettingObserver extends ContentObserver {
+ private final class SettingObserver extends ContentObserver {
private final Uri mFontScaleUri = Settings.System.getUriFor(FONT_SCALE);
private final Uri mHideErrorDialogsUri = Settings.Global.getUriFor(HIDE_ERROR_DIALOGS);
+ private final Uri mForceBoldTextUri = Settings.Secure.getUriFor(
+ Settings.Secure.FORCE_BOLD_TEXT);
- public FontScaleSettingObserver() {
+ SettingObserver() {
super(mH);
final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(mFontScaleUri, false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(mHideErrorDialogsUri, false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(mForceBoldTextUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -699,6 +705,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLock) {
updateShouldShowDialogsLocked(getGlobalConfiguration());
}
+ } else if (mForceBoldTextUri.equals(uri)) {
+ updateForceBoldTextIfNeeded(userId);
}
}
}
@@ -712,6 +720,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
int OOM_ADJUSTMENT = 1;
int LRU_UPDATE = 2;
int PROCESS_CHANGE = 3;
+
int caller() default NONE;
}
@@ -758,7 +767,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
public void installSystemProviders() {
- mFontScaleSettingObserver = new FontScaleSettingObserver();
+ mSettingsObserver = new SettingObserver();
}
public void retrieveSettings(ContentResolver resolver) {
@@ -869,7 +878,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
protected ActivityStackSupervisor createStackSupervisor() {
- final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this, mH.getLooper());
+ final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this,
+ mH.getLooper());
supervisor.initialize();
return supervisor;
}
@@ -954,6 +964,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mLockTaskController;
}
+ TransitionController getTransitionController() {
+ return mWindowOrganizerController.getTransitionController();
+ }
+
/**
* Return the global configuration used by the process corresponding to the input pid. This is
* usually the global configuration with some overrides specific to that process.
@@ -1120,7 +1134,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
throw new IllegalArgumentException("Bad PendingIntent object");
}
- PendingIntentRecord pir = (PendingIntentRecord)target;
+ PendingIntentRecord pir = (PendingIntentRecord) target;
synchronized (mGlobalLock) {
// If this is coming from the currently resumed activity, it is
@@ -1173,14 +1187,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// Look for the original activity in the list...
final int N = resolves != null ? resolves.size() : 0;
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
ResolveInfo rInfo = resolves.get(i);
if (rInfo.activityInfo.packageName.equals(r.packageName)
&& rInfo.activityInfo.name.equals(r.info.name)) {
// We found the current one... the next matching is
// after it.
i++;
- if (i<N) {
+ if (i < N) {
aInfo = resolves.get(i).activityInfo;
}
if (debug) {
@@ -1204,11 +1218,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
intent.setComponent(new ComponentName(
aInfo.applicationInfo.packageName, aInfo.name));
- intent.setFlags(intent.getFlags()&~(
- Intent.FLAG_ACTIVITY_FORWARD_RESULT|
- Intent.FLAG_ACTIVITY_CLEAR_TOP|
- Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
- FLAG_ACTIVITY_NEW_TASK));
+ intent.setFlags(intent.getFlags() & ~(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ | FLAG_ACTIVITY_NEW_TASK));
// Okay now we need to start the new activity, replacing the currently running activity.
// This is a little tricky because we want to start the new one as if the current one is
@@ -1573,11 +1586,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* Start the recents activity to perform the recents animation.
*
- * @param intent The intent to start the recents activity.
+ * @param intent The intent to start the recents activity.
+ * @param eventTime When the (touch) event is triggered to start recents activity.
* @param recentsAnimationRunner Pass {@code null} to only preload the activity.
*/
@Override
- public void startRecentsActivity(Intent intent, @Deprecated IAssistDataReceiver unused,
+ public void startRecentsActivity(Intent intent, long eventTime,
@Nullable IRecentsAnimationRunner recentsAnimationRunner) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "startRecentsActivity()");
final int callingPid = Binder.getCallingPid();
@@ -1597,7 +1611,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (recentsAnimationRunner == null) {
anim.preloadRecentsActivity();
} else {
- anim.startRecentsActivity(recentsAnimationRunner);
+ anim.startRecentsActivity(recentsAnimationRunner, eventTime);
}
}
} finally {
@@ -1629,12 +1643,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*
* If the target display is private or virtual, some restrictions will apply.
*
- * @param displayId Target display id.
- * @param intent Intent used to launch the activity.
+ * @param displayId Target display id.
+ * @param intent Intent used to launch the activity.
* @param resolvedType The MIME type of the intent.
- * @param userId The id of the user for whom the call is made.
+ * @param userId The id of the user for whom the call is made.
* @return {@code true} if a call to start an activity on the target display should succeed and
- * no {@link SecurityException} will be thrown, {@code false} otherwise.
+ * no {@link SecurityException} will be thrown, {@code false} otherwise.
*/
@Override
public final boolean isActivityStartAllowedOnDisplay(int displayId, Intent intent,
@@ -1663,11 +1677,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* This is the internal entry point for handling Activity.finish().
*
- * @param token The Binder token referencing the Activity we want to finish.
+ * @param token The Binder token referencing the Activity we want to finish.
* @param resultCode Result code, if any, from this Activity.
* @param resultData Result data (Intent), if any, from this Activity.
* @param finishTask Whether to finish the task associated with this Activity.
- *
* @return Returns true if the activity successfully finished, or false if it is still running.
*/
@Override
@@ -2161,7 +2174,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public RootTaskInfo getFocusedRootTaskInfo() throws RemoteException {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getFocusedRootTaskInfo()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
Task focusedStack = getTopDisplayFocusedStack();
@@ -2176,19 +2189,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void setFocusedStack(int stackId) {
- mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedStack()");
- ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedStack: stackId=%d", stackId);
+ public void setFocusedRootTask(int taskId) {
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setFocusedRootTask()");
+ ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedRootTask: taskId=%d", taskId);
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- final Task stack = mRootWindowContainer.getStack(stackId);
- if (stack == null) {
- Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
+ final Task task = mRootWindowContainer.getStack(taskId);
+ if (task == null) {
+ Slog.w(TAG, "setFocusedRootTask: No task with id=" + taskId);
return;
}
- final ActivityRecord r = stack.topRunningActivity();
- if (r != null && r.moveFocusableActivityToTop("setFocusedStack")) {
+ final ActivityRecord r = task.topRunningActivity();
+ if (r != null && r.moveFocusableActivityToTop("setFocusedRootTask")) {
mRootWindowContainer.resumeFocusedStacksTopActivities();
}
}
@@ -2242,8 +2255,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- return mStackSupervisor.removeTaskById(taskId, true, REMOVE_FROM_RECENTS,
- "remove-task");
+ final Task task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+ if (task == null) {
+ Slog.w(TAG, "removeTask: No task remove with id=" + taskId);
+ return false;
+ }
+
+ if (task.isLeafTask()) {
+ mStackSupervisor.removeTask(task, true, REMOVE_FROM_RECENTS, "remove-task");
+ } else {
+ mStackSupervisor.removeRootTask(task);
+ }
+ return true;
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2302,14 +2326,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* There are several possible results of this call:
* - if the task is locked, then we will show the lock toast
* - if there is a task behind the provided task, then that task is made visible and resumed as
- * this task is moved to the back
+ * this task is moved to the back
* - otherwise, if there are no other tasks in the stack:
- * - if this task is in the pinned stack, then we remove the stack completely, which will
- * have the effect of moving the task to the top or bottom of the fullscreen stack
- * (depending on whether it is visible)
- * - otherwise, we simply return home and hide this task
+ * - if this task is in the pinned stack, then we remove the stack completely, which will
+ * have the effect of moving the task to the top or bottom of the fullscreen stack
+ * (depending on whether it is visible)
+ * - otherwise, we simply return home and hide this task
*
- * @param token A reference to the activity we wish to move
+ * @param token A reference to the activity we wish to move
* @param nonRoot If false then this only works if the activity is the root
* of a task; if true it will work for any activity in a task.
* @return Returns true if the move completed, false if not.
@@ -2335,7 +2359,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public Rect getTaskBounds(int taskId) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getTaskBounds()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
Rect rect = new Rect();
try {
synchronized (mGlobalLock) {
@@ -2390,8 +2414,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return false;
}
- if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
- + " to windowingMode=" + windowingMode + " toTop=" + toTop);
+ ProtoLog.d(WM_DEBUG_TASKS, "setTaskWindowingMode: moving task=%d "
+ + "to windowingMode=%d toTop=%b", taskId, windowingMode, toTop);
if (!task.isActivityTypeStandardOrUndefined()) {
throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
@@ -2454,7 +2478,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void unhandledBack() {
- mAmInternal.enforceCallingPermission(android.Manifest.permission.FORCE_BACK, "unhandledBack()");
+ mAmInternal.enforceCallingPermission(android.Manifest.permission.FORCE_BACK,
+ "unhandledBack()");
synchronized (mGlobalLock) {
final long origId = Binder.clearCallingIdentity();
@@ -2501,9 +2526,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void moveTaskToFront(IApplicationThread appThread, String callingPackage, int taskId,
int flags, Bundle bOptions) {
- mAmInternal.enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()");
+ mAmInternal.enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+ "moveTaskToFront()");
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToFront: moving taskId=" + taskId);
+ ProtoLog.d(WM_DEBUG_TASKS, "moveTaskToFront: moving taskId=%d", taskId);
synchronized (mGlobalLock) {
moveTaskToFrontLocked(appThread, callingPackage, taskId, flags,
SafeActivityOptions.fromBundle(bOptions));
@@ -2535,7 +2561,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
try {
final Task task = mRootWindowContainer.anyTaskForId(taskId);
if (task == null) {
- Slog.d(TAG, "Could not find task for id: "+ taskId);
+ ProtoLog.d(WM_DEBUG_TASKS, "Could not find task for id: %d", taskId);
SafeActivityOptions.abort(options);
return;
}
@@ -2737,31 +2763,31 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
+ public void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop) {
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToRootTask()");
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
final Task task = mRootWindowContainer.anyTaskForId(taskId);
if (task == null) {
- Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
+ Slog.w(TAG, "moveTaskToRootTask: No task for id=" + taskId);
return;
}
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
- + " to stackId=" + stackId + " toTop=" + toTop);
+ ProtoLog.d(WM_DEBUG_TASKS, "moveTaskToRootTask: moving task=%d to "
+ + "rootTaskId=%d toTop=%b", taskId, rootTaskId, toTop);
- final Task stack = mRootWindowContainer.getStack(stackId);
- if (stack == null) {
+ final Task rootTask = mRootWindowContainer.getStack(rootTaskId);
+ if (rootTask == null) {
throw new IllegalStateException(
- "moveTaskToStack: No stack for stackId=" + stackId);
+ "moveTaskToRootTask: No rootTask for rootTaskId=" + rootTaskId);
}
- if (!stack.isActivityTypeStandardOrUndefined()) {
- throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
- + taskId + " to stack " + stackId);
+ if (!rootTask.isActivityTypeStandardOrUndefined()) {
+ throw new IllegalArgumentException("moveTaskToRootTask: Attempt to move task "
+ + taskId + " to rootTask " + rootTaskId);
}
- task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
- "moveTaskToStack");
+ task.reparent(rootTask, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+ "moveTaskToRootTask");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2772,7 +2798,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Moves the specified task to the primary-split-screen stack.
*
* @param taskId Id of task to move.
- * @param toTop If the task and stack should be moved to the top.
+ * @param toTop If the task and stack should be moved to the top.
* @return Whether the task was successfully put into splitscreen.
*/
@Override
@@ -2849,18 +2875,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
/**
- * Removes stacks in the input windowing modes from the system if they are of activity type
+ * Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
@Override
- public void removeStacksInWindowingModes(int[] windowingModes) {
+ public void removeRootTasksInWindowingModes(int[] windowingModes) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
- "removeStacksInWindowingModes()");
+ "removeRootTasksInWindowingModes()");
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- mRootWindowContainer.removeStacksInWindowingModes(windowingModes);
+ mRootWindowContainer.removeRootTasksInWindowingModes(windowingModes);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2868,14 +2894,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void removeStacksWithActivityTypes(int[] activityTypes) {
+ public void removeRootTasksWithActivityTypes(int[] activityTypes) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
- "removeStacksWithActivityTypes()");
+ "removeRootTasksWithActivityTypes()");
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- mRootWindowContainer.removeStacksWithActivityTypes(activityTypes);
+ mRootWindowContainer.removeRootTasksWithActivityTypes(activityTypes);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2897,7 +2923,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public List<RootTaskInfo> getAllRootTaskInfos() {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllRootTaskInfos()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mRootWindowContainer.getAllRootTaskInfos(INVALID_DISPLAY);
@@ -2910,7 +2936,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public RootTaskInfo getRootTaskInfo(int windowingMode, int activityType) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getRootTaskInfo()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mRootWindowContainer.getRootTaskInfo(windowingMode, activityType);
@@ -2924,7 +2950,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
"getAllRootTaskInfosOnDisplay()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mRootWindowContainer.getAllRootTaskInfos(displayId);
@@ -2938,7 +2964,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public RootTaskInfo getRootTaskInfoOnDisplay(int windowingMode, int activityType,
int displayId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getRootTaskInfoOnDisplay()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mRootWindowContainer.getRootTaskInfo(windowingMode, activityType, displayId);
@@ -2949,14 +2975,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
+ public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()");
final long callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
// Cancel the recents animation synchronously (do not hold the WM lock)
- mWindowManager.cancelRecentsAnimation(restoreHomeStackPosition
+ mWindowManager.cancelRecentsAnimation(restoreHomeRootTaskPosition
? REORDER_MOVE_TO_ORIGINAL_POSITION
: REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation/uid=" + callingUid);
}
@@ -2980,7 +3006,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void startSystemLockTaskMode(int taskId) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "startSystemLockTaskMode");
// This makes inner call to look as if it was initiated by system.
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final Task task = mRootWindowContainer.anyTaskForId(taskId,
@@ -3031,10 +3057,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// - will put the device in fully locked mode (LockTask), if the app is allowlisted
// - will start the pinned mode, otherwise
final int callingUid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
// When a task is locked, dismiss the pinned stack if it exists
- mRootWindowContainer.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
getLockTaskController().startLockTaskMode(task, isSystemCaller, callingUid);
} finally {
@@ -3044,7 +3070,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private void stopLockTaskModeInternal(@Nullable IBinder token, boolean isSystemCaller) {
final int callingUid = Binder.getCallingUid();
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
Task task = null;
@@ -3125,7 +3151,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public List<IBinder> getAppTasks(String callingPackage) {
int callingUid = Binder.getCallingUid();
assertPackageMatchesCallingUid(callingPackage);
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mRecentTasks.getAppTasksList(callingUid, callingPackage);
@@ -3285,8 +3311,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (intent.getSourceBounds() != null) {
intent.setSourceBounds(null);
}
- if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
- if ((intent.getFlags()&Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS) == 0) {
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS) == 0) {
// The caller has added this as an auto-remove task... that makes no
// sense, so turn off auto-remove.
intent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
@@ -3344,7 +3370,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public boolean resizeTask(int taskId, Rect bounds, int resizeMode) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeTask()");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final Task task = mRootWindowContainer.anyTaskForId(taskId,
@@ -3409,7 +3435,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
synchronized (mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
if (mKeyguardShown != keyguardShowing) {
mKeyguardShown = keyguardShowing;
final Message msg = PooledLambda.obtainMessage(
@@ -3456,37 +3482,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void removeStack(int stackId) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
- synchronized (mGlobalLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- final Task stack = mRootWindowContainer.getStack(stackId);
- if (stack == null) {
- Slog.w(TAG, "removeStack: No stack with id=" + stackId);
- return;
- }
- if (!stack.isActivityTypeStandardOrUndefined()) {
- throw new IllegalArgumentException(
- "Removing non-standard stack is not allowed.");
- }
- mStackSupervisor.removeStack(stack);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- @Override
- public void moveStackToDisplay(int stackId, int displayId) {
- mAmInternal.enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveStackToDisplay()");
+ public void moveRootTaskToDisplay(int taskId, int displayId) {
+ mAmInternal.enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveRootTaskToDisplay()");
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
try {
- if (DEBUG_STACK) Slog.d(TAG_STACK, "moveStackToDisplay: moving stackId=" + stackId
- + " to displayId=" + displayId);
- mRootWindowContainer.moveStackToDisplay(stackId, displayId, ON_TOP);
+ ProtoLog.d(WM_DEBUG_TASKS, "moveRootTaskToDisplay: moving taskId=%d to "
+ + "displayId=%d", taskId, displayId);
+ mRootWindowContainer.moveStackToDisplay(taskId, displayId, ON_TOP);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3496,7 +3500,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void toggleFreeformWindowingMode(IBinder token) {
synchronized (mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
@@ -3644,14 +3648,16 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
try {
if (AppGlobals.getPackageManager().isUidPrivileged(callingUid)) {
allowed = true;
- if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid
- + " is using old GET_TASKS but privileged; allowing");
+ ProtoLog.w(WM_DEBUG_TASKS,
+ "%s: caller %d is using old GET_TASKS but privileged; allowing",
+ caller, callingUid);
}
} catch (RemoteException e) {
}
}
- if (DEBUG_TASKS) Slog.w(TAG, caller + ": caller " + callingUid
- + " does not hold REAL_GET_TASKS; limiting output");
+ ProtoLog.w(WM_DEBUG_TASKS,
+ "%s: caller %d does not hold REAL_GET_TASKS; limiting output", caller,
+ callingUid);
}
return allowed;
}
@@ -3814,7 +3820,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public boolean showAssistFromActivity(IBinder token, Bundle args) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
ActivityRecord caller = ActivityRecord.forTokenLocked(token);
@@ -3858,7 +3864,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
try {
activityToCallback.app.getThread().scheduleLocalVoiceInteractionStarted(activity,
voiceInteractor);
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
startRunningVoiceLocked(voiceSession, activityToCallback.info.applicationInfo.uid);
} finally {
@@ -3952,52 +3958,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
- /**
- * Try to place task to provided position. The final position might be different depending on
- * current user and stacks state. The task will be moved to target stack if it's currently in
- * different stack.
- */
- @Override
- public void positionTaskInStack(int taskId, int stackId, int position) {
- mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
- synchronized (mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
- try {
- if (DEBUG_STACK) Slog.d(TAG_STACK, "positionTaskInStack: positioning task="
- + taskId + " in stackId=" + stackId + " at position=" + position);
- final Task task = mRootWindowContainer.anyTaskForId(taskId);
- if (task == null) {
- throw new IllegalArgumentException("positionTaskInStack: no task for id="
- + taskId);
- }
-
- final Task stack = mRootWindowContainer.getStack(stackId);
-
- if (stack == null) {
- throw new IllegalArgumentException("positionTaskInStack: no stack for id="
- + stackId);
- }
- if (!stack.isActivityTypeStandardOrUndefined()) {
- throw new IllegalArgumentException("positionTaskInStack: Attempt to change"
- + " the position of task " + taskId + " in/to non-standard stack");
- }
-
- // TODO: Have the callers of this API call a separate reparent method if that is
- // what they intended to do vs. having this method also do reparenting.
- if (task.getRootTask() == stack) {
- // Change position in current stack.
- stack.positionChildAt(task, position);
- } else {
- // Reparent to new stack.
- task.reparent(stack, position, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
- !DEFER_RESUME, "positionTaskInStack");
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
@Override
public void reportSizeConfigurations(IBinder token, int[] horizontalSizeConfiguration,
int[] verticalSizeConfigurations, int[] smallestSizeConfigurations) {
@@ -4016,34 +3976,34 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void suppressResizeConfigChanges(boolean suppress) throws RemoteException {
- mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "suppressResizeConfigChanges()");
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS,
+ "suppressResizeConfigChanges()");
synchronized (mGlobalLock) {
mSuppressResizeConfigChanges = suppress;
}
}
/**
- * Moves the top activity in the input stackId to the pinned stack.
- *
- * @param stackId Id of stack to move the top activity to pinned stack.
- * @param bounds Bounds to use for pinned stack.
+ * Moves the top activity in the input rootTaskId to the pinned root task.
*
- * @return True if the top activity of the input stack was successfully moved to the pinned
- * stack.
+ * @param rootTaskId Id of root task to move the top activity to pinned root task.
+ * @param bounds Bounds to use for pinned root task.
+ * @return True if the top activity of the input stack was successfully moved to the pinned root
+ * task.
*/
@Override
- public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
+ public boolean moveTopActivityToPinnedRootTask(int rootTaskId, Rect bounds) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
- "moveTopActivityToPinnedStack()");
+ "moveTopActivityToPinnedRootTask()");
synchronized (mGlobalLock) {
if (!mSupportsPictureInPicture) {
- throw new IllegalStateException("moveTopActivityToPinnedStack:"
+ throw new IllegalStateException("moveTopActivityToPinnedRootTask:"
+ "Device doesn't support picture-in-picture mode");
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
- return mRootWindowContainer.moveTopStackActivityToPinnedStack(stackId);
+ return mRootWindowContainer.moveTopStackActivityToPinnedRootTask(rootTaskId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4184,7 +4144,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (params.hasSetAspectRatio()
&& !mWindowManager.isValidPictureInPictureAspectRatio(
- r.mDisplayContent, params.getAspectRatio())) {
+ r.mDisplayContent, params.getAspectRatio())) {
final float minAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
final float maxAspectRatio = mContext.getResources().getFloat(
@@ -4215,11 +4175,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// TODO(b/149338177): remove when CTS no-longer requires it
@Override
- public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
+ public void resizePrimarySplitScreen(Rect dockedBounds, Rect tempDockedTaskBounds,
Rect tempDockedTaskInsetBounds,
Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");
- long ident = Binder.clearCallingIdentity();
+ enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePrimarySplitScreen()");
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final TaskDisplayArea tc = mRootWindowContainer.getDefaultTaskDisplayArea();
@@ -4232,8 +4192,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final Rect primaryRect =
tempDockedTaskInsetBounds != null ? tempDockedTaskInsetBounds
- : (tempDockedTaskBounds != null ? tempDockedTaskBounds
- : dockedBounds);
+ : (tempDockedTaskBounds != null ? tempDockedTaskBounds
+ : dockedBounds);
wct.setBounds(primary.mRemoteToken.toWindowContainerToken(), primaryRect);
Rect otherRect = tempOtherTaskInsetBounds != null ? tempOtherTaskInsetBounds
: tempOtherTaskBounds;
@@ -4502,7 +4462,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void updateLockTaskFeatures(int userId, int flags) {
final int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != SYSTEM_UID) {
- mAmInternal.enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
+ mAmInternal.enforceCallingPermission(
+ android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
"updateLockTaskFeatures()");
}
synchronized (mGlobalLock) {
@@ -4740,7 +4701,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (disableNonVrUi) {
// If we are in a VR mode where Picture-in-Picture mode is unsupported,
// then remove the pinned stack.
- mRootWindowContainer.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ mRootWindowContainer.removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
}
}
});
@@ -4806,11 +4767,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* Clears launch params for the given package.
+ *
* @param packageNames the names of the packages of which the launch params are to be cleared
*/
@Override
public void clearLaunchParamsForPackages(List<String> packageNames) {
- mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS,
"clearLaunchParamsForPackages");
synchronized (mGlobalLock) {
for (int i = 0; i < packageNames.size(); ++i) {
@@ -4825,7 +4787,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*/
@Override
public void setDisplayToSingleTaskInstance(int displayId) {
- mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS,
"setDisplayToSingleTaskInstance");
final long origId = Binder.clearCallingIdentity();
try {
@@ -4844,7 +4806,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*/
@Override
public void requestPictureInPictureMode(IBinder token) throws RemoteException {
- mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS,
"requestPictureInPictureMode");
final long origId = Binder.clearCallingIdentity();
try {
@@ -4940,7 +4902,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
boolean needSep = printedAnything;
boolean printed = ActivityStackSupervisor.printThisActivity(pw,
- mRootWindowContainer.getTopResumedActivity(), dumpPackage, needSep,
+ mRootWindowContainer.getTopResumedActivity(), dumpPackage, needSep,
" ResumedActivity: ", null);
if (printed) {
printedAnything = true;
@@ -4974,19 +4936,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* There are three things that cmd can be:
- * - a flattened component name that matches an existing activity
- * - the cmd arg isn't the flattened component name of an existing activity:
- * dump all activity whose component contains the cmd as a substring
- * - A hex number of the ActivityRecord object instance.
+ * - a flattened component name that matches an existing activity
+ * - the cmd arg isn't the flattened component name of an existing activity:
+ * dump all activity whose component contains the cmd as a substring
+ * - A hex number of the ActivityRecord object instance.
* <p>
* The caller should not hold lock when calling this method because it will wait for the
* activities to complete the dump.
*
- * @param dumpVisibleStacksOnly dump activity with {@param name} only if in a visible stack
- * @param dumpFocusedStackOnly dump activity with {@param name} only if in the focused stack
+ * @param dumpVisibleStacksOnly dump activity with {@param name} only if in a visible stack
+ * @param dumpFocusedStackOnly dump activity with {@param name} only if in the focused stack
*/
protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
- int opti, boolean dumpAll, boolean dumpVisibleStacksOnly, boolean dumpFocusedStackOnly) {
+ int opti, boolean dumpAll, boolean dumpVisibleStacksOnly,
+ boolean dumpFocusedStackOnly) {
ArrayList<ActivityRecord> activities;
synchronized (mGlobalLock) {
@@ -5013,9 +4976,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final Task task = r.getTask();
if (lastTask != task) {
lastTask = task;
- pw.print("TASK "); pw.print(lastTask.affinity);
- pw.print(" id="); pw.print(lastTask.mTaskId);
- pw.print(" userId="); pw.println(lastTask.mUserId);
+ pw.print("TASK ");
+ pw.print(lastTask.affinity);
+ pw.print(" id=");
+ pw.print(lastTask.mTaskId);
+ pw.print(" userId=");
+ pw.println(lastTask.mUserId);
if (dumpAll) {
lastTask.dump(pw, " ");
}
@@ -5035,8 +5001,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
String innerPrefix = prefix + " ";
IApplicationThread appThread = null;
synchronized (mGlobalLock) {
- pw.print(prefix); pw.print("ACTIVITY "); pw.print(r.shortComponentName);
- pw.print(" "); pw.print(Integer.toHexString(System.identityHashCode(r)));
+ pw.print(prefix);
+ pw.print("ACTIVITY ");
+ pw.print(r.shortComponentName);
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(r)));
pw.print(" pid=");
if (r.hasProcess()) {
pw.println(r.app.getPid());
@@ -5095,7 +5064,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public Configuration getConfiguration() {
Configuration ci;
- synchronized(mGlobalLock) {
+ synchronized (mGlobalLock) {
ci = new Configuration(getGlobalConfigurationForCallingPid());
ci.userSetLocale = false;
}
@@ -5409,6 +5378,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ private void updateForceBoldTextIfNeeded(@UserIdInt int userId) {
+ final int forceBoldTextConfig = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FORCE_BOLD_TEXT, Configuration.FORCE_BOLD_TEXT_UNDEFINED, userId);
+ synchronized (mGlobalLock) {
+ if (getGlobalConfiguration().forceBoldText == forceBoldTextConfig) {
+ return;
+ }
+ final Configuration configuration =
+ mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY);
+ configuration.forceBoldText = forceBoldTextConfig;
+ updatePersistentConfiguration(configuration, userId);
+ }
+ }
+
// Actually is sleeping or shutting down or whatever else in the future
// is an inactive state.
boolean isSleepingOrShuttingDownLocked() {
@@ -5588,6 +5571,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mH.sendMessage(m);
}
+ void updateTopApp(ActivityRecord topResumedActivity) {
+ final ActivityRecord top = topResumedActivity != null ? topResumedActivity
+ // If there is no resumed activity, it will choose the pausing activity.
+ : mRootWindowContainer.getTopResumedActivity();
+ mTopApp = top != null ? top.app : null;
+ }
+
void updateActivityUsageStats(ActivityRecord activity, int event) {
ComponentName taskRoot = null;
final Task task = activity.getTask();
@@ -5926,7 +5916,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final IBinder threadBinder = thread.asBinder();
final ArrayMap<String, SparseArray<WindowProcessController>> pmap = mProcessNames.getMap();
- for (int i = pmap.size()-1; i >= 0; i--) {
+ for (int i = pmap.size() - 1; i >= 0; i--) {
final SparseArray<WindowProcessController> procs = pmap.valueAt(i);
for (int j = procs.size() - 1; j >= 0; j--) {
final WindowProcessController proc = procs.valueAt(j);
@@ -5991,7 +5981,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
TimeMigrationUtils.formatMillisWithFixedFormat(System.currentTimeMillis());
sb.append(timeString);
sb.append(": ");
- TimeUtils.formatDuration(SystemClock.uptimeMillis()-startTime, sb);
+ TimeUtils.formatDuration(SystemClock.uptimeMillis() - startTime, sb);
sb.append(" since ");
sb.append(msg);
FileOutputStream fos = new FileOutputStream(tracesFile);
@@ -6014,7 +6004,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
File lastTracesFile = null;
File curTracesFile = null;
- for (int i=9; i>=0; i--) {
+ for (int i = 9; i >= 0; i--) {
String name = String.format(Locale.US, "slow%02d.txt", i);
curTracesFile = new File(tracesDir, name);
if (curTracesFile.exists()) {
@@ -6061,7 +6051,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
case REPORT_TIME_TRACKER_MSG: {
AppTimeTracker tracker = (AppTimeTracker) msg.obj;
tracker.deliverResult(mContext);
- } break;
+ }
+ break;
}
}
}
@@ -6257,7 +6248,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*/
@Override
public void setVr2dDisplayId(int vr2dDisplayId) {
- if (DEBUG_STACK) Slog.d(TAG, "setVr2dDisplayId called for: " + vr2dDisplayId);
+ ProtoLog.d(WM_DEBUG_TASKS, "setVr2dDisplayId called for: %d", vr2dDisplayId);
synchronized (mGlobalLock) {
mVr2dDisplayId = vr2dDisplayId;
}
@@ -6293,8 +6284,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void cancelRecentsAnimation(boolean restoreHomeStackPosition) {
- ActivityTaskManagerService.this.cancelRecentsAnimation(restoreHomeStackPosition);
+ public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
+ ActivityTaskManagerService.this.cancelRecentsAnimation(restoreHomeRootTaskPosition);
}
@Override
@@ -6386,17 +6377,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public int getTopProcessState() {
- synchronized (mGlobalLockWithoutBoost) {
- return mTopProcessState;
- }
- }
-
- @HotPath(caller = HotPath.OOM_ADJUSTMENT)
- @Override
- public boolean isHeavyWeightProcess(WindowProcessController proc) {
- synchronized (mGlobalLockWithoutBoost) {
- return proc == mHeavyWeightProcess;
- }
+ return mTopProcessState;
}
@HotPath(caller = HotPath.PROCESS_CHANGE)
@@ -6428,9 +6409,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public boolean isSleeping() {
- synchronized (mGlobalLockWithoutBoost) {
- return isSleepingLocked();
- }
+ return mSleeping;
}
@Override
@@ -6565,7 +6544,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Set the corresponding display information for the process global configuration. To be
* called when we need to show IME on a different display.
*
- * @param pid The process id associated with the IME window.
+ * @param pid The process id associated with the IME window.
* @param displayId The ID of the display showing the IME.
*/
@Override
@@ -6577,7 +6556,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
if (pid == MY_PID || pid < 0) {
ProtoLog.w(WM_DEBUG_CONFIGURATION,
- "Trying to update display configuration for system/invalid process.");
+ "Trying to update display configuration for system/invalid process.");
return;
}
synchronized (mGlobalLock) {
@@ -6967,7 +6946,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
pw.println();
getActivityStartController().dump(pw, " ", null);
pw.println();
- pw.println("-------------------------------------------------------------------------------");
+ pw.println("-------------------------------------------------------------------"
+ + "------------");
dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
"" /* header */);
@@ -7223,25 +7203,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public WindowProcessController getTopApp() {
- synchronized (mGlobalLockWithoutBoost) {
- if (mRootWindowContainer == null) {
- // Return null if mRootWindowContainer not yet initialize, while update
- // oomadj after AMS created.
- return null;
- }
- final ActivityRecord top = mRootWindowContainer.getTopResumedActivity();
- return top != null ? top.app : null;
- }
- }
-
- @HotPath(caller = HotPath.OOM_ADJUSTMENT)
- @Override
- public void rankTaskLayersIfNeeded() {
- synchronized (mGlobalLockWithoutBoost) {
- if (mRootWindowContainer != null) {
- mRootWindowContainer.rankTaskLayersIfNeeded();
- }
- }
+ return mTopApp;
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index dd1d55b2d54d..3e798790f45e 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -58,7 +58,7 @@ class AppTaskImpl extends IAppTask.Stub {
checkCaller();
synchronized (mService.mGlobalLock) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
// We remove the task from recents to preserve backwards
if (!mService.mStackSupervisor.removeTaskById(mTaskId, false,
@@ -76,7 +76,7 @@ class AppTaskImpl extends IAppTask.Stub {
checkCaller();
synchronized (mService.mGlobalLock) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
Task task = mService.mRootWindowContainer.anyTaskForId(mTaskId,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
@@ -162,7 +162,7 @@ class AppTaskImpl extends IAppTask.Stub {
checkCaller();
synchronized (mService.mGlobalLock) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
Task task = mService.mRootWindowContainer.anyTaskForId(mTaskId,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f76108f332d1..0e47ea8058f1 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -2214,6 +2214,10 @@ public class AppTransition implements Dump {
*/
boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent,
@TransitionFlags int flags, boolean forceOverride) {
+ if (mService.mAtmService.getTransitionController().adaptLegacyPrepare(
+ transit, flags, forceOverride)) {
+ return false;
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d "
+ "Callers=%s",
@@ -2255,7 +2259,7 @@ public class AppTransition implements Dump {
|| transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
}
- private static boolean isKeyguardTransit(int transit) {
+ static boolean isKeyguardTransit(int transit) {
return isKeyguardGoingAwayTransit(transit) || transit == TRANSIT_KEYGUARD_OCCLUDE
|| transit == TRANSIT_KEYGUARD_UNOCCLUDE;
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 6305fda4924c..958a7a8f07f8 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -16,9 +16,13 @@
package com.android.server.wm;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
+
import android.util.ArrayMap;
import android.util.ArraySet;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.util.Set;
/**
@@ -63,25 +67,38 @@ class BLASTSyncEngine {
private void tryFinish() {
if (mRemainingTransactions == 0 && mReady) {
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Finished. Reporting %d "
+ + "containers to %s", BLASTSyncEngine.this.hashCode(), mSyncId,
+ mWindowContainersReady.size(), mListener);
mListener.onTransactionReady(mSyncId, mWindowContainersReady);
mPendingSyncs.remove(mSyncId);
}
}
- public void onTransactionReady(int mSyncId, Set<WindowContainer> windowContainersReady) {
+ public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) {
mRemainingTransactions--;
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Child ready, now ready=%b"
+ + " and waiting on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId,
+ mReady, mRemainingTransactions);
mWindowContainersReady.addAll(windowContainersReady);
tryFinish();
}
void setReady() {
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Set ready",
+ BLASTSyncEngine.this.hashCode(), mSyncId);
mReady = true;
tryFinish();
}
boolean addToSync(WindowContainer wc) {
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Trying to add %s",
+ BLASTSyncEngine.this.hashCode(), mSyncId, wc);
if (wc.prepareForSync(this, mSyncId)) {
mRemainingTransactions++;
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Added %s. now waiting "
+ + "on %d transactions", BLASTSyncEngine.this.hashCode(), mSyncId, wc,
+ mRemainingTransactions);
return true;
}
return false;
@@ -105,6 +122,7 @@ class BLASTSyncEngine {
final int id = mNextSyncId++;
final SyncState s = new SyncState(listener, id);
mPendingSyncs.put(id, s);
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncSet{%x:%d} Start for %s", hashCode(), id, listener);
return id;
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
index 4e742b96c8d0..af6c255a84a5 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
@@ -24,8 +24,8 @@ import android.os.IBinder;
*/
public interface BackgroundActivityStartCallback {
/**
- * The token that allowed the activity start that triggered {@link
- * #onExclusiveTokenActivityStart()}.
+ * The token for which this callback is responsible for deciding whether the app can start
+ * background activities or not.
*
* Ideally this should just return a final variable, don't do anything costly here (don't hold
* any locks).
@@ -33,7 +33,10 @@ public interface BackgroundActivityStartCallback {
IBinder getToken();
/**
- * Called when the background activity start happens.
+ * Returns true if the background activity start due to originating token in {@link #getToken()}
+ * should be allowed or not.
+ *
+ * This will be called holding the WM lock, don't do anything costly here.
*/
- void onExclusiveTokenActivityStart(String packageName);
+ boolean isActivityStartAllowed(int uid, String packageName);
}
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 8bd42f03ff86..e4f8d8ba6399 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -41,6 +41,7 @@ import android.window.IDisplayAreaOrganizer;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
+import java.io.PrintWriter;
import java.util.Comparator;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -71,6 +72,13 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
IDisplayAreaOrganizer mOrganizer;
private final Configuration mTmpConfiguration = new Configuration();
+ /**
+ * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it
+ * can never specify orientation, but shows the fixed-orientation apps below it in the
+ * letterbox; otherwise, it rotates based on the fixed-orientation request.
+ */
+ protected boolean mIgnoreOrientationRequest;
+
DisplayArea(WindowManagerService wms, Type type, String name) {
this(wms, type, name, FEATURE_UNDEFINED);
}
@@ -127,6 +135,52 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
}
}
+ @Override
+ int getOrientation(int candidate) {
+ mLastOrientationSource = null;
+ if (mIgnoreOrientationRequest) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ return super.getOrientation(candidate);
+ }
+
+ /**
+ * Sets whether this {@link DisplayArea} should ignore fixed-orientation request from apps and
+ * windows below it.
+ *
+ * @return Whether the display orientation changed after calling this method.
+ */
+ boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) {
+ if (mIgnoreOrientationRequest == ignoreOrientationRequest) {
+ return false;
+ }
+ mIgnoreOrientationRequest = ignoreOrientationRequest;
+
+ // Check whether we should notify Display to update orientation.
+ if (mDisplayContent == null) {
+ return false;
+ }
+
+ // The orientation request from this DA may now be respected.
+ if (!ignoreOrientationRequest) {
+ return mDisplayContent.updateOrientation();
+ }
+
+ final int lastOrientation = mDisplayContent.getLastOrientation();
+ final WindowContainer lastOrientationSource = mDisplayContent.getLastOrientationSource();
+ if (lastOrientation == SCREEN_ORIENTATION_UNSET
+ || lastOrientation == SCREEN_ORIENTATION_UNSPECIFIED) {
+ // Orientation won't be changed.
+ return false;
+ }
+ if (lastOrientationSource == null || lastOrientationSource.isDescendantOf(this)) {
+ // Try update if the orientation may be affected.
+ return mDisplayContent.updateOrientation();
+ }
+ return false;
+ }
+
/**
* When a {@link DisplayArea} is repositioned, it should only be moved among its siblings of the
* same {@link Type}.
@@ -200,6 +254,14 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
}
@Override
+ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
+ if (mIgnoreOrientationRequest) {
+ pw.println(prefix + "mIgnoreOrientationRequest=true");
+ }
+ }
+
+ @Override
long getProtoFieldId() {
return DISPLAY_AREA;
}
@@ -370,6 +432,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
Comparator.comparingInt(WindowToken::getWindowLayerFromType);
private final Predicate<WindowState> mGetOrientingWindow = w -> {
+ if (!w.isVisible() || !w.mLegacyPolicyVisibilityAfterAnim) {
+ return false;
+ }
final WindowManagerPolicy policy = mWmService.mPolicy;
if (policy.isKeyguardHostWindow(w.mAttrs)) {
if (mWmService.mKeyguardGoingAway) {
@@ -405,6 +470,11 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
@Override
int getOrientation(int candidate) {
+ mLastOrientationSource = null;
+ if (mIgnoreOrientationRequest) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
// Find a window requesting orientation.
final WindowState win = getWindow(mGetOrientingWindow);
diff --git a/services/core/java/com/android/server/wm/DisplayAreaGroup.java b/services/core/java/com/android/server/wm/DisplayAreaGroup.java
new file mode 100644
index 000000000000..bcf8c7c901af
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaGroup.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.content.pm.ActivityInfo.reverseOrientation;
+
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+
+/** The root of a partition of the logical display. */
+class DisplayAreaGroup extends RootDisplayArea {
+
+ DisplayAreaGroup(WindowManagerService wms, String name, int featureId) {
+ super(wms, name, featureId);
+ }
+
+ @Override
+ boolean isOrientationDifferentFromDisplay() {
+ if (mDisplayContent == null) {
+ return false;
+ }
+
+ final Rect bounds = getBounds();
+ final Rect displayBounds = mDisplayContent.getBounds();
+
+ return (bounds.width() < bounds.height())
+ != (displayBounds.width() < displayBounds.height());
+ }
+
+ @ActivityInfo.ScreenOrientation
+ @Override
+ int getOrientation(int candidate) {
+ int orientation = super.getOrientation(candidate);
+
+ // Reverse the requested orientation if the orientation of this DAG is different from the
+ // display, so that when the display rotates to the reversed orientation, this DAG will be
+ // in the requested orientation, so as the requested app.
+ // For example, if the display is 1200x900 (landscape), and this DAG is 600x900 (portrait).
+ // When an app below this DAG is requesting landscape, it should actually request the
+ // display to be portrait, so that the DAG and the app will be in landscape.
+ return isOrientationDifferentFromDisplay() ? reverseOrientation(orientation) : orientation;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index 01c007e381b1..dd92f507a33d 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -52,10 +52,7 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl
public void binderDied() {
synchronized (mGlobalLock) {
mOrganizersByFeatureIds.remove(mFeature);
- mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
- if (da.mOrganizer != mOrganizer) return;
- da.setOrganizer(null);
- });
+ removeOrganizer(mOrganizer);
}
}
}
@@ -112,11 +109,7 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl
organizer.asBinder(), uid);
mOrganizersByFeatureIds.entrySet().removeIf(
entry -> entry.getValue().asBinder() == organizer.asBinder());
-
- mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
- if (da.mOrganizer != organizer) return;
- da.setOrganizer(null);
- });
+ removeOrganizer(organizer);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -151,4 +144,13 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl
// Oh well...
}
}
+
+ private void removeOrganizer(IDisplayAreaOrganizer organizer) {
+ IBinder organizerBinder = organizer.asBinder();
+ mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
+ if (da.mOrganizer != null && da.mOrganizer.asBinder().equals(organizerBinder)) {
+ da.setOrganizer(null);
+ }
+ });
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0044d7498614..0bade7db911f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -28,6 +28,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -88,7 +90,9 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_L
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
+import static com.android.server.wm.DisplayContentProto.CAN_SHOW_IME;
import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
+import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
import static com.android.server.wm.DisplayContentProto.DISPLAY_READY;
@@ -96,6 +100,10 @@ import static com.android.server.wm.DisplayContentProto.DPI;
import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
import static com.android.server.wm.DisplayContentProto.ID;
+import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
+import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
@@ -191,7 +199,6 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
-import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -246,10 +253,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
ActivityTaskManagerService mAtmService;
- /** Unique identifier of this display. */
+ /**
+ * Unique logical identifier of this display.
+ *
+ * @see DisplayInfo#displayId
+ */
final int mDisplayId;
/**
+ * Unique physical identifier of this display. Unlike {@link #mDisplayId} this value can change
+ * at runtime if the underlying physical display changes.
+ *
+ * @see DisplayInfo#uniqueId
+ */
+ @Nullable
+ String mCurrentUniqueDisplayId;
+
+ /**
* We organize all top-level Surfaces into the following layer.
* It contains a few Surfaces which are always on top of others, and omitted from
* Screen-Magnification, for example the strict mode flash or the fullscreen magnification
@@ -464,6 +484,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
ActivityRecord mFocusedApp = null;
+ /** The last focused {@link TaskDisplayArea} on this display. */
+ private TaskDisplayArea mLastFocusedTaskDisplayArea = null;
+
/**
* The launching activity which is using fixed rotation transformation.
*
@@ -547,11 +570,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private SurfaceControl mParentSurfaceControl;
private InputWindowHandle mPortalWindowHandle;
- // Last systemUiVisibility we received from status bar.
- private int mLastStatusBarVisibility = 0;
- // Last systemUiVisibility we dispatched to windows.
- private int mLastDispatchedSystemUiVisibility = 0;
-
/** Corner radius that windows should have in order to match the display. */
private final float mWindowCornerRadius;
@@ -911,6 +929,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAtmService = mWmService.mAtmService;
mDisplay = display;
mDisplayId = display.getDisplayId();
+ mCurrentUniqueDisplayId = display.getUniqueId();
mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer;
mWallpaperController = new WallpaperController(mWmService, this);
display.getDisplayInfo(mDisplayInfo);
@@ -1466,6 +1485,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// intermediate orientation change, it is more stable to freeze the display.
return false;
}
+ if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
+ // If the activity is executing or has done the lifecycle callback, use normal
+ // rotation animation so the display info can be updated immediately (see
+ // updateDisplayAndOrientation). This prevents a compatibility issue such as
+ // calling setRequestedOrientation in Activity#onCreate and then get display info.
+ // If fixed rotation is applied, the display rotation will still be the old one,
+ // unless the client side gets the rotation again after the adjustments arrive.
+ return false;
+ }
} else if (r != topRunningActivity()) {
// If the transition has not started yet, the activity must be the top.
return false;
@@ -1844,7 +1872,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
- outConfig.windowConfiguration.getBounds().set(0, 0, dw, dh);
+ outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
+ outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
final int uiMode = getConfiguration().uiMode;
final DisplayCutout displayCutout =
@@ -2273,6 +2302,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
+ boolean isVisibleRequested() {
+ return isVisible();
+ }
+
+ @Override
void onAppTransitionDone() {
super.onAppTransitionDone();
mWmService.mWindowsChanged = true;
@@ -2313,6 +2347,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
int getOrientation() {
mLastOrientationSource = null;
+ if (mIgnoreOrientationRequest) {
+ // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is ignoring all orientation requests, return %d",
+ mDisplayId, SCREEN_ORIENTATION_UNSPECIFIED);
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
if (mWmService.mDisplayFrozen) {
if (mWmService.mPolicy.isKeyguardLocked()) {
@@ -2327,7 +2368,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return getLastOrientation();
}
}
- return super.getOrientation();
+
+ final int orientation = super.getOrientation();
+ if (orientation == SCREEN_ORIENTATION_UNSET) {
+ // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "No app or window is requesting an orientation, return %d for display id=%d",
+ SCREEN_ORIENTATION_UNSPECIFIED, mDisplayId);
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ return orientation;
}
void updateDisplayInfo() {
@@ -2383,13 +2434,20 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight;
final int newDensity = mDisplayInfo.logicalDensityDpi;
final DisplayCutout newCutout = mDisplayInfo.displayCutout;
+ final String newUniqueId = mDisplayInfo.uniqueId;
final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
|| mInitialDisplayHeight != newHeight
|| mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi
|| !Objects.equals(mInitialDisplayCutout, newCutout);
+ final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
+
+ if (displayMetricsChanged || physicalDisplayChanged) {
+ if (physicalDisplayChanged) {
+ // Reapply the window settings as the underlying physical display has changed.
+ mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ }
- if (displayMetricsChanged) {
// If there is an override set for base values - use it, otherwise use new values.
updateBaseDisplayMetrics(mIsSizeForced ? mBaseDisplayWidth : newWidth,
mIsSizeForced ? mBaseDisplayHeight : newHeight,
@@ -2400,6 +2458,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mInitialDisplayHeight = newHeight;
mInitialDisplayDensity = newDensity;
mInitialDisplayCutout = newCutout;
+ mCurrentUniqueDisplayId = newUniqueId;
reconfigureDisplayLocked();
}
}
@@ -2418,6 +2477,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Update base (override) display metrics. */
void updateBaseDisplayMetrics(int baseWidth, int baseHeight, int baseDensity) {
+ final int originalWidth = mBaseDisplayWidth;
+ final int originalHeight = mBaseDisplayHeight;
+ final int originalDensity = mBaseDisplayDensity;
+
mBaseDisplayWidth = baseWidth;
mBaseDisplayHeight = baseHeight;
mBaseDisplayDensity = baseDensity;
@@ -2432,9 +2495,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
-
- updateBounds();
+ if (mBaseDisplayWidth != originalWidth || mBaseDisplayHeight != originalHeight
+ || mBaseDisplayDensity != originalDensity) {
+ mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+ updateBounds();
+ }
}
/**
@@ -2836,7 +2901,26 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
proto.write(FOCUSED_ROOT_TASK_ID, INVALID_TASK_ID);
}
proto.write(DISPLAY_READY, isReady());
-
+ if (mInputMethodTarget != null) {
+ mInputMethodTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel);
+ }
+ if (mInputMethodInputTarget != null) {
+ mInputMethodInputTarget.dumpDebug(proto, INPUT_METHOD_INPUT_TARGET, logLevel);
+ }
+ if (mInputMethodControlTarget != null
+ && mInputMethodControlTarget.getWindow() != null) {
+ mInputMethodControlTarget.getWindow().dumpDebug(proto, INPUT_METHOD_CONTROL_TARGET,
+ logLevel);
+ }
+ if (mCurrentFocus != null) {
+ mCurrentFocus.dumpDebug(proto, CURRENT_FOCUS, logLevel);
+ }
+ if (mInsetsStateController != null
+ && mInsetsStateController.getImeSourceProvider() != null) {
+ mInsetsStateController.getImeSourceProvider().dumpDebug(proto,
+ IME_INSETS_SOURCE_PROVIDER, logLevel);
+ }
+ proto.write(CAN_SHOW_IME, canShowIme());
proto.end(token);
}
@@ -2887,10 +2971,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
pw.print(" mLastFocus="); pw.println(mLastFocus);
}
pw.print(" mFocusedApp="); pw.println(mFocusedApp);
- if (mLastStatusBarVisibility != 0) {
- pw.print(" mLastStatusBarVisibility=0x");
- pw.println(Integer.toHexString(mLastStatusBarVisibility));
- }
if (mFixedRotationLaunchingApp != null) {
pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
}
@@ -3202,6 +3282,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
throw new IllegalStateException(newFocus + " is not on " + getName()
+ " but " + ((appDisplay != null) ? appDisplay.getName() : "none"));
}
+
+ // Called even if the focused app is not changed in case the app is moved to a different
+ // TaskDisplayArea.
+ setLastFocusedTaskDisplayArea(newFocus.getDisplayArea());
}
if (mFocusedApp == newFocus) {
return false;
@@ -3214,6 +3298,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return true;
}
+ /** Called when the focused {@link TaskDisplayArea} on this display may have changed. */
+ @VisibleForTesting
+ void setLastFocusedTaskDisplayArea(@Nullable TaskDisplayArea taskDisplayArea) {
+ if (taskDisplayArea != null) {
+ mLastFocusedTaskDisplayArea = taskDisplayArea;
+ }
+ }
+
+ /** Gets the last focused {@link TaskDisplayArea} on this display. */
+ TaskDisplayArea getLastFocusedTaskDisplayArea() {
+ return mLastFocusedTaskDisplayArea;
+ }
+
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers");
@@ -3333,6 +3430,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mUpdateImeTarget = updateImeTarget;
WindowState target = getWindow(mComputeImeTargetPredicate);
+ // Keeps the IME target with the last window while swiping up to recents to prevent
+ // flickering due to IME hide animation on top of recents.
+ // TODO(b/166736352): This logic should go away once we switch over target immediately
+ // and do the screenshot to preserve IME on disappearing target
+ if (target != null && curTarget != null && target.isActivityTypeHome()
+ && curTarget.getInsetsState().getSource(ITYPE_IME).isVisible()) {
+ return curTarget;
+ }
// Yet more tricksyness! If this window is a "starting" window, we do actually want
// to be on top of it, but it is not -really- where input will go. So look down below
@@ -3505,7 +3610,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
);
}
- private void updateImeParent() {
+ void updateImeParent() {
final SurfaceControl newParent = computeImeParent();
if (newParent != null) {
getPendingTransaction().reparent(mImeWindowsContainers.mSurfaceControl, newParent);
@@ -3733,50 +3838,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return win != null;
}
- void hideTransientBars() {
- // TODO(b/118118435): Remove this after migration
- final int transientFlags = View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
- statusBarVisibilityChanged(mLastStatusBarVisibility & ~transientFlags);
-
- getInsetsPolicy().hideTransient();
- }
-
- void statusBarVisibilityChanged(int visibility) {
- mLastStatusBarVisibility = visibility;
- updateStatusBarVisibilityLocked(visibility);
- }
-
- private boolean updateStatusBarVisibilityLocked(int visibility) {
- if (mLastDispatchedSystemUiVisibility == visibility) {
- return false;
- }
- final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility)
- // We are only interested in differences of one of the
- // clearable flags...
- & View.SYSTEM_UI_CLEARABLE_FLAGS
- // ...if it has actually been cleared.
- & ~visibility;
-
- mLastDispatchedSystemUiVisibility = visibility;
- if (isDefaultDisplay) {
- mWmService.mInputManager.setSystemUiVisibility(visibility);
- }
- updateSystemUiVisibility(visibility, globalDiff);
- return true;
- }
-
- void updateSystemUiVisibility(int visibility, int globalDiff) {
- forAllWindows(w -> {
- final int curValue = w.mSystemUiVisibility;
- final int diff = (curValue ^ visibility) & globalDiff;
- final int newValue = (curValue & ~diff) | (visibility & diff);
- if (newValue != curValue) {
- w.mSeq++;
- w.mSystemUiVisibility = newValue;
- }
- }, true /* traverseTopToBottom */);
- }
-
void onWindowFreezeTimeout() {
Slog.w(TAG_WM, "Window freeze timeout expired.");
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -4185,6 +4246,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
int getOrientation(int candidate) {
+ if (mIgnoreOrientationRequest) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
// IME does not participate in orientation.
return candidate;
}
@@ -4449,6 +4514,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
void executeAppTransition() {
+ mAtmService.getTransitionController().setReady();
if (mAppTransition.isTransitionSet()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
@@ -4931,18 +4997,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Removes stacks in the input windowing modes from the system if they are of activity type
+ * Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
- void removeStacksInWindowingModes(int... windowingModes) {
+ void removeRootTasksInWindowingModes(int... windowingModes) {
forAllTaskDisplayAreas(taskDisplayArea -> {
- taskDisplayArea.removeStacksInWindowingModes(windowingModes);
+ taskDisplayArea.removeRootTasksInWindowingModes(windowingModes);
});
}
- void removeStacksWithActivityTypes(int... activityTypes) {
+ void removeRootTasksWithActivityTypes(int... activityTypes) {
forAllTaskDisplayAreas(taskDisplayArea -> {
- taskDisplayArea.removeStacksWithActivityTypes(activityTypes);
+ taskDisplayArea.removeRootTasksWithActivityTypes(activityTypes);
});
}
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index f8495b5abdf5..d67f3f967e66 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -16,9 +16,8 @@
package com.android.server.wm;
-import static android.view.Surface.ROTATION_180;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.DisplayFramesProto.CURRENT;
+import static com.android.server.wm.DisplayFramesProto.DOCK;
import static com.android.server.wm.DisplayFramesProto.STABLE_BOUNDS;
import android.annotation.NonNull;
@@ -155,6 +154,8 @@ public class DisplayFrames {
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mStable.dumpDebug(proto, STABLE_BOUNDS);
+ mDock.dumpDebug(proto, DOCK);
+ mCurrent.dumpDebug(proto, CURRENT);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 27b8c8685cc0..de197677808c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -31,6 +31,8 @@ import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
@@ -43,6 +45,7 @@ import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
@@ -159,6 +162,7 @@ import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface;
import android.view.View;
+import android.view.ViewDebug;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Side.InsetsSide;
import android.view.WindowInsets.Type;
@@ -220,7 +224,8 @@ public class DisplayPolicy {
/** Use the transit animation in style resource (see {@link #selectAnimation}). */
static final int ANIMATION_STYLEABLE = 0;
- private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR};
+ private static final int[] SHOW_TYPES_FOR_SWIPE = {ITYPE_NAVIGATION_BAR, ITYPE_STATUS_BAR,
+ ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
private static final int[] SHOW_TYPES_FOR_PANIC = {ITYPE_NAVIGATION_BAR};
private final WindowManagerService mService;
@@ -318,6 +323,16 @@ public class DisplayPolicy {
private WindowState mNavigationBarAlt = null;
@WindowManagerPolicy.AltBarPosition
private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
+ // Alternative climate bar for when flexible insets mapping is used to place a climate bar on
+ // the screen.
+ private WindowState mClimateBarAlt = null;
+ @WindowManagerPolicy.AltBarPosition
+ private int mClimateBarAltPosition = ALT_BAR_UNKNOWN;
+ // Alternative extra nav bar for when flexible insets mapping is used to place an extra nav bar
+ // on the screen.
+ private WindowState mExtraNavBarAlt = null;
+ @WindowManagerPolicy.AltBarPosition
+ private int mExtraNavBarAltPosition = ALT_BAR_UNKNOWN;
/** See {@link #getNavigationBarFrameHeight} */
private int[] mNavigationBarFrameHeightForRotationDefault = new int[4];
@@ -795,7 +810,7 @@ public class DisplayPolicy {
mRefreshRatePolicy = new RefreshRatePolicy(mService,
mDisplayContent.getDisplayInfo(),
- mService.mHighRefreshRateBlacklist);
+ mService.mHighRefreshRateDenylist);
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(mHandler,
mContext, () -> {
@@ -815,6 +830,12 @@ public class DisplayPolicy {
if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
requestTransientBars(mNavigationBarAlt);
}
+ if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
+ requestTransientBars(mClimateBarAlt);
+ }
+ if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
+ requestTransientBars(mExtraNavBarAlt);
+ }
}
void systemReady() {
@@ -1054,7 +1075,6 @@ public class DisplayPolicy {
attrs.hideTimeoutMilliseconds = mAccessibilityManager.getRecommendedTimeoutMillis(
(int) attrs.hideTimeoutMilliseconds,
AccessibilityManager.FLAG_CONTENT_TEXT);
- attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
// Toasts can't be clickable
attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
break;
@@ -1082,6 +1102,12 @@ public class DisplayPolicy {
if (mNavigationBarAlt == win) {
mNavigationBarAltPosition = getAltBarPosition(attrs);
}
+ if (mClimateBarAlt == win) {
+ mClimateBarAltPosition = getAltBarPosition(attrs);
+ }
+ if (mExtraNavBarAlt == win) {
+ mExtraNavBarAltPosition = getAltBarPosition(attrs);
+ }
}
/**
@@ -1160,9 +1186,12 @@ public class DisplayPolicy {
}
if (attrs.providesInsetsTypes != null) {
- mContext.enforcePermission(
- android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
- "DisplayPolicy");
+ // Recents component is allowed to add inset types.
+ if (!mService.mAtmInternal.isCallerRecents(callingUid)) {
+ mContext.enforcePermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
+ "DisplayPolicy");
+ }
enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providesInsetsTypes);
for (@InternalInsetsType int insetType : attrs.providesInsetsTypes) {
@@ -1179,6 +1208,16 @@ public class DisplayPolicy {
return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
+ case ITYPE_CLIMATE_BAR:
+ if (mClimateBarAlt != null && mClimateBarAlt.isAlive()) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ break;
+ case ITYPE_EXTRA_NAVIGATION_BAR:
+ if (mExtraNavBarAlt != null && mExtraNavBarAlt.isAlive()) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ break;
}
}
}
@@ -1292,6 +1331,14 @@ public class DisplayPolicy {
mNavigationBarAlt = win;
mNavigationBarAltPosition = getAltBarPosition(attrs);
break;
+ case ITYPE_CLIMATE_BAR:
+ mClimateBarAlt = win;
+ mClimateBarAltPosition = getAltBarPosition(attrs);
+ break;
+ case ITYPE_EXTRA_NAVIGATION_BAR:
+ mExtraNavBarAlt = win;
+ mExtraNavBarAltPosition = getAltBarPosition(attrs);
+ break;
}
mDisplayContent.setInsetProvider(insetsType, win, null);
}
@@ -1340,6 +1387,8 @@ public class DisplayPolicy {
switch (insetsType) {
case ITYPE_NAVIGATION_BAR:
case ITYPE_STATUS_BAR:
+ case ITYPE_CLIMATE_BAR:
+ case ITYPE_EXTRA_NAVIGATION_BAR:
case ITYPE_CAPTION_BAR:
if (++count > 1) {
throw new IllegalArgumentException(
@@ -1369,6 +1418,12 @@ public class DisplayPolicy {
if (mDisplayContent.isDefaultDisplay) {
mService.mPolicy.setKeyguardCandidateLw(null);
}
+ } else if (mClimateBarAlt == win) {
+ mClimateBarAlt = null;
+ mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
+ } else if (mExtraNavBarAlt == win) {
+ mExtraNavBarAlt = null;
+ mDisplayContent.setInsetProvider(ITYPE_EXTRA_NAVIGATION_BAR, null, null);
}
if (mLastFocusedWindow == win) {
mLastFocusedWindow = null;
@@ -1457,7 +1512,8 @@ public class DisplayPolicy {
return R.anim.dock_left_enter;
}
}
- } else if (win == mStatusBarAlt || win == mNavigationBarAlt) {
+ } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
+ || win == mExtraNavBarAlt) {
if (win.getAttrs().windowAnimations != 0) {
return ANIMATION_STYLEABLE;
}
@@ -1539,9 +1595,9 @@ public class DisplayPolicy {
boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout) {
- final int fl = PolicyControl.getWindowFlags(null, attrs);
+ final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
- final int sysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
+ final int sysUiVis = attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) != 0;
final boolean layoutInScreenAndInsetDecor = layoutInScreen
@@ -2095,7 +2151,7 @@ public class DisplayPolicy {
final WindowManager.LayoutParams attrs = win.getAttrs();
final int type = attrs.type;
- final int fl = PolicyControl.getWindowFlags(win, attrs);
+ final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
@@ -2337,7 +2393,7 @@ public class DisplayPolicy {
final boolean affectsSystemUi = win.canAffectSystemUiFlags();
if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
mService.mPolicy.applyKeyguardPolicyLw(win, imeTarget);
- final int fl = PolicyControl.getWindowFlags(win, attrs);
+ final int fl = attrs.flags;
if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi
&& attrs.type == TYPE_INPUT_METHOD) {
mForcingShowNavBar = true;
@@ -2512,8 +2568,7 @@ public class DisplayPolicy {
return false;
}
final LayoutParams attrs = mTopFullscreenOpaqueWindowState.getAttrs();
- final int fl = PolicyControl.getWindowFlags(null, attrs);
- final int sysui = PolicyControl.getSystemUiVisibility(null, attrs);
+ final int fl = attrs.flags;
final InsetsSource request = mTopFullscreenOpaqueWindowState.getRequestedInsetsState()
.peekSource(ITYPE_STATUS_BAR);
if (WindowManagerDebugConfig.DEBUG) {
@@ -2956,10 +3011,19 @@ public class DisplayPolicy {
}
final InsetsState requestedState = controlTarget.getRequestedInsetsState();
- final @InsetsType int restorePositionTypes = (requestedState.getSourceOrDefaultVisibility(
- ITYPE_NAVIGATION_BAR) ? Type.navigationBars() : 0) | (
- requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR) ? Type.statusBars()
- : 0);
+ final @InsetsType int restorePositionTypes =
+ (requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)
+ ? Type.navigationBars() : 0)
+ | (requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
+ ? Type.statusBars() : 0)
+ | (mExtraNavBarAlt != null
+ && requestedState.getSourceOrDefaultVisibility(
+ ITYPE_EXTRA_NAVIGATION_BAR)
+ ? Type.navigationBars() : 0)
+ | (mClimateBarAlt != null
+ && requestedState.getSourceOrDefaultVisibility(
+ ITYPE_CLIMATE_BAR)
+ ? Type.statusBars() : 0);
if (swipeTarget == mNavigationBar
&& (restorePositionTypes & Type.navigationBars()) != 0) {
@@ -3037,9 +3101,9 @@ public class DisplayPolicy {
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
- final int fullscreenAppearance = updateLightStatusBarLw(0 /* vis */,
+ final int fullscreenAppearance = updateLightStatusBarLw(0 /* appearance */,
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
- final int dockedAppearance = updateLightStatusBarLw(0 /* vis */,
+ final int dockedAppearance = updateLightStatusBarLw(0 /* appearance */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
final boolean inSplitScreen =
mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
@@ -3110,6 +3174,10 @@ public class DisplayPolicy {
}
});
+ if (mDisplayContent.isDefaultDisplay) {
+ mService.mInputManager.setSystemUiLightsOut(
+ isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
+ }
return true;
}
@@ -3122,9 +3190,7 @@ public class DisplayPolicy {
// If the top fullscreen-or-dimming window is also the top fullscreen, respect
// its light flag.
appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
- final int legacyAppearance = InsetsFlags.getAppearance(
- PolicyControl.getSystemUiVisibility(statusColorWin, null));
- appearance |= (statusColorWin.mAttrs.insetsFlags.appearance | legacyAppearance)
+ appearance |= statusColorWin.mAttrs.insetsFlags.appearance
& APPEARANCE_LIGHT_STATUS_BARS;
} else if (statusColorWin.isDimming()) {
// Otherwise if it's dimming, clear the light flag.
@@ -3147,8 +3213,8 @@ public class DisplayPolicy {
final boolean imeWindowCanNavColorWindow = imeWindow != null
&& imeWindow.isVisibleLw()
&& navBarPosition == NAV_BAR_BOTTOM
- && (PolicyControl.getWindowFlags(imeWindow, null)
- & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ && (imeWindow.mAttrs.flags
+ & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
if (opaque != null && opaqueOrDimming == opaque) {
// If the top fullscreen-or-dimming window is also the top fullscreen, respect it
@@ -3170,7 +3236,7 @@ public class DisplayPolicy {
// The IME window and the dimming window are competing. Check if the dimming window can be
// IME target or not.
- if (LayoutParams.mayUseInputMethod(PolicyControl.getWindowFlags(opaqueOrDimming, null))) {
+ if (LayoutParams.mayUseInputMethod(opaqueOrDimming.mAttrs.flags)) {
// The IME window is above the dimming window.
return imeWindow;
} else {
@@ -3444,22 +3510,30 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged);
if (mLastDisableFlags != 0) {
pw.print(prefix); pw.print("mLastDisableFlags=0x");
- pw.print(Integer.toHexString(mLastDisableFlags));
+ pw.println(Integer.toHexString(mLastDisableFlags));
+ }
+ if (mLastAppearance != 0) {
+ pw.print(prefix); pw.print("mLastAppearance=");
+ pw.println(ViewDebug.flagsToString(InsetsFlags.class, "appearance", mLastAppearance));
+ }
+ if (mLastBehavior != 0) {
+ pw.print(prefix); pw.print("mLastBehavior=");
+ pw.println(ViewDebug.flagsToString(InsetsFlags.class, "behavior", mLastBehavior));
}
pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
- pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen);
+ pw.print(" mDreamingLockscreen="); pw.println(mDreamingLockscreen);
if (mStatusBar != null) {
- pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar);
+ pw.print(prefix); pw.print("mStatusBar="); pw.println(mStatusBar);
}
if (mStatusBarAlt != null) {
- pw.print(prefix); pw.print("mStatusBarAlt="); pw.print(mStatusBarAlt);
+ pw.print(prefix); pw.print("mStatusBarAlt="); pw.println(mStatusBarAlt);
pw.print(prefix); pw.print("mStatusBarAltPosition=");
pw.println(mStatusBarAltPosition);
}
if (mNotificationShade != null) {
- pw.print(prefix); pw.print("mExpandedPanel="); pw.print(mNotificationShade);
+ pw.print(prefix); pw.print("mExpandedPanel="); pw.println(mNotificationShade);
}
- pw.print(" isKeyguardShowing="); pw.println(isKeyguardShowing());
+ pw.print(prefix); pw.print("isKeyguardShowing="); pw.println(isKeyguardShowing());
if (mNavigationBar != null) {
pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar);
pw.print(prefix); pw.print("mNavBarOpacityMode="); pw.println(mNavBarOpacityMode);
@@ -3472,6 +3546,16 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mNavigationBarAltPosition=");
pw.println(mNavigationBarAltPosition);
}
+ if (mClimateBarAlt != null) {
+ pw.print(prefix); pw.print("mClimateBarAlt="); pw.println(mClimateBarAlt);
+ pw.print(prefix); pw.print("mClimateBarAltPosition=");
+ pw.println(mClimateBarAltPosition);
+ }
+ if (mExtraNavBarAlt != null) {
+ pw.print(prefix); pw.print("mExtraNavBarAlt="); pw.println(mExtraNavBarAlt);
+ pw.print(prefix); pw.print("mExtraNavBarAltPosition=");
+ pw.println(mExtraNavBarAltPosition);
+ }
if (mFocusedWindow != null) {
pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
}
@@ -3490,9 +3574,9 @@ public class DisplayPolicy {
}
pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
- pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars");
- pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
+ pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
+ pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c8c83a6e34f0..04e37faf0ee4 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -385,13 +385,16 @@ class DisplayWindowSettings {
dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
entry.mUserRotation, entry.mFixedToUserRotation);
- if (entry.mForcedDensity != 0) {
- dc.mBaseDisplayDensity = entry.mForcedDensity;
- }
- if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
- dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight,
- dc.mBaseDisplayDensity);
- }
+ final boolean hasDensityOverride = entry.mForcedDensity != 0;
+ final boolean hasSizeOverride = entry.mForcedWidth != 0 && entry.mForcedHeight != 0;
+ dc.mIsDensityForced = hasDensityOverride;
+ dc.mIsSizeForced = hasSizeOverride;
+
+ final int width = hasSizeOverride ? entry.mForcedWidth : dc.mBaseDisplayWidth;
+ final int height = hasSizeOverride ? entry.mForcedHeight : dc.mBaseDisplayHeight;
+ final int density = hasDensityOverride ? entry.mForcedDensity : dc.mBaseDisplayDensity;
+ dc.updateBaseDisplayMetrics(width, height, density);
+
dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED;
}
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index c5c236416013..14880ed30f24 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -71,7 +71,7 @@ class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
}
private void doTake(IBinder permissionOwner) throws RemoteException {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
for (int i = 0; i < mUris.size(); i++) {
UriGrantsManager.getService().grantUriPermissionFromOwner(
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 6e32d0eddaaf..b80ed6be2256 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -258,16 +258,13 @@ class DragState {
}
class InputInterceptor {
- InputChannel mServerChannel, mClientChannel;
+ InputChannel mClientChannel;
DragInputEventReceiver mInputEventReceiver;
InputApplicationHandle mDragApplicationHandle;
InputWindowHandle mDragWindowHandle;
InputInterceptor(Display display) {
- InputChannel[] channels = InputChannel.openInputChannelPair("drag");
- mServerChannel = channels[0];
- mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel);
+ mClientChannel = mService.mInputManager.createInputChannel("drag");
mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
mService.mH.getLooper(), mDragDropController);
@@ -278,7 +275,7 @@ class DragState {
mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
display.getDisplayId());
mDragWindowHandle.name = "drag";
- mDragWindowHandle.token = mServerChannel.getToken();
+ mDragWindowHandle.token = mClientChannel.getToken();
mDragWindowHandle.layoutParamsFlags = 0;
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -308,13 +305,11 @@ class DragState {
}
void tearDown() {
- mService.mInputManager.unregisterInputChannel(mServerChannel.getToken());
+ mService.mInputManager.removeInputChannel(mClientChannel.getToken());
mInputEventReceiver.dispose();
mInputEventReceiver = null;
mClientChannel.dispose();
- mServerChannel.dispose();
mClientChannel = null;
- mServerChannel = null;
mDragWindowHandle = null;
mDragApplicationHandle = null;
@@ -326,7 +321,7 @@ class DragState {
}
InputChannel getInputChannel() {
- return mInputInterceptor == null ? null : mInputInterceptor.mServerChannel;
+ return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
}
InputWindowHandle getInputWindowHandle() {
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 2a5bf16e09d0..3b89a24184f0 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -184,23 +184,13 @@ class EmbeddedWindowController {
InputChannel openInputChannel() {
final String name = getName();
-
- final InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- mInputChannel = inputChannels[0];
- final InputChannel clientChannel = inputChannels[1];
- mWmService.mInputManager.registerInputChannel(mInputChannel);
-
- if (mInputChannel.getToken() != clientChannel.getToken()) {
- throw new IllegalStateException("Client and Server tokens are expected to"
- + "be the same");
- }
-
- return clientChannel;
+ mInputChannel = mWmService.mInputManager.createInputChannel(name);
+ return mInputChannel;
}
void onRemoved() {
if (mInputChannel != null) {
- mWmService.mInputManager.unregisterInputChannel(mInputChannel.getToken());
+ mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
mInputChannel.dispose();
mInputChannel = null;
}
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 251c01469c6a..231cc9745ce1 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -63,26 +63,8 @@ class EnsureActivitiesVisibleHelper {
}
/**
- * Update visibility to activities.
- * @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
- * @param starting The top most activity in the task.
- * The activity is either starting or resuming.
- * Caller should ensure starting activity is visible.
- *
- */
- void processUpdate(@Nullable ActivityRecord starting) {
- reset(starting, 0 /* configChanges */, false /* preserveWindows */,
- false /* notifyClients */);
- if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible processUpdate behind " + mTop);
- }
-
- mTask.forAllActivities(this::updateActivityVisibility);
- }
-
- /**
- * Commit visibility with an option to also update the configuration of visible activities.
+ * Update and commit visibility with an option to also update the configuration of visible
+ * activities.
* @see Task#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
* @param starting The top most activity in the task.
@@ -95,12 +77,13 @@ class EnsureActivitiesVisibleHelper {
* @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
* be sent to the clients.
*/
- void processCommit(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
+ void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
+ boolean notifyClients) {
reset(starting, configChanges, preserveWindows, notifyClients);
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible processCommit behind " + mTop);
+ Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop
+ + " configChanges=0x" + Integer.toHexString(configChanges));
}
if (mTop != null) {
mTask.checkTranslucentActivityWaiting(mTop);
@@ -114,25 +97,20 @@ class EnsureActivitiesVisibleHelper {
&& (starting == null || !starting.isDescendantOf(mTask));
mTask.forAllActivities(a -> {
- commitActivityVisibility(a, starting, resumeTopActivity);
+ setActivityVisibilityState(a, starting, resumeTopActivity);
});
}
- private boolean isAboveTop(boolean isTop) {
- if (mAboveTop && !isTop) {
- return true;
- }
- mAboveTop = false;
- return false;
- }
-
- private void updateActivityVisibility(ActivityRecord r) {
+ private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
+ final boolean resumeTopActivity) {
final boolean isTop = r == mTop;
- if (isAboveTop(isTop)) {
+ if (mAboveTop && !isTop) {
return;
}
+ mAboveTop = false;
- r.updateVisibility(mBehindFullscreenActivity);
+ r.updateVisibilityIgnoringKeyguard(mBehindFullscreenActivity);
+ final boolean reallyVisible = r.shouldBeVisibleUnchecked();
// Check whether activity should be visible without Keyguard influence
if (r.visibleIgnoringKeyguard) {
@@ -149,36 +127,14 @@ class EnsureActivitiesVisibleHelper {
}
}
- if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) {
- if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Home task: at " + mTask
- + " stackShouldBeVisible=" + mContainerShouldBeVisible
- + " behindFullscreenActivity=" + mBehindFullscreenActivity);
- }
- // No other task in the home stack should be visible behind the home activity.
- // Home activities is usually a translucent activity with the wallpaper behind
- // them. However, when they don't have the wallpaper behind them, we want to
- // show activities in the next application stack behind them vs. another
- // task in the home stack like recents.
- mBehindFullscreenActivity = true;
- }
- }
-
- private void commitActivityVisibility(ActivityRecord r, ActivityRecord starting,
- final boolean resumeTopActivity) {
- final boolean isTop = r == mTop;
- if (isAboveTop(isTop)) {
- return;
- }
-
- final boolean reallyVisible = r.shouldBeVisibleUnchecked();
-
if (reallyVisible) {
if (r.finishing) {
return;
}
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
- + " finishing=" + r.finishing + " state=" + r.getState());
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Make visible? " + r
+ + " finishing=" + r.finishing + " state=" + r.getState());
+ }
// First: if this is not the current activity being started, make
// sure it matches the current configuration.
if (r != mStarting && mNotifyClients) {
@@ -191,8 +147,9 @@ class EnsureActivitiesVisibleHelper {
resumeTopActivity && isTop, r);
} else if (r.mVisibleRequested) {
// If this activity is already visible, then there is nothing to do here.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
- "Skipping: already visible at " + r);
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
+ }
if (r.mClientVisibilityDeferred && mNotifyClients) {
r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
@@ -209,13 +166,29 @@ class EnsureActivitiesVisibleHelper {
// Aggregate current change flags.
mConfigChanges |= r.configChangeFlags;
} else {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
- + " finishing=" + r.finishing + " state=" + r.getState()
- + " stackShouldBeVisible=" + mContainerShouldBeVisible
- + " behindFullscreenActivity=" + mBehindFullscreenActivity
- + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+ + " finishing=" + r.finishing + " state=" + r.getState()
+ + " stackShouldBeVisible=" + mContainerShouldBeVisible
+ + " behindFullscreenActivity=" + mBehindFullscreenActivity
+ + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
+ }
r.makeInvisible();
}
+
+ if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) {
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Home task: at " + mTask
+ + " stackShouldBeVisible=" + mContainerShouldBeVisible
+ + " behindFullscreenActivity=" + mBehindFullscreenActivity);
+ }
+ // No other task in the home stack should be visible behind the home activity.
+ // Home activities is usually a translucent activity with the wallpaper behind
+ // them. However, when they don't have the wallpaper behind them, we want to
+ // show activities in the next application stack behind them vs. another
+ // task in the home stack like recents.
+ mBehindFullscreenActivity = true;
+ }
}
private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges,
@@ -230,12 +203,16 @@ class EnsureActivitiesVisibleHelper {
// This activity needs to be visible, but isn't even running...
// get it started and resume if no other stack in this stack is resumed.
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r);
+ }
if (r != starting) {
r.startFreezingScreenLocked(configChanges);
}
if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
+ }
r.setVisibility(true);
}
if (r != starting) {
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index aab901ebcdb6..6c346090578f 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -26,7 +26,7 @@ option java_package com.android.server.wm
# The Activity Manager failed to pause the given activity.
30012 wm_failed_to_pause (User|1|5),(Token|1|5),(Wanting to pause|3),(Currently pausing|3)
# Attempting to pause the current activity
-30013 wm_pause_activity (User|1|5),(Token|1|5),(Component Name|3),(User Leaving|3)
+30013 wm_pause_activity (User|1|5),(Token|1|5),(Component Name|3),(User Leaving|3),(Reason|3)
# Application process has been started
# An activity is being destroyed:
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateDenylist.java
index aac6b2544c4f..cdc14cd11228 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateDenylist.java
@@ -34,62 +34,62 @@ import java.io.PrintWriter;
/**
* A Denylist for packages that should force the display out of high refresh rate.
*/
-class HighRefreshRateBlacklist {
+class HighRefreshRateDenylist {
- private final ArraySet<String> mBlacklistedPackages = new ArraySet<>();
+ private final ArraySet<String> mDenylistedPackages = new ArraySet<>();
@NonNull
- private final String[] mDefaultBlacklist;
+ private final String[] mDefaultDenylist;
private final Object mLock = new Object();
private DeviceConfigInterface mDeviceConfig;
private OnPropertiesChangedListener mListener = new OnPropertiesChangedListener();
- static HighRefreshRateBlacklist create(@NonNull Resources r) {
- return new HighRefreshRateBlacklist(r, DeviceConfigInterface.REAL);
+ static HighRefreshRateDenylist create(@NonNull Resources r) {
+ return new HighRefreshRateDenylist(r, DeviceConfigInterface.REAL);
}
@VisibleForTesting
- HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
- mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
+ HighRefreshRateDenylist(Resources r, DeviceConfigInterface deviceConfig) {
+ mDefaultDenylist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
mDeviceConfig = deviceConfig;
mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(), mListener);
final String property = mDeviceConfig.getProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_HIGH_REFRESH_RATE_BLACKLIST);
- updateBlacklist(property);
+ updateDenylist(property);
}
- private void updateBlacklist(@Nullable String property) {
+ private void updateDenylist(@Nullable String property) {
synchronized (mLock) {
- mBlacklistedPackages.clear();
+ mDenylistedPackages.clear();
if (property != null) {
String[] packages = property.split(",");
for (String pkg : packages) {
String pkgName = pkg.trim();
if (!pkgName.isEmpty()) {
- mBlacklistedPackages.add(pkgName);
+ mDenylistedPackages.add(pkgName);
}
}
} else {
// If there's no config, or the config has been deleted, fallback to the device's
// default denylist
- for (String pkg : mDefaultBlacklist) {
- mBlacklistedPackages.add(pkg);
+ for (String pkg : mDefaultDenylist) {
+ mDenylistedPackages.add(pkg);
}
}
}
}
- boolean isBlacklisted(String packageName) {
+ boolean isDenylisted(String packageName) {
synchronized (mLock) {
- return mBlacklistedPackages.contains(packageName);
+ return mDenylistedPackages.contains(packageName);
}
}
void dump(PrintWriter pw) {
- pw.println("High Refresh Rate Blacklist");
+ pw.println("High Refresh Rate Denylist");
pw.println(" Packages:");
synchronized (mLock) {
- for (String pkg : mBlacklistedPackages) {
+ for (String pkg : mDenylistedPackages) {
pw.println(" " + pkg);
}
}
@@ -100,13 +100,13 @@ class HighRefreshRateBlacklist {
void dispose() {
mDeviceConfig.removeOnPropertiesChangedListener(mListener);
mDeviceConfig = null;
- mBlacklistedPackages.clear();
+ mDenylistedPackages.clear();
}
private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
if (properties.getKeyset().contains(KEY_HIGH_REFRESH_RATE_BLACKLIST)) {
- updateBlacklist(
+ updateDenylist(
properties.getString(KEY_HIGH_REFRESH_RATE_BLACKLIST, null /*default*/));
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index f0f338534ed2..0813b4f9efe0 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -17,7 +17,11 @@
package com.android.server.wm;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
+import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
+import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
+import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.WindowInsets;
@@ -138,8 +142,9 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider {
&& dcTarget.getParentWindow() == mImeTargetFromIme
&& dcTarget.mSubLayer > mImeTargetFromIme.getWindow().mSubLayer)
|| mImeTargetFromIme == mDisplayContent.getImeFallback()
+ || mImeTargetFromIme == mDisplayContent.mInputMethodInputTarget
|| controlTarget == mImeTargetFromIme
- && (mImeTargetFromIme.getWindow() == null
+ && (mImeTargetFromIme.getWindow() == null
|| !mImeTargetFromIme.getWindow().isClosing());
}
@@ -153,4 +158,15 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider {
pw.println();
}
}
+
+ @Override
+ void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
+ final long token = proto.start(fieldId);
+ super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
+ if (mImeTargetFromIme != null) {
+ mImeTargetFromIme.getWindow().dumpDebug(proto, IME_TARGET_FROM_IME, logLevel);
+ }
+ proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn);
+ proto.end(token);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 8b1a0c93cfa3..02dad3978d42 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
@@ -46,6 +47,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -134,11 +136,8 @@ public class ImmersiveModeConfirmation {
boolean userSetupComplete, boolean navBarEmpty) {
mHandler.removeMessages(H.SHOW);
if (isImmersiveMode) {
- final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
- if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s sConfirmed=%s",
- disabled, sConfirmed));
- if (!disabled
- && (DEBUG_SHOW_EVERY_TIME || !sConfirmed)
+ if (DEBUG) Slog.d(TAG, "immersiveModeChanged() sConfirmed=" + sConfirmed);
+ if ((DEBUG_SHOW_EVERY_TIME || !sConfirmed)
&& userSetupComplete
&& !mVrModeEnabled
&& !navBarEmpty
@@ -339,6 +338,13 @@ public class ImmersiveModeConfirmation {
public boolean onTouchEvent(MotionEvent motion) {
return true;
}
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ // we will be hiding the nav bar, so layout as if it's already hidden
+ return new WindowInsets.Builder(insets).setInsets(
+ Type.systemBars(), Insets.NONE).build();
+ }
}
/**
@@ -359,10 +365,6 @@ public class ImmersiveModeConfirmation {
mClingWindow = new ClingWindowView(mContext, mConfirm);
- // we will be hiding the nav bar, so layout as if it's already hidden
- mClingWindow.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-
// show the confirmation
WindowManager.LayoutParams lp = getClingWindowLayoutParams();
getWindowManager().addView(mClingWindow, lp);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 1d1a2663823c..edb5e853af4f 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -35,7 +35,7 @@ import java.io.PrintWriter;
class InputConsumerImpl implements IBinder.DeathRecipient {
final WindowManagerService mService;
- final InputChannel mServerChannel, mClientChannel;
+ final InputChannel mClientChannel;
final InputApplicationHandle mApplicationHandle;
final InputWindowHandle mWindowHandle;
@@ -58,16 +58,10 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
mClientPid = clientPid;
mClientUser = clientUser;
- InputChannel[] channels = InputChannel.openInputChannelPair(name);
- mServerChannel = channels[0];
+ mClientChannel = mService.mInputManager.createInputChannel(name);
if (inputChannel != null) {
- channels[1].transferTo(inputChannel);
- channels[1].dispose();
- mClientChannel = inputChannel;
- } else {
- mClientChannel = channels[1];
+ mClientChannel.copyTo(inputChannel);
}
- mService.mInputManager.registerInputChannel(mServerChannel);
mApplicationHandle = new InputApplicationHandle(new Binder());
mApplicationHandle.name = name;
@@ -75,7 +69,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
mWindowHandle.name = name;
- mWindowHandle.token = mServerChannel.getToken();
+ mWindowHandle.token = mClientChannel.getToken();
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
mWindowHandle.layoutParamsFlags = 0;
mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -156,9 +150,8 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
}
void disposeChannelsLw(SurfaceControl.Transaction t) {
- mService.mInputManager.unregisterInputChannel(mServerChannel.getToken());
+ mService.mInputManager.removeInputChannel(mClientChannel.getToken());
mClientChannel.dispose();
- mServerChannel.dispose();
t.remove(mInputSurface);
unlinkFromDeathRecipient();
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index acf5f75f7e23..143b657b0437 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -265,6 +265,7 @@ final class InputMonitor {
consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL;
break;
case INPUT_CONSUMER_RECENTS_ANIMATION:
+ consumer.mWindowHandle.focusable = true;
break;
default:
throw new IllegalArgumentException("Illegal input consumer : " + name
@@ -285,10 +286,12 @@ final class InputMonitor {
inputWindowHandle.dispatchingTimeoutMillis = child.getInputDispatchingTimeoutMillis();
inputWindowHandle.visible = isVisible;
inputWindowHandle.focusable = focusable;
+ inputWindowHandle.touchOcclusionMode = child.getTouchOcclusionMode();
inputWindowHandle.hasWallpaper = hasWallpaper;
inputWindowHandle.paused = child.mActivityRecord != null ? child.mActivityRecord.paused : false;
inputWindowHandle.ownerPid = child.mSession.mPid;
inputWindowHandle.ownerUid = child.mSession.mUid;
+ inputWindowHandle.packageName = child.getOwningPackage();
inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
inputWindowHandle.displayId = child.getDisplayId();
@@ -543,7 +546,7 @@ final class InputMonitor {
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
- mRecentsAnimationInputConsumer.mWindowHandle, focusable)) {
+ mRecentsAnimationInputConsumer.mWindowHandle)) {
mRecentsAnimationInputConsumer.show(mInputTransaction, w);
mAddRecentsAnimationInputConsumerHandle = false;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 0c9c87a08800..361788497e11 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -36,6 +36,7 @@ import android.view.InsetsAnimationControlCallbacks;
import android.view.InsetsAnimationControlImpl;
import android.view.InsetsAnimationControlRunner;
import android.view.InsetsController;
+import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
@@ -207,11 +208,21 @@ class InsetsPolicy {
* @see InsetsStateController#getInsetsForDispatch
*/
InsetsState getInsetsForDispatch(WindowState target) {
- InsetsState originalState = mStateController.getInsetsForDispatch(target);
+ final InsetsState originalState = mStateController.getInsetsForDispatch(target);
InsetsState state = originalState;
for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
- state = new InsetsState(state);
- state.setSourceVisible(mShowingTransientTypes.get(i), false);
+ final int type = mShowingTransientTypes.get(i);
+ final InsetsSource originalSource = state.peekSource(type);
+ if (originalSource != null && originalSource.isVisible()) {
+ if (state == originalState) {
+ // The source will be modified, create a non-deep copy to store the new one.
+ state = new InsetsState(originalState);
+ }
+ // Replace the source with a copy in invisible state.
+ final InsetsSource source = new InsetsSource(originalSource);
+ source.setVisible(false);
+ state.addSource(source);
+ }
}
return state;
}
@@ -247,11 +258,14 @@ class InsetsPolicy {
}
}
+ /**
+ * If the caller is not {@link #updateBarControlTarget}, it should call
+ * updateBarControlTarget(mFocusedWin) after this invocation.
+ */
private void abortTransient() {
mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(),
mShowingTransientTypes.toArray());
mShowingTransientTypes.clear();
- updateBarControlTarget(mFocusedWin);
}
private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused,
@@ -285,7 +299,7 @@ class InsetsPolicy {
// fake control to the client, so that it can re-show the bar during this scenario.
return mDummyControlTarget;
}
- if (mPolicy.topAppHidesStatusBar()) {
+ if (!canBeTopFullscreenOpaqueWindow(focusedWin) && mPolicy.topAppHidesStatusBar()) {
// Non-fullscreen focused window should not break the state that the top-fullscreen-app
// window hides status bar.
return mPolicy.getTopFullscreenOpaqueWindow();
@@ -293,6 +307,16 @@ class InsetsPolicy {
return focusedWin;
}
+ private static boolean canBeTopFullscreenOpaqueWindow(@Nullable WindowState win) {
+ // The condition doesn't use WindowState#canAffectSystemUiFlags because the window may
+ // haven't drawn or committed the visibility.
+ final boolean nonAttachedAppWindow = win != null
+ && win.mAttrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && win.mAttrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+ return nonAttachedAppWindow && win.mAttrs.isFullscreen() && !win.isFullyTransparent()
+ && !win.inMultiWindowMode();
+ }
+
private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin,
boolean forceShowsSystemBarsForWindowingMode) {
if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a7f32c09b83b..7defc5ddb3f4 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,11 +16,28 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
+import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
+import static com.android.server.wm.InsetsSourceProviderProto.CONTROL;
+import static com.android.server.wm.InsetsSourceProviderProto.CONTROLLABLE;
+import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
+import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.FINISH_SEAMLESS_ROTATE_FRAME_NUMBER;
+import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
+import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
+import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
+import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET;
+import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
+import static com.android.server.wm.InsetsSourceProviderProto.SERVER_VISIBLE;
+import static com.android.server.wm.InsetsSourceProviderProto.SOURCE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL;
import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED;
@@ -93,6 +110,8 @@ class InsetsSourceProvider {
case ITYPE_STATUS_BAR:
case ITYPE_NAVIGATION_BAR:
case ITYPE_IME:
+ case ITYPE_CLIMATE_BAR:
+ case ITYPE_EXTRA_NAVIGATION_BAR:
mControllable = true;
break;
default:
@@ -448,6 +467,36 @@ class InsetsSourceProvider {
}
}
+ void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
+ final long token = proto.start(fieldId);
+ mSource.dumpDebug(proto, SOURCE);
+ mTmpRect.dumpDebug(proto, FRAME);
+ mFakeControl.dumpDebug(proto, FAKE_CONTROL);
+ if (mControl != null) {
+ mControl.dumpDebug(proto, CONTROL);
+ }
+ if (mControlTarget != null && mControlTarget.getWindow() != null) {
+ mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);
+ }
+ if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) {
+ mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);
+ }
+ if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) {
+ mFakeControlTarget.getWindow().dumpDebug(proto, FAKE_CONTROL_TARGET, logLevel);
+ }
+ if (mAdapter != null && mAdapter.mCapturedLeash != null) {
+ mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
+ }
+ mImeOverrideFrame.dumpDebug(proto, IME_OVERRIDDEN_FRAME);
+ proto.write(IS_LEASH_READY_FOR_DISPATCHING, mIsLeashReadyForDispatching);
+ proto.write(CLIENT_VISIBLE, mClientVisible);
+ proto.write(SERVER_VISIBLE, mServerVisible);
+ proto.write(SEAMLESS_ROTATING, mSeamlessRotating);
+ proto.write(FINISH_SEAMLESS_ROTATE_FRAME_NUMBER, mFinishSeamlessRotateFrameNumber);
+ proto.write(CONTROLLABLE, mControllable);
+ proto.end(token);
+ }
+
private class ControlAdapter implements AnimationAdapter {
private SurfaceControl mCapturedLeash;
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c0bdbff6b761..b59452ffaf36 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_INVALID;
@@ -42,6 +43,7 @@ import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -115,7 +117,7 @@ class InsetsStateController {
}
InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
- final @InternalInsetsType int type = getInsetsTypeForWindowType(attrs.type);
+ final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
final @WindowingMode int windowingMode = token != null
? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
@@ -135,7 +137,9 @@ class InsetsStateController {
return false;
}
- private static @InternalInsetsType int getInsetsTypeForWindowType(int type) {
+ private static @InternalInsetsType
+ int getInsetsTypeForLayoutParams(WindowManager.LayoutParams attrs) {
+ @WindowType int type = attrs.type;
switch (type) {
case TYPE_STATUS_BAR:
return ITYPE_STATUS_BAR;
@@ -143,9 +147,22 @@ class InsetsStateController {
return ITYPE_NAVIGATION_BAR;
case TYPE_INPUT_METHOD:
return ITYPE_IME;
- default:
- return ITYPE_INVALID;
}
+
+ // If not one of the types above, check whether an internal inset mapping is specified.
+ if (attrs.providesInsetsTypes != null) {
+ for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+ switch (insetsType) {
+ case ITYPE_STATUS_BAR:
+ case ITYPE_NAVIGATION_BAR:
+ case ITYPE_CLIMATE_BAR:
+ case ITYPE_EXTRA_NAVIGATION_BAR:
+ return insetsType;
+ }
+ }
+ }
+
+ return ITYPE_INVALID;
}
/** @see #getInsetsForDispatch */
@@ -158,14 +175,15 @@ class InsetsStateController {
state.removeSource(type);
// Navigation bar doesn't get influenced by anything else
- if (type == ITYPE_NAVIGATION_BAR) {
+ if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
state.removeSource(ITYPE_IME);
state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_CLIMATE_BAR);
state.removeSource(ITYPE_CAPTION_BAR);
}
// Status bar doesn't get influenced by caption bar
- if (type == ITYPE_STATUS_BAR) {
+ if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
state.removeSource(ITYPE_CAPTION_BAR);
}
@@ -336,8 +354,12 @@ class InsetsStateController {
@Nullable InsetsControlTarget fakeNavControlling) {
onControlChanged(ITYPE_STATUS_BAR, statusControlling);
onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
+ onControlChanged(ITYPE_CLIMATE_BAR, statusControlling);
+ onControlChanged(ITYPE_EXTRA_NAVIGATION_BAR, navControlling);
onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
+ onControlFakeTargetChanged(ITYPE_CLIMATE_BAR, fakeStatusControlling);
+ onControlFakeTargetChanged(ITYPE_EXTRA_NAVIGATION_BAR, fakeNavControlling);
notifyPendingInsetsControlChanged();
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index bad28ba333ba..71eb18ca4117 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -125,6 +125,14 @@ class KeyguardController {
}
/**
+ *
+ * @return true if the activity is controlling keyguard state.
+ */
+ boolean topActivityOccludesKeyguard(ActivityRecord r) {
+ return getDisplayState(r.getDisplayId()).mTopOccludesActivity == r;
+ }
+
+ /**
* @return {@code true} if the keyguard is going away, {@code false} otherwise.
*/
boolean isKeyguardGoingAway() {
@@ -258,15 +266,14 @@ class KeyguardController {
* @return True if we may show an activity while Keyguard is showing because we are in the
* process of dismissing it anyways, false otherwise.
*/
- boolean canShowActivityWhileKeyguardShowing(ActivityRecord r, boolean dismissKeyguard) {
-
+ boolean canShowActivityWhileKeyguardShowing(ActivityRecord r) {
// Allow to show it when we are about to dismiss Keyguard. This isn't allowed if r is
// already the dismissing activity, in which case we don't allow it to repeatedly dismiss
// Keyguard.
- return dismissKeyguard && canDismissKeyguard() && !mAodShowing
+ return r.containsDismissKeyguardWindow() && canDismissKeyguard() && !mAodShowing
&& (mDismissalRequested
|| (r.canShowWhenLocked()
- && getDisplay(r.getDisplayId()).mDismissingKeyguardActivity != r));
+ && getDisplayState(r.getDisplayId()).mDismissingKeyguardActivity != r));
}
/**
@@ -290,7 +297,7 @@ class KeyguardController {
if (isKeyguardOrAodShowing(r.mDisplayContent.getDisplayId())) {
// If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard
// right away and AOD isn't visible.
- return canShowActivityWhileKeyguardShowing(r, r.containsDismissKeyguardWindow());
+ return canShowActivityWhileKeyguardShowing(r);
} else if (isKeyguardLocked()) {
return canShowWhileOccluded(r.containsDismissKeyguardWindow(), r.canShowWhenLocked());
} else {
@@ -299,16 +306,17 @@ class KeyguardController {
}
/**
- * Makes sure to update lockscreen occluded/dismiss state if needed after completing all
- * visibility updates ({@link ActivityStackSupervisor#endActivityVisibilityUpdate}).
+ * Makes sure to update lockscreen occluded/dismiss/turnScreenOn state if needed before
+ * completing set all visibility
+ * ({@link ActivityStackSupervisor#beginActivityVisibilityUpdate}).
*/
- void visibilitiesUpdated() {
+ void updateVisibility() {
boolean requestDismissKeyguard = false;
for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
- final KeyguardDisplayState state = getDisplay(display.mDisplayId);
- state.visibilitiesUpdated(this, display);
+ final KeyguardDisplayState state = getDisplayState(display.mDisplayId);
+ state.updateVisibility(this, display);
requestDismissKeyguard |= state.mRequestDismissKeyguard;
}
@@ -340,7 +348,6 @@ class KeyguardController {
false /* alwaysKeepCurrent */, 0 /* flags */,
true /* forceOverride */);
updateKeyguardSleepToken(DEFAULT_DISPLAY);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mWindowManager.executeAppTransition();
} finally {
mService.continueWindowLayout();
@@ -370,13 +377,12 @@ class KeyguardController {
&& dc.mAppTransition.getAppTransition() == TRANSIT_KEYGUARD_UNOCCLUDE) {
dc.prepareAppTransition(mBeforeUnoccludeTransit, false /* alwaysKeepCurrent */,
0 /* flags */, true /* forceOverride */);
- mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
mWindowManager.executeAppTransition();
}
}
- private boolean isDisplayOccluded(int displayId) {
- return getDisplay(displayId).mOccluded;
+ boolean isDisplayOccluded(int displayId) {
+ return getDisplayState(displayId).mOccluded;
}
/**
@@ -433,7 +439,7 @@ class KeyguardController {
}
private void updateKeyguardSleepToken(int displayId) {
- final KeyguardDisplayState state = getDisplay(displayId);
+ final KeyguardDisplayState state = getDisplayState(displayId);
if (isKeyguardUnoccludedOrAodShowing(displayId)) {
state.mSleepTokenAcquirer.acquire(displayId);
} else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
@@ -441,7 +447,7 @@ class KeyguardController {
}
}
- private KeyguardDisplayState getDisplay(int displayId) {
+ private KeyguardDisplayState getDisplayState(int displayId) {
KeyguardDisplayState state = mDisplayStates.get(displayId);
if (state == null) {
state = new KeyguardDisplayState(mService, displayId, mSleepTokenAcquirer);
@@ -462,8 +468,11 @@ class KeyguardController {
private static class KeyguardDisplayState {
private final int mDisplayId;
private boolean mOccluded;
+
+ private ActivityRecord mTopOccludesActivity;
private ActivityRecord mDismissingKeyguardActivity;
private ActivityRecord mTopTurnScreenOnActivity;
+
private boolean mRequestDismissKeyguard;
private final ActivityTaskManagerService mService;
private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
@@ -476,58 +485,77 @@ class KeyguardController {
}
void onRemoved() {
+ mTopOccludesActivity = null;
mDismissingKeyguardActivity = null;
mTopTurnScreenOnActivity = null;
mSleepTokenAcquirer.release(mDisplayId);
}
- void visibilitiesUpdated(KeyguardController controller, DisplayContent display) {
+ /**
+ * Updates {@link #mOccluded}, {@link #mTopTurnScreenOnActivity} and
+ * {@link #mDismissingKeyguardActivity} if the top task could be visible.
+ */
+ void updateVisibility(KeyguardController controller, DisplayContent display) {
final boolean lastOccluded = mOccluded;
- final ActivityRecord lastDismissActivity = mDismissingKeyguardActivity;
+
+ final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity;
final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity;
+
mRequestDismissKeyguard = false;
mOccluded = false;
+
+ mTopOccludesActivity = null;
mDismissingKeyguardActivity = null;
mTopTurnScreenOnActivity = null;
- // only top + focusable + visible task can control occluding.
- final Task stack = getStackForControllingOccluding(display);
- if (stack != null) {
- final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
- final ActivityRecord topTurnScreenOn = stack.getTopTurnScreenOnActivity();
- mOccluded = stack.topActivityOccludesKeyguard() || (topDismissing != null
- && stack.topRunningActivity() == topDismissing
- && controller.canShowWhileOccluded(
- true /* dismissKeyguard */,
- false /* showWhenLocked */));
- if (topDismissing != null) {
- mDismissingKeyguardActivity = topDismissing;
- }
- if (topTurnScreenOn != null) {
- mTopTurnScreenOnActivity = topTurnScreenOn;
- }
- // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
- if (mDisplayId != DEFAULT_DISPLAY && stack.mDisplayContent != null) {
- mOccluded |= stack.mDisplayContent.canShowWithInsecureKeyguard()
- && controller.canDismissKeyguard();
+ boolean occludedByActivity = false;
+ final Task task = getRootTaskForControllingOccluding(display);
+ if (task != null) {
+ final ActivityRecord r = task.getTopNonFinishingActivity();
+ if (r != null) {
+ final boolean showWhenLocked = r.canShowWhenLocked();
+ if (r.containsDismissKeyguardWindow()) {
+ mDismissingKeyguardActivity = r;
+ }
+ if (r.getTurnScreenOnFlag()
+ && r.currentLaunchCanTurnScreenOn()) {
+ mTopTurnScreenOnActivity = r;
+ }
+
+ if (showWhenLocked) {
+ mTopOccludesActivity = r;
+ }
+
+ // Only the top activity may control occluded, as we can't occlude the Keyguard
+ // if the top app doesn't want to occlude it.
+ occludedByActivity = showWhenLocked || (mDismissingKeyguardActivity != null
+ && task.topRunningActivity() == mDismissingKeyguardActivity
+ && controller.canShowWhileOccluded(
+ true /* dismissKeyguard */, false /* showWhenLocked */));
+ // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
+ if (mDisplayId != DEFAULT_DISPLAY && task.mDisplayContent != null) {
+ occludedByActivity |=
+ task.mDisplayContent.canShowWithInsecureKeyguard()
+ && controller.canDismissKeyguard();
+ }
}
}
// TODO(b/123372519): isShowingDream can only works on default display.
- if (mDisplayId == DEFAULT_DISPLAY) {
- mOccluded |= mService.mRootWindowContainer.getDefaultDisplay()
- .getDisplayPolicy().isShowingDreamLw();
- }
-
- mRequestDismissKeyguard = lastDismissActivity != mDismissingKeyguardActivity
+ mOccluded = occludedByActivity || (mDisplayId == DEFAULT_DISPLAY
+ && mService.mRootWindowContainer.getDefaultDisplay()
+ .getDisplayPolicy().isShowingDreamLw());
+ mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
&& !mOccluded
&& mDismissingKeyguardActivity != null
&& controller.mWindowManager.isKeyguardSecure(
controller.mService.getCurrentUserId());
- if (mTopTurnScreenOnActivity != null
- && mTopTurnScreenOnActivity != lastTurnScreenOnActivity
- && !mService.mWindowManager.mPowerManager.isInteractive()) {
+ if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
+ && mTopTurnScreenOnActivity != null
+ && !mService.mWindowManager.mPowerManager.isInteractive()
+ && (mRequestDismissKeyguard || occludedByActivity)) {
controller.mStackSupervisor.wakeUp("handleTurnScreenOn");
+ mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
if (lastOccluded != mOccluded) {
@@ -542,13 +570,13 @@ class KeyguardController {
* occlusion state.
*/
@Nullable
- private Task getStackForControllingOccluding(DisplayContent display) {
+ private Task getRootTaskForControllingOccluding(DisplayContent display) {
return display.getItemFromTaskDisplayAreas(taskDisplayArea -> {
for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
- final Task stack = taskDisplayArea.getStackAt(sNdx);
- if (stack != null && stack.isFocusableAndVisible()
- && !stack.inPinnedWindowingMode()) {
- return stack;
+ final Task task = taskDisplayArea.getStackAt(sNdx);
+ if (task != null && task.isFocusableAndVisible()
+ && !task.inPinnedWindowingMode()) {
+ return task;
}
}
return null;
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 56e1187d51da..a0074a2d760c 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -144,11 +144,11 @@ class LaunchParamsController {
mTmpParams.mPreferredTaskDisplayArea, true /* onTop */);
}
- if (mTmpParams.hasWindowingMode()
- && mTmpParams.mWindowingMode != task.getRootTask().getWindowingMode()) {
+ if (mTmpParams.hasWindowingMode() && task.isRootTask()
+ && mTmpParams.mWindowingMode != task.getWindowingMode()) {
final int activityType = activity != null
? activity.getActivityType() : task.getActivityType();
- task.getRootTask().setWindowingMode(task.getDisplayArea().validateWindowingMode(
+ task.setWindowingMode(task.getDisplayArea().validateWindowingMode(
mTmpParams.mWindowingMode, activity, task, activityType));
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 4fe678dc1974..44ce4de529b0 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -191,7 +191,6 @@ public class Letterbox {
}
private static class InputInterceptor {
- final InputChannel mServerChannel;
final InputChannel mClientChannel;
final InputWindowHandle mWindowHandle;
final InputEventReceiver mInputEventReceiver;
@@ -201,13 +200,10 @@ public class Letterbox {
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
- final InputChannel[] channels = InputChannel.openInputChannelPair(name);
- mServerChannel = channels[0];
- mClientChannel = channels[1];
+ mClientChannel = mWmService.mInputManager.createInputChannel(name);
mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
- mWmService.mInputManager.registerInputChannel(mServerChannel);
- mToken = mServerChannel.getToken();
+ mToken = mClientChannel.getToken();
mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
win.getDisplayId());
@@ -239,9 +235,8 @@ public class Letterbox {
}
void dispose() {
- mWmService.mInputManager.unregisterInputChannel(mServerChannel.getToken());
+ mWmService.mInputManager.removeInputChannel(mToken);
mInputEventReceiver.dispose();
- mServerChannel.dispose();
mClientChannel.dispose();
}
diff --git a/services/core/java/com/android/server/wm/PolicyControl.java b/services/core/java/com/android/server/wm/PolicyControl.java
deleted file mode 100644
index 61b6e0b25961..000000000000
--- a/services/core/java/com/android/server/wm/PolicyControl.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Runtime adjustments applied to the global window policy.
- *
- * This includes forcing immersive mode behavior for one or both system bars (based on a package
- * list) and permanently disabling immersive mode confirmations for specific packages.
- *
- * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs.
- * e.g.
- * to force immersive mode everywhere:
- * "immersive.full=*"
- * to force transient status for all apps except a specific package:
- * "immersive.status=apps,-com.package"
- * to disable the immersive mode confirmations for specific packages:
- * "immersive.preconfirms=com.package.one,com.package.two"
- *
- * Separate multiple name-value pairs with ':'
- * e.g. "immersive.status=apps:immersive.preconfirms=*"
- */
-class PolicyControl {
- private static final String TAG = "PolicyControl";
- private static final boolean DEBUG = false;
-
- @VisibleForTesting
- static final String NAME_IMMERSIVE_FULL = "immersive.full";
- private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
- private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
- private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms";
-
- private static String sSettingValue;
- private static Filter sImmersivePreconfirmationsFilter;
- private static Filter sImmersiveStatusFilter;
- private static Filter sImmersiveNavigationFilter;
-
- static int getSystemUiVisibility(WindowState win, LayoutParams attrs) {
- attrs = attrs != null ? attrs : win.getAttrs();
- int vis = win != null ? win.getSystemUiVisibility()
- : (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility);
- if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
- vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- | View.SYSTEM_UI_FLAG_FULLSCREEN;
- if (attrs.isFullscreen()) {
- vis |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- }
- vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.STATUS_BAR_TRANSLUCENT);
- }
- if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
- vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- if (attrs.isFullscreen()) {
- vis |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- }
- vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.NAVIGATION_BAR_TRANSLUCENT);
- }
- return vis;
- }
-
- static int getWindowFlags(WindowState win, LayoutParams attrs) {
- attrs = attrs != null ? attrs : win.getAttrs();
- int flags = attrs.flags;
- if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
- flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
- flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
- | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
- }
- if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
- flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
- }
- return flags;
- }
-
- static int adjustClearableFlags(WindowState win, int clearableFlags) {
- final LayoutParams attrs = win != null ? win.getAttrs() : null;
- if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
- clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
- }
- return clearableFlags;
- }
-
- static boolean disableImmersiveConfirmation(String pkg) {
- return (sImmersivePreconfirmationsFilter != null
- && sImmersivePreconfirmationsFilter.matches(pkg))
- || ActivityManager.isRunningInTestHarness();
- }
-
- static boolean reloadFromSetting(Context context) {
- if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
- String value = null;
- try {
- value = Settings.Global.getStringForUser(context.getContentResolver(),
- Settings.Global.POLICY_CONTROL,
- UserHandle.USER_CURRENT);
- if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) {
- return false;
- }
- setFilters(value);
- sSettingValue = value;
- } catch (Throwable t) {
- Slog.w(TAG, "Error loading policy control, value=" + value, t);
- return false;
- }
- return true;
- }
-
- static void dump(String prefix, PrintWriter pw) {
- dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw);
- dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw);
- dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw);
- }
-
- private static void dump(String name, Filter filter, String prefix, PrintWriter pw) {
- pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('=');
- if (filter == null) {
- pw.println("null");
- } else {
- filter.dump(pw); pw.println();
- }
- }
-
- @VisibleForTesting
- static void setFilters(String value) {
- if (DEBUG) Slog.d(TAG, "setFilters: " + value);
- sImmersiveStatusFilter = null;
- sImmersiveNavigationFilter = null;
- sImmersivePreconfirmationsFilter = null;
- if (value != null) {
- String[] nvps = value.split(":");
- for (String nvp : nvps) {
- int i = nvp.indexOf('=');
- if (i == -1) continue;
- String n = nvp.substring(0, i);
- String v = nvp.substring(i + 1);
- if (n.equals(NAME_IMMERSIVE_FULL)) {
- Filter f = Filter.parse(v);
- sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
- if (sImmersivePreconfirmationsFilter == null) {
- sImmersivePreconfirmationsFilter = f;
- }
- } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
- Filter f = Filter.parse(v);
- sImmersiveStatusFilter = f;
- } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
- Filter f = Filter.parse(v);
- sImmersiveNavigationFilter = f;
- if (sImmersivePreconfirmationsFilter == null) {
- sImmersivePreconfirmationsFilter = f;
- }
- } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) {
- Filter f = Filter.parse(v);
- sImmersivePreconfirmationsFilter = f;
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter);
- Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter);
- Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter);
- }
- }
-
- private static class Filter {
- private static final String ALL = "*";
- private static final String APPS = "apps";
-
- private final ArraySet<String> mAllowlist;
- private final ArraySet<String> mDenylist;
-
- private Filter(ArraySet<String> allowlist, ArraySet<String> denylist) {
- mAllowlist = allowlist;
- mDenylist = denylist;
- }
-
- boolean matches(LayoutParams attrs) {
- if (attrs == null) return false;
- boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
- && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
- if (isApp && mDenylist.contains(APPS)) return false;
- if (onDenylist(attrs.packageName)) return false;
- if (isApp && mAllowlist.contains(APPS)) return true;
- return onAllowlist(attrs.packageName);
- }
-
- boolean matches(String packageName) {
- return !onDenylist(packageName) && onAllowlist(packageName);
- }
-
- private boolean onDenylist(String packageName) {
- return mDenylist.contains(packageName) || mDenylist.contains(ALL);
- }
-
- private boolean onAllowlist(String packageName) {
- return mAllowlist.contains(ALL) || mAllowlist.contains(packageName);
- }
-
- void dump(PrintWriter pw) {
- pw.print("Filter[");
- dump("allowlist", mAllowlist, pw); pw.print(',');
- dump("denylist", mDenylist, pw); pw.print(']');
- }
-
- private void dump(String name, ArraySet<String> set, PrintWriter pw) {
- pw.print(name); pw.print("=(");
- final int n = set.size();
- for (int i = 0; i < n; i++) {
- if (i > 0) pw.print(',');
- pw.print(set.valueAt(i));
- }
- pw.print(')');
- }
-
- @Override
- public String toString() {
- StringWriter sw = new StringWriter();
- dump(new PrintWriter(sw, true));
- return sw.toString();
- }
-
- // value = comma-delimited list of tokens, where token = (package name|apps|*)
- // e.g. "com.package1", or "apps, com.android.keyguard" or "*"
- static Filter parse(String value) {
- if (value == null) return null;
- ArraySet<String> allowlist = new ArraySet<String>();
- ArraySet<String> denylist = new ArraySet<String>();
- for (String token : value.split(",")) {
- token = token.trim();
- if (token.startsWith("-") && token.length() > 1) {
- token = token.substring(1);
- denylist.add(token);
- } else {
- allowlist.add(token);
- }
- }
- return new Filter(allowlist, denylist);
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 69a5f4d9219f..f857114bfc82 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -35,10 +35,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Process.SYSTEM_UID;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -73,6 +73,7 @@ import android.view.MotionEvent;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.am.ActivityManagerService;
@@ -158,7 +159,8 @@ class RecentTasks {
*/
private int mRecentsUid = -1;
private ComponentName mRecentsComponent = null;
- private @Nullable String mFeatureId;
+ @Nullable
+ private String mFeatureId;
/**
* Mapping of user id -> whether recent tasks have been loaded for that user.
@@ -399,7 +401,7 @@ class RecentTasks {
/**
* @return whether the given component is the recents component and shares the same uid as the
- * recents component.
+ * recents component.
*/
boolean isRecentsComponent(ComponentName cn, int uid) {
return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
@@ -425,7 +427,8 @@ class RecentTasks {
/**
* @return the featureId for the recents component.
*/
- @Nullable String getRecentsComponentFeatureId() {
+ @Nullable
+ String getRecentsComponentFeatureId() {
return mFeatureId;
}
@@ -619,7 +622,7 @@ class RecentTasks {
/** Remove recent tasks for a user. */
private void removeTasksForUserLocked(int userId) {
- if(userId <= 0) {
+ if (userId <= 0) {
Slog.i(TAG, "Can't remove recent task on user " + userId);
return;
}
@@ -627,8 +630,8 @@ class RecentTasks {
for (int i = mTasks.size() - 1; i >= 0; --i) {
Task task = mTasks.get(i);
if (task.mUserId == userId) {
- if(DEBUG_TASKS) Slog.i(TAG_TASKS,
- "remove RecentTask " + task + " when finishing user" + userId);
+ ProtoLog.i(WM_DEBUG_TASKS, "remove RecentTask %s when finishing user "
+ + "%d", task, userId);
remove(task);
}
}
@@ -642,11 +645,11 @@ class RecentTasks {
&& packageNames.contains(task.realActivity.getPackageName())
&& task.mUserId == userId
&& task.realActivitySuspended != suspended) {
- task.realActivitySuspended = suspended;
- if (suspended) {
- mSupervisor.removeTask(task, false, REMOVE_FROM_RECENTS, "suspended-package");
- }
- notifyTaskPersisterLocked(task, false);
+ task.realActivitySuspended = suspended;
+ if (suspended) {
+ mSupervisor.removeTask(task, false, REMOVE_FROM_RECENTS, "suspended-package");
+ }
+ notifyTaskPersisterLocked(task, false);
}
}
}
@@ -782,25 +785,31 @@ class RecentTasks {
continue;
} else {
// Otherwise just not available for now.
- if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
- "Making recent unavailable: " + task);
+ if (DEBUG_RECENTS && task.isAvailable) {
+ Slog.d(TAG_RECENTS,
+ "Making recent unavailable: " + task);
+ }
task.isAvailable = false;
}
} else {
if (!ai.enabled || !ai.applicationInfo.enabled
|| (ai.applicationInfo.flags
- & ApplicationInfo.FLAG_INSTALLED) == 0) {
- if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
- "Making recent unavailable: " + task
- + " (enabled=" + ai.enabled + "/"
- + ai.applicationInfo.enabled
- + " flags="
- + Integer.toHexString(ai.applicationInfo.flags)
- + ")");
+ & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ if (DEBUG_RECENTS && task.isAvailable) {
+ Slog.d(TAG_RECENTS,
+ "Making recent unavailable: " + task
+ + " (enabled=" + ai.enabled + "/"
+ + ai.applicationInfo.enabled
+ + " flags="
+ + Integer.toHexString(ai.applicationInfo.flags)
+ + ")");
+ }
task.isAvailable = false;
} else {
- if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
- "Making recent available: " + task);
+ if (DEBUG_RECENTS && !task.isAvailable) {
+ Slog.d(TAG_RECENTS,
+ "Making recent available: " + task);
+ }
task.isAvailable = true;
}
}
@@ -976,16 +985,20 @@ class RecentTasks {
final int size = mTasks.size();
for (int i = 0; i < size; i++) {
final Task task = mTasks.get(i);
- if (TaskPersister.DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task
- + " persistable=" + task.isPersistable);
+ if (TaskPersister.DEBUG) {
+ Slog.d(TAG, "LazyTaskWriter: task=" + task
+ + " persistable=" + task.isPersistable);
+ }
final Task rootTask = task.getRootTask();
if ((task.isPersistable || task.inRecents)
&& (rootTask == null || !rootTask.isHomeOrRecentsStack())) {
if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.mTaskId);
} else {
- if (TaskPersister.DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task="
- + task);
+ if (TaskPersister.DEBUG) {
+ Slog.d(TAG, "omitting from persistentTaskIds task="
+ + task);
+ }
}
}
}
@@ -1053,8 +1066,10 @@ class RecentTasks {
// TODO: VI what about if it's just an activity?
// Probably nothing to do here
if (task.voiceSession != null) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "addRecent: not adding voice interaction " + task);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS,
+ "addRecent: not adding voice interaction " + task);
+ }
return;
}
// Another quick case: check if the top-most recent task is the same.
@@ -1066,8 +1081,10 @@ class RecentTasks {
// tasks that are at the top.
if (isAffiliated && recentsCount > 0 && task.inRecents
&& task.mAffiliatedTaskId == mTasks.get(0).mAffiliatedTaskId) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0)
- + " at top when adding " + task);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0)
+ + " at top when adding " + task);
+ }
return;
}
@@ -1124,14 +1141,17 @@ class RecentTasks {
if (other == task.mNextAffiliate) {
// We found the index of our next affiliation, which is who is
// before us in the list, so add after that point.
- taskIndex = otherIndex+1;
+ taskIndex = otherIndex + 1;
} else {
// We found the index of our previous affiliation, which is who is
// after us in the list, so add at their position.
taskIndex = otherIndex;
}
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "addRecent: new affiliated task added at " + taskIndex + ": " + task);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS,
+ "addRecent: new affiliated task added at " + taskIndex + ": "
+ + task);
+ }
mTasks.add(taskIndex, task);
notifyTaskAdded(task);
@@ -1145,13 +1165,17 @@ class RecentTasks {
// everything and then go through our general path of adding a new task.
needAffiliationFix = true;
} else {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "addRecent: couldn't find other affiliation " + other);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS,
+ "addRecent: couldn't find other affiliation " + other);
+ }
needAffiliationFix = true;
}
} else {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
- "addRecent: adding affiliated task without next/prev:" + task);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS,
+ "addRecent: adding affiliated task without next/prev:" + task);
+ }
needAffiliationFix = true;
}
}
@@ -1217,8 +1241,10 @@ class RecentTasks {
final Task task = mTasks.remove(recentsCount - 1);
notifyTaskRemoved(task, true /* wasTrimmed */, false /* killProcess */);
recentsCount--;
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + task
- + " max=" + mGlobalMaxNumTasks);
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG, "Trimming over max-recents task=" + task
+ + " max=" + mGlobalMaxNumTasks);
+ }
}
// Remove any tasks that belong to currently quiet profiles
@@ -1229,13 +1255,15 @@ class RecentTasks {
if (userInfo != null && userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) {
mTmpQuietProfileUserIds.put(userId, true);
}
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "User: " + userInfo
- + " quiet=" + mTmpQuietProfileUserIds.get(userId));
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG, "User: " + userInfo
+ + " quiet=" + mTmpQuietProfileUserIds.get(userId));
+ }
}
// Remove any inactive tasks, calculate the latest set of visible tasks.
int numVisibleTasks = 0;
- for (int i = 0; i < mTasks.size();) {
+ for (int i = 0; i < mTasks.size(); ) {
final Task task = mTasks.get(i);
if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) {
@@ -1259,8 +1287,10 @@ class RecentTasks {
} else {
// Fall through to trim visible tasks that are no longer in range and
// trimmable
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
- "Trimming out-of-range visible task=" + task);
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG,
+ "Trimming out-of-range visible task=" + task);
+ }
}
}
} else {
@@ -1279,8 +1309,10 @@ class RecentTasks {
* @return whether the given task should be considered active.
*/
private boolean isActiveRecentTask(Task task, SparseBooleanArray quietProfileUserIds) {
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isActiveRecentTask: task=" + task
- + " globalMax=" + mGlobalMaxNumTasks);
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG, "isActiveRecentTask: task=" + task
+ + " globalMax=" + mGlobalMaxNumTasks);
+ }
if (quietProfileUserIds.get(task.mUserId)) {
// Quiet profile user's tasks are never active
@@ -1293,8 +1325,10 @@ class RecentTasks {
final Task affiliatedTask = getTask(task.mAffiliatedTaskId);
if (affiliatedTask != null) {
if (!isActiveRecentTask(affiliatedTask, quietProfileUserIds)) {
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
- "\taffiliatedWithTask=" + affiliatedTask + " is not active");
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG,
+ "\taffiliatedWithTask=" + affiliatedTask + " is not active");
+ }
return false;
}
}
@@ -1309,13 +1343,15 @@ class RecentTasks {
*/
@VisibleForTesting
boolean isVisibleRecentTask(Task task) {
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isVisibleRecentTask: task=" + task
- + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks
- + " sessionDuration=" + mActiveTasksSessionDurationMs
- + " inactiveDuration=" + task.getInactiveDuration()
- + " activityType=" + task.getActivityType()
- + " windowingMode=" + task.getWindowingMode()
- + " intentFlags=" + task.getBaseIntent().getFlags());
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG, "isVisibleRecentTask: task=" + task
+ + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks
+ + " sessionDuration=" + mActiveTasksSessionDurationMs
+ + " inactiveDuration=" + task.getInactiveDuration()
+ + " activityType=" + task.getActivityType()
+ + " windowingMode=" + task.getWindowingMode()
+ + " intentFlags=" + task.getBaseIntent().getFlags());
+ }
switch (task.getActivityType()) {
case ACTIVITY_TYPE_HOME:
@@ -1481,8 +1517,10 @@ class RecentTasks {
mHiddenTasks.add(removedTask);
}
notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
- if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask
- + " for addition of task=" + task);
+ if (DEBUG_RECENTS_TRIM_TASKS) {
+ Slog.d(TAG, "Trimming task=" + removedTask
+ + " for addition of task=" + task);
+ }
}
notifyTaskPersisterLocked(removedTask, false /* flush */);
}
@@ -1635,16 +1673,20 @@ class RecentTasks {
top = top.mNextAffiliate;
topIndex--;
}
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding affilliates starting at "
- + topIndex + " from intial " + taskIndex);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS, "addRecent: adding affiliates starting at "
+ + topIndex + " from initial " + taskIndex);
+ }
// Find the end of the chain, doing a validity check along the way.
boolean isValid = top.mAffiliatedTaskId == task.mAffiliatedTaskId;
int endIndex = topIndex;
Task prev = top;
while (endIndex < recentsCount) {
Task cur = mTasks.get(endIndex);
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
- + endIndex + " " + cur);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
+ + endIndex + " " + cur);
+ }
if (cur == top) {
// Verify start of the chain.
if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
@@ -1714,14 +1756,18 @@ class RecentTasks {
if (isValid) {
// All looks good, we can just move all of the affiliated tasks
// to the top.
- for (int i=topIndex; i<=endIndex; i++) {
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
- + " from " + i + " to " + (i-topIndex));
+ for (int i = topIndex; i <= endIndex; i++) {
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
+ + " from " + i + " to " + (i - topIndex));
+ }
Task cur = mTasks.remove(i);
mTasks.add(i - topIndex, cur);
}
- if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex
- + " to " + endIndex);
+ if (DEBUG_RECENTS) {
+ Slog.d(TAG_RECENTS, "addRecent: done moving tasks " + topIndex
+ + " to " + endIndex);
+ }
return true;
}
@@ -1781,7 +1827,9 @@ class RecentTasks {
printedHeader = true;
printedAnything = true;
}
- pw.print(" * Recent #"); pw.print(i); pw.print(": ");
+ pw.print(" * Recent #");
+ pw.print(i);
+ pw.print(": ");
pw.println(task);
if (dumpAll) {
task.dump(pw, " ");
@@ -1800,7 +1848,7 @@ class RecentTasks {
boolean match = taskInfo.baseIntent != null
&& taskInfo.baseIntent.getComponent() != null
&& dumpPackage.equals(
- taskInfo.baseIntent.getComponent().getPackageName());
+ taskInfo.baseIntent.getComponent().getPackageName());
if (!match) {
match |= taskInfo.baseActivity != null
&& dumpPackage.equals(taskInfo.baseActivity.getPackageName());
@@ -1831,7 +1879,9 @@ class RecentTasks {
printedAnything = true;
}
- pw.print(" * RecentTaskInfo #"); pw.print(i); pw.print(": ");
+ pw.print(" * RecentTaskInfo #");
+ pw.print(i);
+ pw.print(": ");
taskInfo.dump(pw, " ");
}
}
@@ -1855,8 +1905,8 @@ class RecentTasks {
/**
* @return Whether the activity types and windowing modes of the two tasks are considered
- * compatible. This is necessary because we currently don't persist the activity type
- * or the windowing mode with the task, so they can be undefined when restored.
+ * compatible. This is necessary because we currently don't persist the activity type
+ * or the windowing mode with the task, so they can be undefined when restored.
*/
private boolean hasCompatibleActivityTypeAndWindowingMode(Task t1, Task t2) {
final int activityType = t1.getActivityType();
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 35338bb67469..1cf50ab25240 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -161,7 +161,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
}
}
- void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
+ void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner, long eventTime) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
@@ -249,8 +249,13 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
// we fetch the visible tasks to be controlled by the animation
mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ ActivityOptions options = null;
+ if (eventTime > 0) {
+ options = ActivityOptions.makeBasic();
+ options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
+ }
mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState,
- START_TASK_TO_FRONT, targetActivity, null /* options */);
+ START_TASK_TO_FRONT, targetActivity, options);
// Register for stack order changes
mDefaultTaskDisplayArea.registerStackOrderChangedListener(this);
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index b50cb4c34398..4ad2575c9490 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -283,6 +283,20 @@ public class RecentsAnimationController implements DeathRecipient {
public void hideCurrentInputMethod() {
final long token = Binder.clearCallingIdentity();
try {
+ synchronized (mService.getWindowManagerLock()) {
+ // Make sure to update the correct IME parent in case that the IME parent may
+ // be computed as display layer when re-layout window happens during rotation
+ // but there is intermediate state that the bounds of task and the IME
+ // target's activity is not the same during rotating.
+ mDisplayContent.updateImeParent();
+
+ // Ignore hiding IME if IME window is attached to app.
+ // Since we would like to snapshot Task with IME window while transitioning
+ // to recents.
+ if (mDisplayContent.isImeAttachedToApp()) {
+ return;
+ }
+ }
final InputMethodManagerInternal inputMethodManagerInternal =
LocalServices.getService(InputMethodManagerInternal.class);
if (inputMethodManagerInternal != null) {
@@ -375,9 +389,7 @@ public class RecentsAnimationController implements DeathRecipient {
final int taskCount = visibleTasks.size();
for (int i = 0; i < taskCount; i++) {
final Task task = visibleTasks.get(i);
- final WindowConfiguration config = task.getWindowConfiguration();
- if (config.tasksAreFloating()
- || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ if (skipAnimation(task)) {
continue;
}
addAnimation(task, !recentTaskIds.get(task.mTaskId));
@@ -420,6 +432,19 @@ public class RecentsAnimationController implements DeathRecipient {
}
}
+
+ /**
+ * Whether a task should be filtered from the recents animation. This can be true for tasks
+ * being displayed outside of recents.
+ */
+ private boolean skipAnimation(Task task) {
+ final WindowConfiguration config = task.getWindowConfiguration();
+ return task.isAlwaysOnTop()
+ || config.tasksAreFloating()
+ || config.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+ }
+
+
@VisibleForTesting
AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
return addAnimation(task, isRecentTaskInvisible, false /* hidden */,
@@ -515,8 +540,9 @@ public class RecentsAnimationController implements DeathRecipient {
void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
if (mRunner != null) {
- // No need to send task appeared when the task target already exists.
- if (isAnimatingTask(task)) {
+ // No need to send task appeared when the task target already exists, or when the
+ // task is being managed as a multi-window mode outside of recents (e.g. bubbles).
+ if (isAnimatingTask(task) || skipAnimation(task)) {
return;
}
final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
@@ -813,15 +839,13 @@ public class RecentsAnimationController implements DeathRecipient {
&& !isTargetApp(activity) && isAnimatingApp(activity);
}
- boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
- boolean focusable) {
+ boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle) {
// Update the input consumer touchable region to match the target app main window
final WindowState targetAppMainWindow = mTargetActivityRecord != null
? mTargetActivityRecord.findMainWindow()
: null;
if (targetAppMainWindow != null) {
targetAppMainWindow.getBounds(mTmpRect);
- inputWindowHandle.focusable = focusable;
inputWindowHandle.touchableRegion.set(mTmpRect);
return true;
}
@@ -929,10 +953,10 @@ public class RecentsAnimationController implements DeathRecipient {
? MODE_OPENING
: MODE_CLOSING;
mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
- !topApp.fillsParent(), mainWindow.mWinAnimator.mLastClipRect,
+ !topApp.fillsParent(), new Rect(),
insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
mLocalBounds, mBounds, mTask.getWindowConfiguration(),
- mIsRecentTaskInvisible, null, null);
+ mIsRecentTaskInvisible, null, null, mTask.getPictureInPictureParams());
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index b1330ce0dbd5..d99b1327e7d9 100755
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -30,7 +30,7 @@ class RefreshRatePolicy {
private final int mLowRefreshRateId;
private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
- private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
+ private final HighRefreshRateDenylist mHighRefreshRateDenylist;
private final WindowManagerService mWmService;
private final ForceRefreshRatePackageList mForceList;
@@ -56,9 +56,9 @@ class RefreshRatePolicy {
static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
- HighRefreshRateBlacklist blacklist) {
+ HighRefreshRateDenylist denylist) {
mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
- mHighRefreshRateBlacklist = blacklist;
+ mHighRefreshRateDenylist = denylist;
mWmService = wmService;
mForceList = new ForceRefreshRatePackageList(mWmService, displayInfo);
}
@@ -116,7 +116,7 @@ class RefreshRatePolicy {
}
// If app is denylisted using higher refresh rate, return default (lower) refresh rate
- if (mHighRefreshRateBlacklist.isBlacklisted(packageName)) {
+ if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
return mLowRefreshRateId;
}
return 0;
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index c3953b4efa16..9181a0fb0734 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -17,14 +17,12 @@
package com.android.server.wm;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.wm.Task.TAG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import android.app.ActivityOptions;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Debug;
-import android.util.Slog;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -201,8 +199,8 @@ class ResetTargetTaskHelper {
noOptions = takeOption(p, noOptions);
- if (DEBUG_TASKS) Slog.w(TAG_TASKS,
- "resetTaskIntendedTask: calling finishActivity on " + p);
+ ProtoLog.w(WM_DEBUG_TASKS, "resetTaskIntendedTask: calling finishActivity "
+ + "on %s", p);
p.finishIfPossible(reason, false /* oomAdj */);
}
}
@@ -213,15 +211,15 @@ class ResetTargetTaskHelper {
while (!mResultActivities.isEmpty()) {
final ActivityRecord p = mResultActivities.remove(0);
- if (ignoreFinishing&& p.finishing) continue;
+ if (ignoreFinishing && p.finishing) continue;
if (takeOptions) {
noOptions = takeOption(p, noOptions);
}
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing activity %s from task=%s "
- + "adding to task=%s Callers=%s", p, mTask, targetTask, Debug.getCallers(4));
- if (DEBUG_TASKS) Slog.v(TAG_TASKS,
- "Pushing next activity " + p + " out to target's task " + target);
+ + "adding to task=%s Callers=%s", p, mTask, targetTask, Debug.getCallers(4));
+ ProtoLog.v(WM_DEBUG_TASKS, "Pushing next activity %s out to target's task %s", p,
+ target);
p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
}
}
@@ -253,8 +251,8 @@ class ResetTargetTaskHelper {
// If the activity currently at the bottom has the same task affinity as
// the one we are moving, then merge it into the same task.
targetTask = task;
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
- + r + " out to bottom task " + targetTask);
+ ProtoLog.v(WM_DEBUG_TASKS, "Start pushing activity %s out to bottom task %s", r,
+ targetTask);
}
if (targetTask == null) {
if (alwaysCreateTask) {
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index faed7fa99fdd..d9a87734e9e7 100644
--- a/services/core/java/com/android/server/wm/RootDisplayArea.java
+++ b/services/core/java/com/android/server/wm/RootDisplayArea.java
@@ -27,7 +27,8 @@ import java.util.Map;
/**
* Root of a {@link DisplayArea} hierarchy. It can be either the {@link DisplayContent} as the root
- * of the whole logical display, or the root of a {@link DisplayArea} group.
+ * of the whole logical display, or a {@link DisplayAreaGroup} as the root of a partition of the
+ * logical display.
*/
class RootDisplayArea extends DisplayArea<DisplayArea> {
@@ -50,6 +51,16 @@ class RootDisplayArea extends DisplayArea<DisplayArea> {
super(wms, Type.ANY, name, featureId);
}
+ @Override
+ RootDisplayArea getRootDisplayArea() {
+ return this;
+ }
+
+ /** Whether the orientation (based on dimensions) of this root is different from the Display. */
+ boolean isOrientationDifferentFromDisplay() {
+ return false;
+ }
+
/** Finds the {@link DisplayArea.Tokens} that this type of window should be attached to. */
DisplayArea.Tokens findAreaForToken(WindowToken token) {
int windowLayerFromType = token.getWindowLayerFromType();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2c19edbf04e5..5d5f7b05026c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -44,6 +44,8 @@ import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
@@ -55,11 +57,8 @@ import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
@@ -76,6 +75,7 @@ import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.Task.ActivityState.STOPPING;
import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.Task.STACK_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
@@ -178,7 +178,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
private static final int SET_SCREEN_BRIGHTNESS_OVERRIDE = 1;
private static final int SET_USER_ACTIVITY_TIMEOUT = 2;
static final String TAG_TASKS = TAG + POSTFIX_TASKS;
- private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE;
static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
@@ -234,7 +233,9 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
})
- public @interface AnyTaskForIdMatchTaskMode {}
+ public @interface AnyTaskForIdMatchTaskMode {
+ }
+
// Match only tasks in the current stacks
static final int MATCH_TASK_IN_STACKS_ONLY = 0;
// Match either tasks in the current stacks, or in the recent tasks if not found in the stacks
@@ -275,6 +276,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// Whether tasks have moved and we need to rank the tasks before next OOM scoring
private boolean mTaskLayersChanged = true;
private int mTmpTaskLayerRank;
+ private final ArraySet<WindowProcessController> mTmpTaskLayerChangedProcs = new ArraySet<>();
+ private final LockedScheduler mRankTaskLayersScheduler;
private boolean mTmpBoolean;
private RemoteException mTmpRemoteException;
@@ -302,6 +305,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
};
private final FindTaskResult mTmpFindTaskResult = new FindTaskResult();
+
static class FindTaskResult implements Function<Task, Boolean> {
ActivityRecord mRecord;
boolean mIdealMatch;
@@ -332,7 +336,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// If documentData is non-null then it must match the existing task data.
documentData = isDocument ? intent.getData() : null;
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + target + " in " + parent);
+ ProtoLog.d(WM_DEBUG_TASKS, "Looking for task of %s in %s", target,
+ parent);
parent.forAllLeafTasks(this);
}
@@ -350,12 +355,12 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
public Boolean apply(Task task) {
if (task.voiceSession != null) {
// We never match voice sessions; those always run independently.
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": voice session");
+ ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: voice session", task);
return false;
}
if (task.mUserId != userId) {
// Looking for a different task.
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": different user");
+ ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: different user", task);
return false;
}
@@ -363,11 +368,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
if (r == null || r.finishing || r.mUserId != userId
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch root " + r);
+ ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch root %s", task, r);
return false;
}
if (!r.hasCompatibleActivityType(mTarget)) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping " + task + ": mismatch activity type");
+ ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch activity type", task);
return false;
}
@@ -386,40 +391,40 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
taskDocumentData = null;
}
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Comparing existing cls="
- + (task.realActivity != null ? task.realActivity.flattenToShortString() : "")
- + "/aff=" + r.getTask().rootAffinity + " to new cls="
- + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
+ ProtoLog.d(WM_DEBUG_TASKS, "Comparing existing cls=%s /aff=%s to new cls=%s /aff=%s",
+ r.getTask().rootAffinity, intent.getComponent().flattenToShortString(),
+ info.taskAffinity, (task.realActivity != null
+ ? task.realActivity.flattenToShortString() : ""));
// TODO Refactor to remove duplications. Check if logic can be simplified.
if (task.realActivity != null && task.realActivity.compareTo(cls) == 0
&& Objects.equals(documentData, taskDocumentData)) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!");
+ ProtoLog.d(WM_DEBUG_TASKS, "Found matching class!");
//dump();
- if (DEBUG_TASKS) Slog.d(TAG_TASKS,
- "For Intent " + intent + " bringing to top: " + r.intent);
+ ProtoLog.d(WM_DEBUG_TASKS, "For Intent %s bringing to top: %s", intent, r.intent);
mRecord = r;
mIdealMatch = true;
return true;
} else if (affinityIntent != null && affinityIntent.getComponent() != null
&& affinityIntent.getComponent().compareTo(cls) == 0 &&
Objects.equals(documentData, taskDocumentData)) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching class!");
- if (DEBUG_TASKS) Slog.d(TAG_TASKS,
- "For Intent " + intent + " bringing to top: " + r.intent);
+ ProtoLog.d(WM_DEBUG_TASKS, "Found matching class!");
+ ProtoLog.d(WM_DEBUG_TASKS, "For Intent %s bringing to top: %s", intent, r.intent);
mRecord = r;
mIdealMatch = true;
return true;
} else if (!isDocument && !taskIsDocument
&& mRecord == null && task.rootAffinity != null) {
if (task.rootAffinity.equals(mTarget.taskAffinity)) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Found matching affinity candidate!");
+ ProtoLog.d(WM_DEBUG_TASKS, "Found matching affinity candidate!");
// It is possible for multiple tasks to have the same root affinity especially
// if they are in separate stacks. We save off this candidate, but keep looking
// to see if there is a better candidate.
mRecord = r;
mIdealMatch = false;
}
- } else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
+ } else {
+ ProtoLog.d(WM_DEBUG_TASKS, "Not a match: %s", task);
+ }
return false;
}
@@ -449,6 +454,12 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
mStackSupervisor = mService.mStackSupervisor;
mStackSupervisor.mRootWindowContainer = this;
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl("Display-off");
+ mRankTaskLayersScheduler = new LockedScheduler(mService) {
+ @Override
+ public void execute() {
+ rankTaskLayersIfNeeded();
+ }
+ };
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -569,6 +580,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
* Returns {@code true} if the callingUid has any non-toast window currently visible to the
* user. Also ignores {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_STARTING},
* since those windows don't belong to apps.
+ *
* @see WindowState#isNonToastOrStarting()
*/
boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
@@ -782,7 +794,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
"Looks like we have reclaimed some memory, clearing surface for retry.");
if (surfaceController != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
+ "SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
winAnimator.destroySurface();
if (winAnimator.mWin.mActivityRecord != null) {
winAnimator.mWin.mActivityRecord.removeStartingWindow();
@@ -813,8 +825,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// "Something has changed! Let's make it correct now."
// TODO: Super long method that should be broken down...
void performSurfacePlacementNoTrace() {
- if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
- + Debug.getCallers(3));
+ if (DEBUG_WINDOW_TRACE) {
+ Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
+ + Debug.getCallers(3));
+ }
int i;
@@ -842,8 +856,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
final DisplayContent defaultDisplay = mWmService.getDefaultDisplayContentLocked();
final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG,
+ ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
+ }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
mWmService.openSurfaceTransaction();
try {
@@ -853,8 +869,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
} finally {
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG,
+ "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
+ }
}
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -887,8 +905,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (isLayoutNeeded()) {
defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("mLayoutNeeded",
- defaultDisplay.pendingLayoutChanges);
+ if (DEBUG_LAYOUT_REPEATS) {
+ surfacePlacer.debugLayoutRepeats("mLayoutNeeded",
+ defaultDisplay.pendingLayoutChanges);
+ }
}
handleResizingWindows();
@@ -1096,11 +1116,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
/**
- * @param w WindowState this method is applied to.
+ * @param w WindowState this method is applied to.
* @param obscured True if there is a window on top of this obscuring the display.
- * @param syswin System window?
+ * @param syswin System window?
* @return True when the display contains content to show the user. When false, the display
- * manager may choose to mirror or blank the display.
+ * manager may choose to mirror or blank the display.
*/
boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) {
final WindowManager.LayoutParams attrs = w.mAttrs;
@@ -1234,7 +1254,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
void dumpTopFocusedDisplayId(PrintWriter pw) {
- pw.print(" mTopFocusedDisplayId="); pw.println(mTopFocusedDisplayId);
+ pw.print(" mTopFocusedDisplayId=");
+ pw.println(mTopFocusedDisplayId);
}
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
@@ -1350,7 +1371,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- @Nullable Context getDisplayUiContext(int displayId) {
+ @Nullable
+ Context getDisplayUiContext(int displayId) {
return getDisplayContent(displayId) != null
? getDisplayContent(displayId).getDisplayUiContext() : null;
}
@@ -1428,7 +1450,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
* corresponding record in display manager.
*/
// TODO: Look into consolidating with getDisplayContent()
- @Nullable DisplayContent getDisplayContentOrCreate(int displayId) {
+ @Nullable
+ DisplayContent getDisplayContentOrCreate(int displayId) {
DisplayContent displayContent = getDisplayContent(displayId);
if (displayContent != null) {
return displayContent;
@@ -1485,8 +1508,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
final DisplayContent display = getDisplayContent(displayId);
return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
- result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
- allowInstrumenting, fromHomeKey),
+ result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
+ allowInstrumenting, fromHomeKey),
false /* initValue */);
}
@@ -1495,11 +1518,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
* displayId - default display area always uses primary home component.
* For secondary display areas, the home activity must have category SECONDARY_HOME and then
* resolves according to the priorities listed below.
- * - If default home is not set, always use the secondary home defined in the config.
- * - Use currently selected primary home activity.
- * - Use the activity in the same package as currently selected primary home activity.
- * If there are multiple activities matched, use first one.
- * - Use the secondary home defined in the config.
+ * - If default home is not set, always use the secondary home defined in the config.
+ * - Use currently selected primary home activity.
+ * - Use the activity in the same package as currently selected primary home activity.
+ * If there are multiple activities matched, use first one.
+ * - Use the secondary home defined in the config.
*/
boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
boolean allowInstrumenting, boolean fromHomeKey) {
@@ -1547,6 +1570,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* This resolves the home activity info.
+ *
* @return the home activity info if any.
*/
@VisibleForTesting
@@ -1620,7 +1644,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
if (aInfo != null) {
- if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, false /* allowInstrumenting */)) {
+ if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea,
+ false /* allowInstrumenting */)) {
aInfo = null;
}
}
@@ -1678,6 +1703,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Check if the display area is valid for secondary home activity.
+ *
* @param taskDisplayArea The target display area.
* @return {@code true} if allow to launch, {@code false} otherwise.
*/
@@ -1718,8 +1744,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Check if home activity start should be allowed on a display.
- * @param homeInfo {@code ActivityInfo} of the home activity that is going to be launched.
- * @param taskDisplayArea The target display area.
+ *
+ * @param homeInfo {@code ActivityInfo} of the home activity that is going to be
+ * launched.
+ * @param taskDisplayArea The target display area.
* @param allowInstrumenting Whether launching home should be allowed if being instrumented.
* @return {@code true} if allow to launch, {@code false} otherwise.
*/
@@ -1764,13 +1792,14 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Ensure all activities visibility, update orientation and configuration.
*
- * @param starting The currently starting activity or {@code null} if there is none.
- * @param displayId The id of the display where operation is executed.
+ * @param starting The currently starting activity or {@code null} if there is
+ * none.
+ * @param displayId The id of the display where operation is executed.
* @param markFrozenIfConfigChanged Whether to set {@link ActivityRecord#frozenBeforeDestroy} to
* {@code true} if config changed.
- * @param deferResume Whether to defer resume while updating config.
+ * @param deferResume Whether to defer resume while updating config.
* @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
- * because of configuration update.
+ * because of configuration update.
*/
boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId,
boolean markFrozenIfConfigChanged, boolean deferResume) {
@@ -1907,22 +1936,33 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
- final String processName = app.mName;
boolean didSomething = false;
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- final DisplayContent display = getChildAt(displayNdx);
- final Task stack = display.getFocusedStack();
- if (stack == null) {
- continue;
- }
-
mTmpRemoteException = null;
mTmpBoolean = false; // Set to true if an activity was started.
- final PooledFunction c = PooledLambda.obtainFunction(
- RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
- PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity());
- stack.forAllActivities(c);
- c.recycle();
+ final DisplayContent display = getChildAt(displayNdx);
+ display.forAllTaskDisplayAreas(displayArea -> {
+ if (mTmpRemoteException != null) {
+ return;
+ }
+
+ for (int taskNdx = displayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) {
+ final Task rootTask = displayArea.getStackAt(taskNdx);
+ if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) {
+ break;
+ }
+
+ final PooledFunction c = PooledLambda.obtainFunction(
+ RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
+ PooledLambda.__(ActivityRecord.class), app,
+ rootTask.topRunningActivity());
+ rootTask.forAllActivities(c);
+ c.recycle();
+ if (mTmpRemoteException != null) {
+ return;
+ }
+ }
+ });
if (mTmpRemoteException != null) {
throw mTmpRemoteException;
}
@@ -1942,8 +1982,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
try {
- if (mStackSupervisor.realStartActivityLocked(r, app, top == r /*andResume*/,
- true /*checkConfig*/)) {
+ if (mStackSupervisor.realStartActivityLocked(r, app,
+ top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) {
mTmpBoolean = true;
}
} catch (RemoteException e) {
@@ -1983,22 +2023,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
notifyClients);
}
} finally {
- mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges, preserveWindows,
- notifyClients);
+ mStackSupervisor.endActivityVisibilityUpdate();
}
}
- void commitActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
- forAllTaskDisplayAreas(taskDisplayArea -> {
- for (int stackNdx = taskDisplayArea.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final Task task = taskDisplayArea.getStackAt(stackNdx);
- task.commitActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
- }
- });
- }
-
boolean switchUser(int userId, UserState uss) {
final Task topFocusedStack = getTopDisplayFocusedStack();
final int focusStackId = topFocusedStack != null
@@ -2010,7 +2038,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
// also cause all tasks to be moved to the fullscreen stack at a position that is
// appropriate.
- removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+ removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
mUserStackInFront.put(mCurrentUser, focusStackId);
mCurrentUser = userId;
@@ -2058,9 +2086,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Move stack with all its existing content to specified task display area.
- * @param stackId Id of stack to move.
+ *
+ * @param stackId Id of stack to move.
* @param taskDisplayArea The task display area to move stack to.
- * @param onTop Indicates whether container should be place on top or on bottom.
+ * @param onTop Indicates whether container should be place on top or on bottom.
*/
void moveStackToTaskDisplayArea(int stackId, TaskDisplayArea taskDisplayArea, boolean onTop) {
final Task stack = getStack(stackId);
@@ -2090,9 +2119,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Move stack with all its existing content to specified display.
- * @param stackId Id of stack to move.
+ *
+ * @param stackId Id of stack to move.
* @param displayId Id of display to move stack to.
- * @param onTop Indicates whether container should be place on top or on bottom.
+ * @param onTop Indicates whether container should be place on top or on bottom.
*/
void moveStackToDisplay(int stackId, int displayId, boolean onTop) {
final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
@@ -2111,27 +2141,27 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
moveStackToTaskDisplayArea(stackId, displayContent.getDefaultTaskDisplayArea(), onTop);
}
- boolean moveTopStackActivityToPinnedStack(int stackId) {
- final Task stack = getStack(stackId);
- if (stack == null) {
+ boolean moveTopStackActivityToPinnedRootTask(int rootTaskId) {
+ final Task rootTask = getStack(rootTaskId);
+ if (rootTask == null) {
throw new IllegalArgumentException(
- "moveTopStackActivityToPinnedStack: Unknown stackId=" + stackId);
+ "moveTopStackActivityToPinnedRootTask: Unknown rootTaskId=" + rootTaskId);
}
- final ActivityRecord r = stack.topRunningActivity();
+ final ActivityRecord r = rootTask.topRunningActivity();
if (r == null) {
- Slog.w(TAG, "moveTopStackActivityToPinnedStack: No top running activity"
- + " in stack=" + stack);
+ Slog.w(TAG, "moveTopStackActivityToPinnedRootTask: No top running activity"
+ + " in rootTask=" + rootTask);
return false;
}
if (!mService.mForceResizableActivities && !r.supportsPictureInPicture()) {
- Slog.w(TAG, "moveTopStackActivityToPinnedStack: Picture-In-Picture not supported for "
- + " r=" + r);
+ Slog.w(TAG, "moveTopStackActivityToPinnedRootTask: Picture-In-Picture not supported "
+ + "for r=" + r);
return false;
}
- moveActivityToPinnedStack(r, "moveTopActivityToPinnedStack");
+ moveActivityToPinnedStack(r, "moveTopStackActivityToPinnedRootTask");
return true;
}
@@ -2184,7 +2214,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
final ActivityRecord oldTopActivity = task.getTopMostActivity();
if (oldTopActivity != null && oldTopActivity.isState(STOPPED)
&& task.getDisplayContent().mAppTransition.getAppTransition()
- == TRANSIT_TASK_TO_BACK) {
+ == TRANSIT_TASK_TO_BACK) {
task.getDisplayContent().mClosingApps.add(oldTopActivity);
oldTopActivity.mRequestForceTransition = true;
}
@@ -2219,6 +2249,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Notifies when an activity enters or leaves PIP mode.
+ *
* @param r indicates the activity currently in PIP, can be null to indicate no activity is
* currently in PIP mode.
*/
@@ -2241,7 +2272,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
@Nullable
ActivityRecord findTask(ActivityRecord r, TaskDisplayArea preferredTaskDisplayArea) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
+ ProtoLog.d(WM_DEBUG_TASKS, "Looking for task of %s", r);
mTmpFindTaskResult.clear();
// Looking up task on preferred display area first
@@ -2269,13 +2300,16 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return task;
}
- if (DEBUG_TASKS && mTmpFindTaskResult.mRecord == null) Slog.d(TAG_TASKS, "No task found");
+ if (WM_DEBUG_TASKS.isEnabled() && mTmpFindTaskResult.mRecord == null) {
+ ProtoLog.d(WM_DEBUG_TASKS, "No task found");
+ }
return mTmpFindTaskResult.mRecord;
}
/**
* Finish the topmost activities in all stacks that belong to the crashed app.
- * @param app The app that crashed.
+ *
+ * @param app The app that crashed.
* @param reason Reason to perform this action.
* @return The task id that was finished in this stack, or INVALID_TASK_ID if none was finished.
*/
@@ -2686,27 +2720,39 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
void invalidateTaskLayers() {
mTaskLayersChanged = true;
+ mRankTaskLayersScheduler.scheduleIfNeeded();
}
+ /** Generate oom-score-adjustment rank for all tasks in the system based on z-order. */
void rankTaskLayersIfNeeded() {
if (!mTaskLayersChanged) {
return;
}
mTaskLayersChanged = false;
mTmpTaskLayerRank = 0;
- final PooledConsumer c = PooledLambda.obtainConsumer(
- RootWindowContainer::rankTaskLayerForActivity, this,
- PooledLambda.__(ActivityRecord.class));
- forAllActivities(c);
- c.recycle();
- }
+ // Only rank for leaf tasks because the score of activity is based on immediate parent.
+ forAllLeafTasks(task -> {
+ final int oldRank = task.mLayerRank;
+ final ActivityRecord r = task.topRunningActivityLocked();
+ if (r != null && r.mVisibleRequested) {
+ task.mLayerRank = ++mTmpTaskLayerRank;
+ } else {
+ task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
+ }
+ if (task.mLayerRank != oldRank) {
+ task.forAllActivities(activity -> {
+ if (activity.hasProcess()) {
+ mTmpTaskLayerChangedProcs.add(activity.app);
+ }
+ });
+ }
+ }, true /* traverseTopToBottom */);
- private void rankTaskLayerForActivity(ActivityRecord r) {
- if (r.canBeTopRunning() && r.mVisibleRequested) {
- r.getTask().mLayerRank = ++mTmpTaskLayerRank;
- } else {
- r.getTask().mLayerRank = -1;
+ for (int i = mTmpTaskLayerChangedProcs.size() - 1; i >= 0; i--) {
+ mTmpTaskLayerChangedProcs.valueAt(i).invalidateOomScoreReferenceState(
+ true /* computeNow */);
}
+ mTmpTaskLayerChangedProcs.clear();
}
void clearOtherAppTimeTrackers(AppTimeTracker except) {
@@ -2731,9 +2777,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
private void destroyActivity(ActivityRecord r) {
if (r.finishing || !r.isDestroyable()) return;
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.getState()
- + " resumed=" + r.getStack().mResumedActivity + " pausing="
- + r.getStack().mPausingActivity + " for reason " + mDestroyAllActivitiesReason);
+ if (DEBUG_SWITCH) {
+ Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.getState()
+ + " resumed=" + r.getStack().mResumedActivity + " pausing="
+ + r.getStack().mPausingActivity + " for reason " + mDestroyAllActivitiesReason);
+ }
r.destroyImmediately(mDestroyAllActivitiesReason);
}
@@ -2794,7 +2842,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
private static boolean matchesActivity(ActivityRecord r, int userId,
boolean compareIntentFilters, Intent intent, ComponentName cls) {
- if (!r.canBeTopRunning() || r.mUserId != userId) return false;
+ if (!r.canBeTopRunning() || r.mUserId != userId) return false;
if (compareIntentFilters) {
if (r.intent.filterEquals(intent)) {
@@ -2829,10 +2877,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Returns the right stack to use for launching factoring in all the input parameters.
*
- * @param r The activity we are trying to launch. Can be null.
- * @param options The activity options used to the launch. Can be null.
- * @param candidateTask The possible task the activity might be launched in. Can be null.
- * @param launchParams The resolved launch params to use.
+ * @param r The activity we are trying to launch. Can be null.
+ * @param options The activity options used to the launch. Can be null.
+ * @param candidateTask The possible task the activity might be launched in. Can be null.
+ * @param launchParams The resolved launch params to use.
* @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
* @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
* @return The stack to use for the launch or INVALID_STACK_ID.
@@ -2966,9 +3014,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Get a topmost stack on the display area, that is a valid launch stack for specified activity.
* If there is no such stack, new dynamic stack can be created.
+ *
* @param taskDisplayArea Target display area.
- * @param r Activity that should be launched there.
- * @param candidateTask The possible task the activity might be put in.
+ * @param r Activity that should be launched there.
+ * @param candidateTask The possible task the activity might be put in.
* @return Existing stack if there is a valid one, new dynamic stack if it is valid or null.
*/
@VisibleForTesting
@@ -3034,10 +3083,14 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// TODO: Can probably be consolidated into getLaunchStack()...
private boolean isValidLaunchStack(Task stack, ActivityRecord r, int windowingMode) {
switch (stack.getActivityType()) {
- case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
- case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
- case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
- case ACTIVITY_TYPE_DREAM: return r.isActivityTypeDream();
+ case ACTIVITY_TYPE_HOME:
+ return r.isActivityTypeHome();
+ case ACTIVITY_TYPE_RECENTS:
+ return r.isActivityTypeRecents();
+ case ACTIVITY_TYPE_ASSISTANT:
+ return r.isActivityTypeAssistant();
+ case ACTIVITY_TYPE_DREAM:
+ return r.isActivityTypeDream();
}
if (stack.mCreatedByOrganizer) {
// Don't launch directly into task created by organizer...but why can't we?
@@ -3077,9 +3130,9 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
* from the target stack. If no valid candidates will be found, it will then go through all
* displays and stacks in last-focused order.
*
- * @param currentFocus The stack that previously had focus.
+ * @param currentFocus The stack that previously had focus.
* @param ignoreCurrent If we should ignore {@param currentFocus} when searching for next
- * candidate.
+ * candidate.
* @return Next focusable {@link Task}, {@code null} if not found.
*/
Task getNextFocusableStack(@NonNull Task currentFocus,
@@ -3142,6 +3195,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
FinishDisabledPackageActivitiesHelper mFinishDisabledPackageActivitiesHelper =
new FinishDisabledPackageActivitiesHelper();
+
class FinishDisabledPackageActivitiesHelper {
private String mPackageName;
private Set<String> mFilterByClasses;
@@ -3256,18 +3310,18 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
/**
- * Removes stacks in the input windowing modes from the system if they are of activity type
+ * Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
- void removeStacksInWindowingModes(int... windowingModes) {
+ void removeRootTasksInWindowingModes(int... windowingModes) {
for (int i = getChildCount() - 1; i >= 0; --i) {
- getChildAt(i).removeStacksInWindowingModes(windowingModes);
+ getChildAt(i).removeRootTasksInWindowingModes(windowingModes);
}
}
- void removeStacksWithActivityTypes(int... activityTypes) {
+ void removeRootTasksWithActivityTypes(int... activityTypes) {
for (int i = getChildCount() - 1; i >= 0; --i) {
- getChildAt(i).removeStacksWithActivityTypes(activityTypes);
+ getChildAt(i).removeRootTasksWithActivityTypes(activityTypes);
}
}
@@ -3298,10 +3352,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
final ActivityRecord resumedActivity = stack.getResumedActivity();
if (resumedActivity == null || !resumedActivity.idle) {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "allResumedActivitiesIdle: stack="
- + stack.getRootTaskId() + " " + resumedActivity + " not idle");
- }
+ ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: stack=%d %s "
+ + "not idle", stack.getRootTaskId(), resumedActivity);
return false;
}
}
@@ -3340,9 +3392,9 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
final Task stack = taskDisplayArea.getStackAt(sNdx);
final ActivityRecord r = stack.mPausingActivity;
if (r != null && !r.isState(PAUSED, STOPPED, STOPPING, FINISHING)) {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "allPausedActivitiesComplete: r=" + r
- + " state=" + r.getState());
+ ProtoLog.d(WM_DEBUG_STATES, "allPausedActivitiesComplete: "
+ + "r=%s state=%s", r, r.getState());
+ if (WM_DEBUG_STATES.isEnabled()) {
pausing[0] = false;
} else {
return true;
@@ -3421,10 +3473,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Returns a {@link Task} for the input id if available. {@code null} otherwise.
- * @param id Id of the task we would like returned.
+ *
+ * @param id Id of the task we would like returned.
* @param matchMode The mode to match the given task id in.
- * @param aOptions The activity options to use for restoration. Can be null.
- * @param onTop If the stack for the task should be the topmost on the display.
+ * @param aOptions The activity options to use for restoration. Can be null.
+ * @param onTop If the stack for the task should be the topmost on the display.
*/
Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode,
@Nullable ActivityOptions aOptions, boolean onTop) {
@@ -3479,8 +3532,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
if (!mStackSupervisor.restoreRecentTaskLocked(task, aOptions, onTop)) {
- if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
- "Couldn't restore task id=" + id + " found in recents");
+ if (DEBUG_RECENTS) {
+ Slog.w(TAG_RECENTS,
+ "Couldn't restore task id=" + id + " found in recents");
+ }
return null;
}
if (DEBUG_RECENTS) Slog.w(TAG_RECENTS, "Restored task id=" + id + " from in recents");
@@ -3591,14 +3646,19 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
/**
* Dump all connected displays' configurations.
+ *
* @param prefix Prefix to apply to each line of the dump.
*/
void dumpDisplayConfigs(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.println("Display override configurations:");
+ pw.print(prefix);
+ pw.println("Display override configurations:");
final int displayCount = getChildCount();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = getChildAt(i);
- pw.print(prefix); pw.print(" "); pw.print(displayContent.mDisplayId); pw.print(": ");
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(displayContent.mDisplayId);
+ pw.print(": ");
pw.println(displayContent.getRequestedOverrideConfiguration());
}
}
@@ -3612,7 +3672,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (printed[0]) {
pw.println();
}
- pw.print("Display #"); pw.print(displayContent.mDisplayId);
+ pw.print("Display #");
+ pw.print(displayContent.mDisplayId);
pw.println(" (activities from top to bottom):");
displayContent.forAllTaskDisplayAreas(taskDisplayArea -> {
for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
@@ -3668,4 +3729,34 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
+ ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}";
}
}
+
+ /**
+ * Helper class to schedule the runnable if it hasn't scheduled on display thread inside window
+ * manager lock.
+ */
+ abstract static class LockedScheduler implements Runnable {
+ private final ActivityTaskManagerService mService;
+ private boolean mScheduled;
+
+ LockedScheduler(ActivityTaskManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mService.mGlobalLock) {
+ mScheduled = false;
+ execute();
+ }
+ }
+
+ abstract void execute();
+
+ void scheduleIfNeeded() {
+ if (!mScheduled) {
+ mService.mH.post(this);
+ mScheduled = true;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index ede6708d5f8f..9205401ed2ee 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -150,6 +150,14 @@ public class SafeActivityOptions {
}
/**
+ * Gets the original options passed in. It should only be used for logging. DO NOT use it as a
+ * condition in the logic of activity launch.
+ */
+ ActivityOptions getOriginalOptions() {
+ return mOriginalOptions;
+ }
+
+ /**
* @see ActivityOptions#popAppVerificationBundle
*/
Bundle popAppVerificationBundle() {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 3bba70c75a7c..0b53a22386bd 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -209,16 +209,14 @@ class ScreenRotationAnimation {
.setCallsite("ScreenRotationAnimation")
.build();
- // In case display bounds change, screenshot buffer and surface may mismatch so set a
- // scaling mode.
- SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
- t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
- t2.apply(true /* sync */);
-
// Capture a screenshot into the surface we just created.
final int displayId = displayContent.getDisplayId();
final Surface surface = mService.mSurfaceFactory.get();
+ // In case display bounds change, screenshot buffer and surface may mismatch so set a
+ // scaling mode.
surface.copyFrom(mScreenshotLayer);
+ surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW);
+
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
mService.mDisplayManagerInternal.systemScreenshot(displayId);
if (screenshotBuffer != null) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5f2113afa5b2..1fdb49f38cf5 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -157,33 +157,33 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
- return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
+ return mService.addWindow(this, window, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
}
@Override
- public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
- return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
+ return mService.addWindow(this, window, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, userId);
}
@Override
- public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
InsetsState outInsetsState) {
- return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
+ return mService.addWindow(this, window, attrs, viewVisibility, displayId,
new Rect() /* outFrame */, outContentInsets, outStableInsets,
new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */,
outInsetsState, mDummyControls, UserHandle.getUserId(mUid));
@@ -200,7 +200,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+ public int relayout(IWindow window, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
@@ -209,7 +209,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
- int res = mService.relayoutWindow(this, window, seq, attrs,
+ int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSurfaceSize, outBLASTSurfaceControl);
@@ -255,7 +255,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public boolean performHapticFeedback(int effectId, boolean always) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mService.mPolicy.performHapticFeedback(mUid, mPackageName,
effectId, always, null);
@@ -313,7 +313,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (DEBUG_TASK_POSITIONING) Slog.d(
TAG_WM, "startMovingTask: {" + startX + "," + startY + "}");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mService.mTaskPositioningController.startMovingTask(window, startX, startY);
} finally {
@@ -325,7 +325,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public void finishMovingTask(IWindow window) {
if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishMovingTask");
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mService.mTaskPositioningController.finishTaskPositioning(window);
} finally {
@@ -335,7 +335,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public void reportSystemGestureExclusionChanged(IWindow window, List<Rect> exclusionRects) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mService.reportSystemGestureExclusionChanged(this, window, exclusionRects);
} finally {
@@ -352,7 +352,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized (mService.mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
actionOnWallpaper(window, (wpController, windowState) ->
wpController.setWindowWallpaperPosition(windowState, x, y, xStep, yStep));
@@ -369,7 +369,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
+ zoom);
}
synchronized (mService.mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
actionOnWallpaper(window, (wpController, windowState) ->
wpController.setWallpaperZoomOut(windowState, zoom));
@@ -398,7 +398,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public void setWallpaperDisplayOffset(IBinder window, int x, int y) {
synchronized (mService.mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
actionOnWallpaper(window, (wpController, windowState) ->
wpController.setWindowWallpaperDisplayOffset(windowState, x, y));
@@ -412,7 +412,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized (mService.mGlobalLock) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
final WindowState windowState = mService.windowForClientLocked(this, window, true);
return windowState.getDisplayContent().mWallpaperController
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1ba05326a98d..71a30e6013c2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -80,6 +80,8 @@ import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGH
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
@@ -87,13 +89,9 @@ import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
@@ -161,6 +159,7 @@ import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
import android.app.IActivityController;
+import android.app.PictureInPictureParams;
import android.app.RemoteAction;
import android.app.ResultInfo;
import android.app.TaskInfo;
@@ -318,6 +317,7 @@ class Task extends WindowContainer<WindowContainer> {
// Do not move the stack as a part of reparenting
static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+ // TODO (b/157876447): switch to Task related name
@IntDef(prefix = {"STACK_VISIBILITY"}, value = {
STACK_VISIBILITY_VISIBLE,
STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
@@ -463,9 +463,10 @@ class Task extends WindowContainer<WindowContainer> {
int mMinWidth;
int mMinHeight;
+ static final int LAYER_RANK_INVISIBLE = -1;
// Ranking (from top) of this task among all visible tasks. (-1 means it's not visible)
// This number will be assigned when we evaluate OOM scores for all visible tasks.
- int mLayerRank = -1;
+ int mLayerRank = LAYER_RANK_INVISIBLE;
/** Helper object used for updating override configuration. */
private Configuration mTmpConfig = new Configuration();
@@ -595,10 +596,6 @@ class Task extends WindowContainer<WindowContainer> {
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
- private boolean mTopActivityOccludesKeyguard;
- private ActivityRecord mTopDismissingKeyguardActivity;
- private ActivityRecord mTopTurnScreenOnActivity;
-
private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1;
private final Handler mHandler;
@@ -1105,7 +1102,7 @@ class Task extends WindowContainer<WindowContainer> {
&& toStack.topRunningActivity() != null) {
// Pause the resumed activity on the target stack while re-parenting task on top of it.
toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- null /* resuming */);
+ null /* resuming */, "reparent");
}
final int toStackWindowingMode = toStack.getWindowingMode();
@@ -1284,7 +1281,7 @@ class Task extends WindowContainer<WindowContainer> {
_intent.setSourceBounds(null);
}
}
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Setting Intent of " + this + " to " + _intent);
+ ProtoLog.v(WM_DEBUG_TASKS, "Setting Intent of %s to %s", this, _intent);
intent = _intent;
realActivity = _intent != null ? _intent.getComponent() : null;
origActivity = null;
@@ -1295,8 +1292,7 @@ class Task extends WindowContainer<WindowContainer> {
Intent targetIntent = new Intent(_intent);
targetIntent.setSelector(null);
targetIntent.setSourceBounds(null);
- if (DEBUG_TASKS) Slog.v(TAG_TASKS,
- "Setting Intent of " + this + " to target " + targetIntent);
+ ProtoLog.v(WM_DEBUG_TASKS, "Setting Intent of %s to target %s", this, targetIntent);
intent = targetIntent;
realActivity = targetComponent;
origActivity = _intent.getComponent();
@@ -1481,14 +1477,6 @@ class Task extends WindowContainer<WindowContainer> {
// Update task bounds if needed.
adjustBoundsForDisplayChangeIfNeeded(getDisplayContent());
- if (getWindowConfiguration().windowsAreScaleable()) {
- // We force windows out of SCALING_MODE_FREEZE so that we can continue to animate them
- // while a resize is pending.
- forceWindowsScaleable(true /* force */);
- } else {
- forceWindowsScaleable(false /* force */);
- }
-
mRootWindowContainer.updateUIDsPresentOnDisplay();
// Resume next focusable stack after reparenting to another display if we aren't removing
@@ -1541,7 +1529,6 @@ class Task extends WindowContainer<WindowContainer> {
if (isPersistable) {
mLastTimeMoved = System.currentTimeMillis();
}
- mRootWindowContainer.invalidateTaskLayers();
}
// Close up recents linked list.
@@ -2378,6 +2365,7 @@ class Task extends WindowContainer<WindowContainer> {
private void initializeChangeTransition(Rect startBounds) {
mDisplayContent.prepareAppTransition(TRANSIT_TASK_CHANGE_WINDOWING_MODE,
false /* alwaysKeepCurrent */, 0, false /* forceOverride */);
+ mAtmService.getTransitionController().collect(this);
mDisplayContent.mChangingContainers.add(this);
mSurfaceFreezer.freeze(getPendingTransaction(), startBounds);
@@ -3787,17 +3775,6 @@ class Task extends WindowContainer<WindowContainer> {
positionChildAt(position, child, false /* includeParents */);
}
- void forceWindowsScaleable(boolean force) {
- mWmService.openSurfaceTransaction();
- try {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- mChildren.get(i).forceWindowsScaleableInTransaction(force);
- }
- } finally {
- mWmService.closeSurfaceTransaction("forceWindowsScaleable");
- }
- }
-
void setTaskDescription(TaskDescription taskDescription) {
mTaskDescription = taskDescription;
}
@@ -4065,17 +4042,20 @@ class Task extends WindowContainer<WindowContainer> {
info.topActivityType = top.getActivityType();
info.isResizeable = isResizeable();
- ActivityRecord rootActivity = top.getRootActivity();
- if (rootActivity == null || rootActivity.pictureInPictureArgs.empty()) {
- info.pictureInPictureParams = null;
- } else {
- info.pictureInPictureParams = rootActivity.pictureInPictureArgs;
- }
+ info.pictureInPictureParams = getPictureInPictureParams();
info.topActivityInfo = mReuseActivitiesReport.top != null
? mReuseActivitiesReport.top.info
: null;
}
+ @Nullable PictureInPictureParams getPictureInPictureParams() {
+ final Task top = getTopMostTask();
+ if (top == null) return null;
+ final ActivityRecord rootActivity = top.getRootActivity();
+ return (rootActivity == null || rootActivity.pictureInPictureArgs.empty())
+ ? null : rootActivity.pictureInPictureArgs;
+ }
+
/**
* Returns a {@link TaskInfo} with information from this task.
*/
@@ -4115,6 +4095,10 @@ class Task extends WindowContainer<WindowContainer> {
return STACK_VISIBILITY_INVISIBLE;
}
+ if (isTopActivityLaunchedBehind()) {
+ return STACK_VISIBILITY_VISIBLE;
+ }
+
boolean gotSplitScreenStack = false;
boolean gotOpaqueSplitScreenPrimary = false;
boolean gotOpaqueSplitScreenSecondary = false;
@@ -4232,6 +4216,14 @@ class Task extends WindowContainer<WindowContainer> {
: STACK_VISIBILITY_VISIBLE;
}
+ private boolean isTopActivityLaunchedBehind() {
+ final ActivityRecord top = topRunningActivity();
+ if (top != null && top.mLaunchTaskBehind) {
+ return true;
+ }
+ return false;
+ }
+
ActivityRecord isInTask(ActivityRecord r) {
if (r == null) {
return null;
@@ -4756,6 +4748,16 @@ class Task extends WindowContainer<WindowContainer> {
}
@Override
+ boolean showSurfaceOnCreation() {
+ // Organized tasks handle their own surface visibility
+ final boolean willBeOrganized =
+ mAtmService.mTaskOrganizerController.isSupportedWindowingMode(getWindowingMode())
+ && isRootTask();
+ return !mAtmService.getTransitionController().isShellTransitionsEnabled()
+ || !willBeOrganized;
+ }
+
+ @Override
protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
/**
* Avoid reparenting SurfaceControl of the organized tasks that are always on top, since
@@ -4774,8 +4776,12 @@ class Task extends WindowContainer<WindowContainer> {
// If the task is not yet visible when it is added to the task organizer, then we should
// hide it to allow the task organizer to show it when it is properly reparented. We
// skip this for tasks created by the organizer because they can synchronously update
- // the leash before new children are added to the task.
- if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) {
+ // the leash before new children are added to the task. Also skip this if the task
+ // has already been sent to the organizer which can happen before the first draw if
+ // an existing task is reported to the organizer when it first registers.
+ if (!mAtmService.getTransitionController().isShellTransitionsEnabled()
+ && !mCreatedByOrganizer && !mTaskAppearedSent
+ && mTaskOrganizer != null && !prevHasBeenVisible) {
getSyncTransaction().hide(getSurfaceControl());
commitPendingTransaction();
}
@@ -4826,6 +4832,11 @@ class Task extends WindowContainer<WindowContainer> {
@VisibleForTesting
boolean setTaskOrganizer(ITaskOrganizer organizer) {
+ return setTaskOrganizer(organizer, false /* skipTaskAppeared */);
+ }
+
+ @VisibleForTesting
+ boolean setTaskOrganizer(ITaskOrganizer organizer, boolean skipTaskAppeared) {
if (mTaskOrganizer == organizer) {
return false;
}
@@ -4838,7 +4849,9 @@ class Task extends WindowContainer<WindowContainer> {
sendTaskVanished(prevOrganizer);
if (mTaskOrganizer != null) {
- sendTaskAppeared();
+ if (!skipTaskAppeared) {
+ sendTaskAppeared();
+ }
} else {
// No longer managed by any organizer.
mTaskAppearedSent = false;
@@ -4851,6 +4864,10 @@ class Task extends WindowContainer<WindowContainer> {
return true;
}
+ boolean updateTaskOrganizerState(boolean forceUpdate) {
+ return updateTaskOrganizerState(forceUpdate, false /* skipTaskAppeared */);
+ }
+
/**
* Called when the task state changes (ie. from windowing mode change) an the task organizer
* state should also be updated.
@@ -4858,9 +4875,10 @@ class Task extends WindowContainer<WindowContainer> {
* @param forceUpdate Updates the task organizer to the one currently specified in the task
* org controller for the task's windowing mode, ignoring the cached
* windowing mode checks.
+ * @param skipTaskAppeared Skips calling taskAppeared for the new organizer if it has changed
* @return {@code true} if task organizer changed.
*/
- boolean updateTaskOrganizerState(boolean forceUpdate) {
+ boolean updateTaskOrganizerState(boolean forceUpdate, boolean skipTaskAppeared) {
if (getSurfaceControl() == null) {
// Can't call onTaskAppeared without a surfacecontrol, so defer this until after one
// is created.
@@ -4876,7 +4894,7 @@ class Task extends WindowContainer<WindowContainer> {
if (!forceUpdate && mTaskOrganizer == organizer) {
return false;
}
- return setTaskOrganizer(organizer);
+ return setTaskOrganizer(organizer, skipTaskAppeared);
}
@Override
@@ -5294,8 +5312,8 @@ class Task extends WindowContainer<WindowContainer> {
}
void minimalResumeActivityLocked(ActivityRecord r) {
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + r + " (starting new instance)"
- + " callers=" + Debug.getCallers(5));
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
+ + "callers=%s", r, Debug.getCallers(5));
r.setState(RESUMED, "minimalResumeActivityLocked");
r.completeResumeLocked();
}
@@ -5339,23 +5357,24 @@ class Task extends WindowContainer<WindowContainer> {
if (mResumedActivity != null) {
// Still have something resumed; can't sleep until it is paused.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep needs to pause " + mResumedActivity);
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
"Sleep => pause with userLeaving=false");
- startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */);
+ startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
+ "sleep");
shouldSleep = false ;
} else if (mPausingActivity != null) {
// Still waiting for something to pause; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still waiting to pause " + mPausingActivity);
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
shouldSleep = false;
}
if (!shuttingDown) {
if (containsActivityFromStack(mStackSupervisor.mStoppingActivities)) {
// Still need to tell some activities to stop; can't sleep yet.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still need to stop "
- + mStackSupervisor.mStoppingActivities.size() + " activities");
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
+ mStackSupervisor.mStoppingActivities.size());
mStackSupervisor.scheduleIdle();
shouldSleep = false;
@@ -5403,11 +5422,12 @@ class Task extends WindowContainer<WindowContainer> {
* @param resuming The activity we are currently trying to resume or null if this is not being
* called as part of resuming the top activity, so we shouldn't try to instigate
* a resume here if not null.
+ * @param reason The reason of pausing the activity.
* @return Returns true if an activity now is in the PAUSING state, and we are waiting for
* it to tell us when it is done.
*/
final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
- ActivityRecord resuming) {
+ ActivityRecord resuming, String reason) {
if (mPausingActivity != null) {
Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+ " state=" + mPausingActivity.getState());
@@ -5433,8 +5453,7 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSING: " + prev);
- else if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Start pausing: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
mPausingActivity = prev;
mLastPausedActivity = prev;
mLastNoHistoryActivity = prev.isNoHistory() ? prev : null;
@@ -5465,16 +5484,16 @@ class Task extends WindowContainer<WindowContainer> {
boolean didAutoPip = false;
if (prev.attachedToProcess()) {
if (shouldAutoPip) {
- if (DEBUG_PAUSE) {
- Slog.d(TAG_PAUSE, "Auto-PIP allowed, entering PIP mode directly: " + prev);
- }
+ ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
+ + "directly: %s", prev);
+
didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
mPausingActivity = null;
} else {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
try {
EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
- prev.shortComponentName, "userLeaving=" + userLeaving);
+ prev.shortComponentName, "userLeaving=" + userLeaving, reason);
mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
@@ -5511,8 +5530,8 @@ class Task extends WindowContainer<WindowContainer> {
// key dispatch; the same activity will pick it up again on wakeup.
if (!uiSleeping) {
prev.pauseKeyDispatchingLocked();
- } else if (DEBUG_PAUSE) {
- Slog.v(TAG_PAUSE, "Key dispatch not paused for screen off");
+ } else {
+ ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
}
if (pauseImmediately) {
@@ -5529,7 +5548,7 @@ class Task extends WindowContainer<WindowContainer> {
} else {
// This activity failed to schedule the
// pause, so just treat it as being paused now.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next.");
+ ProtoLog.v(WM_DEBUG_STATES, "Activity not running, resuming next.");
if (resuming == null) {
mRootWindowContainer.resumeFocusedStacksTopActivities();
}
@@ -5540,22 +5559,22 @@ class Task extends WindowContainer<WindowContainer> {
@VisibleForTesting
void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
ActivityRecord prev = mPausingActivity;
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
if (prev != null) {
prev.setWillCloseOrEnterPip(false);
final boolean wasStopping = prev.isState(STOPPING);
prev.setState(PAUSED, "completePausedLocked");
if (prev.finishing) {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
prev = prev.completeFinishing("completePausedLocked");
} else if (prev.hasProcess()) {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev
- + " wasStopping=" + wasStopping
- + " visibleRequested=" + prev.mVisibleRequested);
+ ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ + "wasStopping=%b visibleRequested=%b", prev, wasStopping,
+ prev.mVisibleRequested);
if (prev.deferRelaunchUntilPaused) {
// Complete the deferred relaunch that was waiting for pause to complete.
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Re-launching after pause: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
} else if (wasStopping) {
// We are also stopping, the stop request must have gone soon after the pause.
@@ -5571,7 +5590,7 @@ class Task extends WindowContainer<WindowContainer> {
"completePauseLocked");
}
} else {
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "App died during pause, not stopping: " + prev);
+ ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
prev = null;
}
// It is possible the activity was freezing the screen before it was paused.
@@ -5678,12 +5697,10 @@ class Task extends WindowContainer<WindowContainer> {
// TODO: Should be re-worked based on the fact that each task as a stack in most cases.
void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
- mTopActivityOccludesKeyguard = false;
- mTopDismissingKeyguardActivity = null;
- mTopTurnScreenOnActivity = null;
mStackSupervisor.beginActivityVisibilityUpdate();
try {
- mEnsureActivitiesVisibleHelper.processUpdate(starting);
+ mEnsureActivitiesVisibleHelper.process(starting, configChanges, preserveWindows,
+ notifyClients);
if (mTranslucentActivityWaiting != null &&
mUndrawnActivitiesBelowTopTranslucent.isEmpty()) {
@@ -5692,24 +5709,10 @@ class Task extends WindowContainer<WindowContainer> {
notifyActivityDrawnLocked(null);
}
} finally {
- mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges, preserveWindows,
- notifyClients);
+ mStackSupervisor.endActivityVisibilityUpdate();
}
}
- void commitActivitiesVisible(ActivityRecord starting, int configChanges,
- boolean preserveWindows, boolean notifyClients) {
- mEnsureActivitiesVisibleHelper.processCommit(starting, configChanges, preserveWindows,
- notifyClients);
- }
-
- /**
- * @return true if the top visible activity wants to occlude the Keyguard, false otherwise
- */
- boolean topActivityOccludesKeyguard() {
- return mTopActivityOccludesKeyguard;
- }
-
/**
* Returns true if this stack should be resized to match the bounds specified by
* {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
@@ -5728,44 +5731,6 @@ class Task extends WindowContainer<WindowContainer> {
&& this == getDisplayArea().getTopStackInWindowingMode(getWindowingMode());
}
- /**
- * @return the top most visible activity that wants to dismiss Keyguard
- */
- ActivityRecord getTopDismissingKeyguardActivity() {
- return mTopDismissingKeyguardActivity;
- }
-
- /**
- * @return the top most visible activity that wants to turn screen on
- */
- ActivityRecord getTopTurnScreenOnActivity() {
- return mTopTurnScreenOnActivity;
- }
-
- /**
- * Updates {@link #mTopActivityOccludesKeyguard}, {@link #mTopTurnScreenOnActivity} and
- * {@link #mTopDismissingKeyguardActivity} if this task could be visible.
- *
- */
- void updateKeyguardVisibility(ActivityRecord r, boolean isTop) {
- final boolean showWhenLocked = r.canShowWhenLocked();
- final boolean dismissKeyguard = r.containsDismissKeyguardWindow();
- final boolean turnScreenOn = r.canTurnScreenOn();
- if (dismissKeyguard && mTopDismissingKeyguardActivity == null) {
- mTopDismissingKeyguardActivity = r;
- }
-
- if (turnScreenOn && mTopTurnScreenOnActivity == null) {
- mTopTurnScreenOnActivity = r;
- }
-
- // Only the top activity may control occluded, as we can't occlude the Keyguard if the
- // top app doesn't want to occlude it.
- if (isTop) {
- mTopActivityOccludesKeyguard |= showWhenLocked;
- }
- }
-
void checkTranslucentActivityWaiting(ActivityRecord top) {
if (mTranslucentActivityWaiting != top) {
mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -5916,13 +5881,17 @@ class Task extends WindowContainer<WindowContainer> {
final TaskDisplayArea taskDisplayArea = getDisplayArea();
// If the top activity is the resumed one, nothing to do.
+ // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
+ // we still want to proceed if the visibility of other windows have changed (e.g. bringing
+ // a fullscreen window forward to cover another freeform activity.)
if (mResumedActivity == next && next.isState(RESUMED)
+ && taskDisplayArea.getWindowingMode() != WINDOWING_MODE_FREEFORM
&& taskDisplayArea.allResumedActivitiesComplete()) {
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeTopActivityLocked: Top activity resumed " + next);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity "
+ + "resumed %s", next);
return false;
}
@@ -5933,9 +5902,9 @@ class Task extends WindowContainer<WindowContainer> {
// If we are currently pausing an activity, then don't do anything until that is done.
final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
if (!allPausedComplete) {
- if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) {
- Slog.v(TAG_PAUSE, "resumeTopActivityLocked: Skip resume: some activity pausing.");
- }
+ ProtoLog.v(WM_DEBUG_STATES,
+ "resumeTopActivityLocked: Skip resume: some activity pausing.");
+
return false;
}
@@ -5947,8 +5916,8 @@ class Task extends WindowContainer<WindowContainer> {
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeTopActivityLocked: Going to sleep and all paused");
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Going to sleep and"
+ + " all paused");
return false;
}
@@ -5970,7 +5939,7 @@ class Task extends WindowContainer<WindowContainer> {
// If we are currently pausing an activity, then don't do anything until that is done.
if (!mRootWindowContainer.allPausedActivitiesComplete()) {
- if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG_PAUSE,
+ ProtoLog.v(WM_DEBUG_STATES,
"resumeTopActivityLocked: Skip resume: some activity pausing.");
return false;
@@ -5996,13 +5965,13 @@ class Task extends WindowContainer<WindowContainer> {
boolean pausing = taskDisplayArea.pauseBackStacks(userLeaving, next);
if (mResumedActivity != null) {
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeTopActivityLocked: Pausing " + mResumedActivity);
- pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Pausing %s", mResumedActivity);
+ pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next,
+ "resumeTopActivityInnerLocked");
}
if (pausing) {
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
- "resumeTopActivityLocked: Skip resume: need to start pausing");
+ ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivityLocked: Skip resume: need to"
+ + " start pausing");
// At this point we want to put the upcoming activity's process
// at the top of the LRU list, since we know we will be needing it
// very soon and it would be a waste to let it get killed if it
@@ -6031,18 +6000,19 @@ class Task extends WindowContainer<WindowContainer> {
// Make sure we have executed any pending transitions, since there
// should be nothing left to do at this point.
executeAppTransition(options);
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeTopActivityLocked: Top activity resumed (dontWaitForPause) " + next);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity resumed "
+ + "(dontWaitForPause) %s", next);
return true;
}
// If the most recent activity was noHistory but was only stopped rather
// than stopped+finished because the device went to sleep, we need to make
// sure to finish it as we're making a new activity topmost.
- if (shouldSleepActivities() && mLastNoHistoryActivity != null &&
- !mLastNoHistoryActivity.finishing) {
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "no-history finish of " + mLastNoHistoryActivity + " on new resume");
+ if (shouldSleepActivities() && mLastNoHistoryActivity != null
+ && !mLastNoHistoryActivity.finishing
+ && mLastNoHistoryActivity != next) {
+ ProtoLog.d(WM_DEBUG_STATES, "no-history finish of %s on new resume",
+ mLastNoHistoryActivity);
mLastNoHistoryActivity.finishIfPossible("resume-no-history", false /* oomAdj */);
mLastNoHistoryActivity = null;
}
@@ -6161,8 +6131,7 @@ class Task extends WindowContainer<WindowContainer> {
mAtmService.updateCpuStats();
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
- + " (in existing)");
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
next.setState(RESUMED, "resumeTopActivityInnerLocked");
@@ -6192,9 +6161,8 @@ class Task extends WindowContainer<WindowContainer> {
// is still at the top and schedule another run if something
// weird happened.
ActivityRecord nextNext = topRunningActivity();
- if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
- "Activity config changed during resume: " + next
- + ", new next: " + nextNext);
+ ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
+ + "%s, new next: %s", next, nextNext);
if (nextNext != next) {
// Do over!
mStackSupervisor.scheduleResumeTopActivities();
@@ -6241,12 +6209,11 @@ class Task extends WindowContainer<WindowContainer> {
dc.isNextTransitionForward()));
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
- if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
- + next);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Resumed %s", next);
} catch (Exception e) {
// Whoops, need to restart this activity!
- if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
- + lastState + ": " + next);
+ ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+ + "%s", lastState, next);
next.setState(lastState, "resumeTopActivityInnerLocked");
// lastResumedActivity being non-null implies there is a lastStack present.
@@ -6288,7 +6255,7 @@ class Task extends WindowContainer<WindowContainer> {
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
- if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Restarting " + next);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Restarting %s", next);
mStackSupervisor.startSpecificActivity(next, true, true);
}
@@ -6320,8 +6287,8 @@ class Task extends WindowContainer<WindowContainer> {
// If the current stack is a home stack, or if focus didn't switch to a different stack -
// just start up the Launcher...
ActivityOptions.abort(options);
- if (DEBUG_STATES) Slog.d(TAG_STATES,
- "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home");
+ ProtoLog.d(WM_DEBUG_STATES, "resumeNextFocusableActivityWhenStackIsEmpty: %s, "
+ + "go home", reason);
return mRootWindowContainer.resumeHomeActivity(prev, reason, getDisplayArea());
}
@@ -6406,7 +6373,15 @@ class Task extends WindowContainer<WindowContainer> {
transit = TRANSIT_TASK_OPEN;
}
}
- dc.prepareAppTransition(transit, keepCurTransition);
+ if (mAtmService.getTransitionController().isShellTransitionsEnabled()
+ // TODO(shell-transitions): eventually all transitions.
+ && transit == TRANSIT_TASK_OPEN) {
+ Transition transition =
+ mAtmService.getTransitionController().requestTransition(transit);
+ transition.collect(task);
+ } else {
+ dc.prepareAppTransition(transit, keepCurTransition);
+ }
mStackSupervisor.mNoAnimActivities.remove(r);
}
boolean doShow = true;
@@ -6938,7 +6913,7 @@ class Task extends WindowContainer<WindowContainer> {
moveToBack("moveTaskToBackLocked", tr);
if (inPinnedWindowingMode()) {
- mStackSupervisor.removeStack(this);
+ mStackSupervisor.removeRootTask(this);
return true;
}
@@ -7041,8 +7016,8 @@ class Task extends WindowContainer<WindowContainer> {
boolean handleAppDied(WindowProcessController app) {
boolean isPausingDied = false;
if (mPausingActivity != null && mPausingActivity.app == app) {
- if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE,
- "App died while pausing: " + mPausingActivity);
+ ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
+ mPausingActivity);
mPausingActivity = null;
isPausingDied = true;
}
@@ -7438,6 +7413,10 @@ class Task extends WindowContainer<WindowContainer> {
if (!mChildren.contains(child)) {
return;
}
+ if (child.asTask() != null) {
+ // Non-root task position changed.
+ mRootWindowContainer.invalidateTaskLayers();
+ }
final boolean isTop = getTopChild() == child;
if (isTop) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index a8fd026929c5..f9120f5e4e67 100755
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -35,12 +35,10 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
import static com.android.server.wm.DisplayContent.alwaysCreateStack;
-import static com.android.server.wm.RootWindowContainer.TAG_STATES;
import static com.android.server.wm.Task.ActivityState.DESTROYED;
import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.Task.ActivityState.STOPPED;
@@ -216,11 +214,13 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return mChildren.indexOf(task);
}
- @Nullable Task getRootHomeTask() {
+ @Nullable
+ Task getRootHomeTask() {
return mRootHomeTask;
}
- @Nullable Task getRootRecentsTask() {
+ @Nullable
+ Task getRootRecentsTask() {
return mRootRecentsTask;
}
@@ -445,6 +445,12 @@ final class TaskDisplayArea extends DisplayArea<Task> {
}
@Override
+ void onChildPositionChanged(WindowContainer child) {
+ super.onChildPositionChanged(child);
+ mRootWindowContainer.invalidateTaskLayers();
+ }
+
+ @Override
boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback,
boolean traverseTopToBottom) {
return callback.apply(this);
@@ -524,19 +530,20 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* When stack is added or repositioned, find a proper position for it.
*
* The order is defined as:
- * - Dream is on top of everything
- * - PiP is directly below the Dream
- * - always-on-top stacks are directly below PiP; new always-on-top stacks are added above
- * existing ones
- * - other non-always-on-top stacks come directly below always-on-top stacks; new
- * non-always-on-top stacks are added directly below always-on-top stacks and above existing
- * non-always-on-top stacks
- * - if {@link #mAssistantOnTopOfDream} is enabled, then Assistant is on top of everything
- * (including the Dream); otherwise, it is a normal non-always-on-top stack
+ * - Dream is on top of everything
+ * - PiP is directly below the Dream
+ * - always-on-top stacks are directly below PiP; new always-on-top stacks are added above
+ * existing ones
+ * - other non-always-on-top stacks come directly below always-on-top stacks; new
+ * non-always-on-top stacks are added directly below always-on-top stacks and above existing
+ * non-always-on-top stacks
+ * - if {@link #mAssistantOnTopOfDream} is enabled, then Assistant is on top of everything
+ * (including the Dream); otherwise, it is a normal non-always-on-top stack
*
* @param requestedPosition Position requested by caller.
- * @param stack Stack to be added or positioned.
- * @param adding Flag indicates whether we're adding a new stack or positioning an existing.
+ * @param stack Stack to be added or positioned.
+ * @param adding Flag indicates whether we're adding a new stack or positioning an
+ * existing.
* @return The proper position for the stack.
*/
private int findPositionForStack(int requestedPosition, Task stack, boolean adding) {
@@ -653,6 +660,13 @@ final class TaskDisplayArea extends DisplayArea<Task> {
@Override
int getOrientation(int candidate) {
+ mLastOrientationSource = null;
+ // Only allow to specify orientation if this TDA is not set to ignore orientation request,
+ // and it has the focus.
+ if (mIgnoreOrientationRequest || !isLastFocused()) {
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
// Apps and their containers are not allowed to specify an orientation while using
// root tasks...except for the home stack if it is not resizable and currently
@@ -761,7 +775,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* Adjusts the layer of the stack which belongs to the same group.
* Note that there are three stack groups: home stacks, always on top stacks, and normal stacks.
*
- * @param startLayer The beginning layer of this group of stacks.
+ * @param startLayer The beginning layer of this group of stacks.
* @param normalStacks Set {@code true} if this group is neither home nor always on top.
* @return The adjusted layer value.
*/
@@ -908,6 +922,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Returns an existing stack compatible with the windowing mode and activity type or creates one
* if a compatible stack doesn't exist.
+ *
* @see #getOrCreateStack(int, int, boolean, Intent, Task)
*/
Task getOrCreateStack(int windowingMode, int activityType, boolean onTop) {
@@ -920,6 +935,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* existing compatible root task or creates a new one.
* For one level task, the candidate task would be reused to also be the root task or create
* a new root task if no candidate task.
+ *
* @see #getStack(int, int)
* @see #createStack(int, int, boolean)
*/
@@ -965,6 +981,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Returns an existing stack compatible with the input params or creates one
* if a compatible stack doesn't exist.
+ *
* @see #getOrCreateStack(int, int, boolean)
*/
Task getOrCreateStack(@Nullable ActivityRecord r,
@@ -993,17 +1010,21 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Creates a stack matching the input windowing mode and activity type on this display.
- * @param windowingMode The windowing mode the stack should be created in. If
- * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
- * inherit its parent's windowing mode.
- * @param activityType The activityType the stack should be created in. If
- * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
- * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- * @param onTop If true the stack will be created at the top of the display, else at the bottom.
- * @param info The started activity info.
- * @param intent The intent that started this task.
+ *
+ * @param windowingMode The windowing mode the stack should be created in. If
+ * {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack
+ * will
+ * inherit its parent's windowing mode.
+ * @param activityType The activityType the stack should be created in. If
+ * {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack
+ * will
+ * be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+ * @param onTop If true the stack will be created at the top of the display, else
+ * at the bottom.
+ * @param info The started activity info.
+ * @param intent The intent that started this task.
* @param createdByOrganizer @{code true} if this is created by task organizer, @{code false}
- * otherwise.
+ * otherwise.
* @return The newly created stack.
*/
Task createStack(int windowingMode, int activityType, boolean onTop, ActivityInfo info,
@@ -1210,8 +1231,9 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* paused in stacks that are no longer visible or in pinned windowing mode. This does not
* pause activities in visible stacks, so if an activity is launched within the same stack/task,
* then we should explicitly pause that stack's top activity.
+ *
* @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
- * @param resuming The resuming activity.
+ * @param resuming The resuming activity.
* @return {@code true} if any activity was paused as a result of this call.
*/
boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
@@ -1222,12 +1244,10 @@ final class TaskDisplayArea extends DisplayArea<Task> {
if (resumedActivity != null
&& (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
|| !stack.isTopActivityFocusable())) {
- if (DEBUG_STATES) {
- Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
- + " mResumedActivity=" + resumedActivity);
- }
+ ProtoLog.d(WM_DEBUG_STATES, "pauseBackStacks: stack=%s "
+ + "mResumedActivity=%s", stack, resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
- resuming);
+ resuming, "pauseBackStacks");
}
}
return someActivityPaused;
@@ -1283,9 +1303,8 @@ final class TaskDisplayArea extends DisplayArea<Task> {
for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
final Task stack = getStackAt(stackNdx);
if (!r.hasCompatibleActivityType(stack) && stack.isLeafTask()) {
- if (DEBUG_TASKS) {
- Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) " + stack);
- }
+ ProtoLog.d(WM_DEBUG_TASKS, "Skipping stack: (mismatch activity/stack) "
+ + "%s", stack);
continue;
}
@@ -1324,66 +1343,65 @@ final class TaskDisplayArea extends DisplayArea<Task> {
}
/**
- * Removes stacks in the input windowing modes from the system if they are of activity type
+ * Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
*/
- void removeStacksInWindowingModes(int... windowingModes) {
+ void removeRootTasksInWindowingModes(int... windowingModes) {
if (windowingModes == null || windowingModes.length == 0) {
return;
}
- // Collect the stacks that are necessary to be removed instead of performing the removal
- // by looping mStacks, so that we don't miss any stacks after the stack size changed or
- // stacks reordered.
- final ArrayList<Task> stacks = new ArrayList<>();
+ // Collect the root tasks that are necessary to be removed instead of performing the removal
+ // by looping the children, so that we don't miss any root tasks after the children size
+ // changed or reordered.
+ final ArrayList<Task> rootTasks = new ArrayList<>();
for (int j = windowingModes.length - 1; j >= 0; --j) {
final int windowingMode = windowingModes[j];
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final Task stack = getStackAt(i);
- if (!stack.isActivityTypeStandardOrUndefined()) {
- continue;
- }
- if (stack.getWindowingMode() != windowingMode) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task rootTask = mChildren.get(i);
+ if (rootTask.mCreatedByOrganizer
+ || !rootTask.isActivityTypeStandardOrUndefined()
+ || rootTask.getWindowingMode() != windowingMode) {
continue;
}
- stacks.add(stack);
+ rootTasks.add(rootTask);
}
}
- for (int i = stacks.size() - 1; i >= 0; --i) {
- mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
+ for (int i = rootTasks.size() - 1; i >= 0; --i) {
+ mRootWindowContainer.mStackSupervisor.removeRootTask(rootTasks.get(i));
}
}
- void removeStacksWithActivityTypes(int... activityTypes) {
+ void removeRootTasksWithActivityTypes(int... activityTypes) {
if (activityTypes == null || activityTypes.length == 0) {
return;
}
- // Collect the stacks that are necessary to be removed instead of performing the removal
- // by looping mStacks, so that we don't miss any stacks after the stack size changed or
- // stacks reordered.
- final ArrayList<Task> stacks = new ArrayList<>();
+ // Collect the root tasks that are necessary to be removed instead of performing the removal
+ // by looping the children, so that we don't miss any root tasks after the children size
+ // changed or reordered.
+ final ArrayList<Task> rootTasks = new ArrayList<>();
for (int j = activityTypes.length - 1; j >= 0; --j) {
final int activityType = activityTypes[j];
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final Task stack = getStackAt(i);
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task rootTask = mChildren.get(i);
// Collect the root tasks that are currently being organized.
- if (stack.mCreatedByOrganizer) {
- for (int k = stack.getChildCount() - 1; k >= 0; --k) {
- final Task childStack = (Task) stack.getChildAt(k);
- if (childStack.getActivityType() == activityType) {
- stacks.add(childStack);
+ if (rootTask.mCreatedByOrganizer) {
+ for (int k = rootTask.getChildCount() - 1; k >= 0; --k) {
+ final Task task = (Task) rootTask.getChildAt(k);
+ if (task.getActivityType() == activityType) {
+ rootTasks.add(task);
}
}
- } else if (stack.getActivityType() == activityType) {
- stacks.add(stack);
+ } else if (rootTask.getActivityType() == activityType) {
+ rootTasks.add(rootTask);
}
}
}
- for (int i = stacks.size() - 1; i >= 0; --i) {
- mRootWindowContainer.mStackSupervisor.removeStack(stacks.get(i));
+ for (int i = rootTasks.size() - 1; i >= 0; --i) {
+ mRootWindowContainer.mStackSupervisor.removeRootTask(rootTasks.get(i));
}
}
@@ -1442,12 +1460,13 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Returns true if the {@param windowingMode} is supported based on other parameters passed in.
- * @param windowingMode The windowing mode we are checking support for.
+ *
+ * @param windowingMode The windowing mode we are checking support for.
* @param supportsMultiWindow If we should consider support for multi-window mode in general.
* @param supportsSplitScreen If we should consider support for split-screen multi-window.
- * @param supportsFreeform If we should consider support for freeform multi-window.
- * @param supportsPip If we should consider support for picture-in-picture mutli-window.
- * @param activityType The activity type under consideration.
+ * @param supportsFreeform If we should consider support for freeform multi-window.
+ * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+ * @param activityType The activity type under consideration.
* @return true if the windowing mode is supported.
*/
private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
@@ -1486,9 +1505,9 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
* display with the provided parameters.
*
- * @param r The ActivityRecord in question.
- * @param options Options to start with.
- * @param task The task within-which the activity would start.
+ * @param r The ActivityRecord in question.
+ * @param options Options to start with.
+ * @param task The task within-which the activity would start.
* @param activityType The type of activity to start.
* @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
*/
@@ -1523,9 +1542,9 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* on this display.
*
* @param windowingMode The windowing-mode to validate.
- * @param r The {@link ActivityRecord} to check against.
- * @param task The {@link Task} to check against.
- * @param activityType An activity type.
+ * @param r The {@link ActivityRecord} to check against.
+ * @param task The {@link Task} to check against.
+ * @param activityType An activity type.
* @return {@code true} if windowingMode is valid, {@code false} otherwise.
*/
boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
@@ -1550,7 +1569,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return windowingMode != WINDOWING_MODE_UNDEFINED
&& isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
- supportsFreeform, supportsPip, activityType);
+ supportsFreeform, supportsPip, activityType);
}
/**
@@ -1558,9 +1577,9 @@ final class TaskDisplayArea extends DisplayArea<Task> {
* on this display.
*
* @param windowingMode The windowing-mode to validate.
- * @param r The {@link ActivityRecord} to check against.
- * @param task The {@link Task} to check against.
- * @param activityType An activity type.
+ * @param r The {@link ActivityRecord} to check against.
+ * @param task The {@link Task} to check against.
+ * @param activityType An activity type.
* @return The provided windowingMode or the closest valid mode which is appropriate.
*/
int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
@@ -1583,20 +1602,6 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return stack == getTopStack();
}
- boolean isTopNotFinishNotPinnedStack(Task stack) {
- for (int i = getStackCount() - 1; i >= 0; --i) {
- final Task current = getStackAt(i);
- final ActivityRecord topAct = current.getTopNonFinishingActivity();
- if (topAct == null) {
- continue;
- }
- if (!current.inPinnedWindowingMode()) {
- return current == stack;
- }
- }
- return false;
- }
-
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* considerKeyguardState */);
}
@@ -1644,10 +1649,12 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return topRunning;
}
+ // TODO (b/157876447): switch to Task related name
protected int getStackCount() {
return mChildren.size();
}
+ // TODO (b/157876447): switch to Task related name
protected Task getStackAt(int index) {
return mChildren.get(index);
}
@@ -1660,6 +1667,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Returns the existing home stack or creates and returns a new one if it should exist for the
* display.
+ *
* @param onTop Only be used when there is no existing home stack. If true the home stack will
* be created at the top of the display, else at the bottom.
*/
@@ -1797,7 +1805,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* @return the stack currently above the {@param stack}. Can be null if the {@param stack} is
- * already top-most.
+ * already top-most.
*/
static Task getStackAbove(Task stack) {
final WindowContainer wc = stack.getParent();
@@ -1843,6 +1851,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Notifies of a stack order change
+ *
* @param stack The stack which triggered the order change
*/
void onStackOrderChanged(Task stack) {
@@ -1873,8 +1882,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
notifyClients);
}
} finally {
- mAtmService.mStackSupervisor.endActivityVisibilityUpdate(starting, configChanges,
- preserveWindows, notifyClients);
+ mAtmService.mStackSupervisor.endActivityVisibilityUpdate();
}
}
@@ -1887,6 +1895,7 @@ final class TaskDisplayArea extends DisplayArea<Task> {
/**
* Removes the stacks in the node applying the content removal node from the display.
+ *
* @return last reparented stack, or {@code null} if the stacks had to be destroyed.
*/
Task remove() {
@@ -1943,6 +1952,12 @@ final class TaskDisplayArea extends DisplayArea<Task> {
return lastReparentedStack;
}
+ /** Whether this task display area is the last focused one on this logical display. */
+ @VisibleForTesting
+ boolean isLastFocused() {
+ return mDisplayContent.getLastFocusedTaskDisplayArea() == this;
+ }
+
@Override
protected boolean isTaskDisplayArea() {
return true;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index e07c5677214b..48550ed016c3 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -32,6 +32,7 @@ import android.app.ActivityManager.TaskDescription;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -39,6 +40,7 @@ import android.util.Slog;
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
+import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,8 +78,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
WINDOWING_MODE_FREEFORM
};
- private final WindowManagerGlobalLock mGlobalLock;
-
private class DeathRecipient implements IBinder.DeathRecipient {
ITaskOrganizer mTaskOrganizer;
@@ -103,39 +103,38 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
* transaction before they are presented to the task org.
*/
private class TaskOrganizerCallbacks {
- final WindowManagerService mService;
final ITaskOrganizer mTaskOrganizer;
final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
- private final SurfaceControl.Transaction mTransaction;
-
- TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg,
+ TaskOrganizerCallbacks(ITaskOrganizer taskOrg,
Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
- mService = wm;
mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
mTaskOrganizer = taskOrg;
- mTransaction = wm.mTransactionFactory.get();
}
IBinder getBinder() {
return mTaskOrganizer.asBinder();
}
+ SurfaceControl prepareLeash(Task task, boolean visible, String reason) {
+ SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(), reason);
+ if (!task.mCreatedByOrganizer && !visible) {
+ // To prevent flashes, we hide the task prior to sending the leash to the
+ // task org if the task has previously hidden (ie. when entering PIP)
+ mTransaction.hide(outSurfaceControl);
+ mTransaction.apply();
+ }
+ return outSurfaceControl;
+ }
+
void onTaskAppeared(Task task) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task appeared taskId=%d", task.mTaskId);
final boolean visible = task.isVisible();
final RunningTaskInfo taskInfo = task.getTaskInfo();
mDeferTaskOrgCallbacksConsumer.accept(() -> {
try {
- SurfaceControl outSurfaceControl = new SurfaceControl(task.getSurfaceControl(),
- "TaskOrganizerController.onTaskAppeared");
- if (!task.mCreatedByOrganizer && !visible) {
- // To prevent flashes, we hide the task prior to sending the leash to the
- // task org if the task has previously hidden (ie. when entering PIP)
- mTransaction.hide(outSurfaceControl);
- mTransaction.apply();
- }
- mTaskOrganizer.onTaskAppeared(taskInfo, outSurfaceControl);
+ mTaskOrganizer.onTaskAppeared(taskInfo, prepareLeash(task, visible,
+ "TaskOrganizerController.onTaskAppeared"));
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
}
@@ -208,8 +207,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mDeferTaskOrgCallbacksConsumer != null
? mDeferTaskOrgCallbacksConsumer
: mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
- mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer,
- deferTaskOrgCallbacksConsumer);
+ mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
mDeathRecipient = new DeathRecipient(organizer);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -219,6 +217,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mUid = uid;
}
+ /**
+ * Register this task with this state, but doesn't trigger the task appeared callback to
+ * the organizer.
+ */
+ SurfaceControl addTaskWithoutCallback(Task t, String reason) {
+ t.mTaskAppearedSent = true;
+ if (!mOrganizedTasks.contains(t)) {
+ mOrganizedTasks.add(t);
+ }
+ return mOrganizer.prepareLeash(t, t.isVisible(), reason);
+ }
+
void addTask(Task t) {
if (t.mTaskAppearedSent) return;
@@ -265,6 +275,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
}
+ private final ActivityTaskManagerService mService;
+ private final WindowManagerGlobalLock mGlobalLock;
+
// List of task organizers by priority
private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>();
private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
@@ -273,8 +286,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
// Set of organized tasks (by taskId) that dispatch back pressed to their organizers
private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet();
- private final ActivityTaskManagerService mService;
-
+ private SurfaceControl.Transaction mTransaction;
private RunningTaskInfo mTmpTaskInfo;
private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
@@ -299,7 +311,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
* Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
*/
@Override
- public void registerTaskOrganizer(ITaskOrganizer organizer) {
+ public ParceledListSlice<TaskAppearedInfo> registerTaskOrganizer(ITaskOrganizer organizer) {
enforceStackPermission("registerTaskOrganizer()");
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -307,17 +319,36 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
synchronized (mGlobalLock) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
organizer.asBinder(), uid);
+
+ // Defer initializing the transaction since the transaction factory can be set up
+ // by the tests after construction of the controller
+ if (mTransaction == null) {
+ mTransaction = mService.mWindowManager.mTransactionFactory.get();
+ }
+
if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
mTaskOrganizers.add(organizer);
mTaskOrganizerStates.put(organizer.asBinder(),
new TaskOrganizerState(organizer, uid));
}
+
+ final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+ final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
mService.mRootWindowContainer.forAllTasks((task) -> {
if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) {
return;
}
- task.updateTaskOrganizerState(true /* forceUpdate */);
+
+ boolean returnTask = !task.mCreatedByOrganizer;
+ task.updateTaskOrganizerState(true /* forceUpdate */,
+ returnTask /* skipTaskAppeared */);
+ if (returnTask) {
+ SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task,
+ "TaskOrganizerController.registerTaskOrganizer");
+ taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
+ }
});
+ return new ParceledListSlice<>(taskInfos);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -354,7 +385,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
: null;
}
- private boolean isSupportedWindowingMode(int winMode) {
+ boolean isSupportedWindowingMode(int winMode) {
return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index abe632941b97..a6f0f464c8b3 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -96,7 +96,6 @@ class TaskPositioner implements IBinder.DeathRecipient {
boolean mDragEnded;
IBinder mClientCallback;
- InputChannel mServerChannel;
InputChannel mClientChannel;
InputApplicationHandle mDragApplicationHandle;
InputWindowHandle mDragWindowHandle;
@@ -220,10 +219,7 @@ class TaskPositioner implements IBinder.DeathRecipient {
}
mDisplayContent = displayContent;
- final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
- mServerChannel = channels[0];
- mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel);
+ mClientChannel = mService.mInputManager.createInputChannel(TAG);
mInputEventReceiver = new WindowPositionerEventReceiver(
mClientChannel, mService.mAnimationHandler.getLooper(),
@@ -237,7 +233,7 @@ class TaskPositioner implements IBinder.DeathRecipient {
mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
displayContent.getDisplayId());
mDragWindowHandle.name = TAG;
- mDragWindowHandle.token = mServerChannel.getToken();
+ mDragWindowHandle.token = mClientChannel.getToken();
mDragWindowHandle.layoutParamsFlags = 0;
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -299,14 +295,12 @@ class TaskPositioner implements IBinder.DeathRecipient {
}
mService.mTaskPositioningController.hideInputSurface(mDisplayContent.getDisplayId());
- mService.mInputManager.unregisterInputChannel(mServerChannel.getToken());
+ mService.mInputManager.removeInputChannel(mClientChannel.getToken());
mInputEventReceiver.dispose();
mInputEventReceiver = null;
mClientChannel.dispose();
- mServerChannel.dispose();
mClientChannel = null;
- mServerChannel = null;
mDragWindowHandle = null;
mDragApplicationHandle = null;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index b9a449f558f0..9d35c25fc546 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -188,7 +188,7 @@ class TaskPositioningController {
transferFocusFromWin = displayContent.mCurrentFocus;
}
if (!mInputManager.transferTouchFocus(
- transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
+ transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel)) {
Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
cleanUpTaskPositioner();
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index b475846d60ed..87b470a54f77 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -368,7 +368,8 @@ class TaskSnapshotController {
SurfaceControl[] excludeLayers;
final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
- if (imeWindow != null) {
+ // Exclude IME window snapshot when IME isn't proper to attach to app.
+ if (imeWindow != null && !task.getDisplayContent().isImeAttachedToApp()) {
excludeLayers = new SurfaceControl[1];
excludeLayers[0] = imeWindow.getSurfaceControl();
} else {
@@ -481,9 +482,7 @@ class TaskSnapshotController {
final int color = ColorUtils.setAlphaComponent(
task.getTaskDescription().getBackgroundColor(), 255);
final LayoutParams attrs = mainWindow.getAttrs();
- final InsetsPolicy insetsPolicy = mainWindow.getDisplayContent().getInsetsPolicy();
- final InsetsState insetsState =
- new InsetsState(insetsPolicy.getInsetsForDispatch(mainWindow));
+ final InsetsState insetsState = new InsetsState(mainWindow.getInsetsState());
mergeInsetsSources(insetsState, mainWindow.getRequestedInsetsState());
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index c4ca989e9d6b..6904740343a6 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -237,15 +237,11 @@ class TaskSnapshotSurface implements StartingSurface {
task.getBounds(taskBounds);
currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation;
activityType = activity.getActivityType();
-
- final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent()
- .getInsetsPolicy();
- insetsState =
- new InsetsState(insetsPolicy.getInsetsForDispatch(topFullscreenOpaqueWindow));
+ insetsState = new InsetsState(topFullscreenOpaqueWindow.getInsetsState());
mergeInsetsSources(insetsState, topFullscreenOpaqueWindow.getRequestedInsetsState());
}
try {
- final int res = session.addToDisplay(window, window.mSeq, layoutParams,
+ final int res = session.addToDisplay(window, layoutParams,
View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrames.frame,
tmpFrames.contentInsets, tmpFrames.stableInsets, tmpFrames.displayCutout,
null /* outInputChannel */, mTmpInsetsState, mTempControls);
@@ -262,7 +258,7 @@ class TaskSnapshotSurface implements StartingSurface {
insetsState);
window.setOuter(snapshotSurface);
try {
- session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
mTempControls, sTmpSurfaceSize, sTmpSurfaceControl);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
new file mode 100644
index 000000000000..fc67cd22ee69
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -0,0 +1,487 @@
+/*
+ * 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 android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.window.TransitionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents a logical transition.
+ * @see TransitionController
+ */
+class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+ private static final String TAG = "Transition";
+
+ /** The transition has been created and is collecting, but hasn't formally started. */
+ private static final int STATE_COLLECTING = 0;
+
+ /**
+ * The transition has formally started. It is still collecting but will stop once all
+ * participants are ready to animate (finished drawing).
+ */
+ private static final int STATE_STARTED = 1;
+
+ /**
+ * This transition is currently playing its animation and can no longer collect or be changed.
+ */
+ private static final int STATE_PLAYING = 2;
+
+ final @WindowManager.TransitionType int mType;
+ private int mSyncId;
+ private @WindowManager.TransitionFlags int mFlags;
+ private final TransitionController mController;
+ final ArrayMap<WindowContainer, ChangeInfo> mParticipants = new ArrayMap<>();
+ private int mState = STATE_COLLECTING;
+ private boolean mReadyCalled = false;
+
+ Transition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags, TransitionController controller) {
+ mType = type;
+ mFlags = flags;
+ mController = controller;
+ mSyncId = mController.mSyncEngine.startSyncSet(this);
+ }
+
+ /**
+ * Formally starts the transition. Participants can be collected before this is started,
+ * but this won't consider itself ready until started -- even if all the participants have
+ * drawn.
+ */
+ void start() {
+ if (mState >= STATE_STARTED) {
+ Slog.w(TAG, "Transition already started: " + mSyncId);
+ }
+ mState = STATE_STARTED;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
+ mSyncId);
+ if (mReadyCalled) {
+ setReady();
+ }
+ }
+
+ /** Adds wc to set of WindowContainers participating in this transition. */
+ void collect(@NonNull WindowContainer wc) {
+ if (mSyncId < 0) return;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
+ mSyncId, wc);
+ // Add to sync set before checking contains because it may not have added it at other
+ // times (eg. if wc was previously invisible).
+ mController.mSyncEngine.addToSyncSet(mSyncId, wc);
+ if (mParticipants.containsKey(wc)) return;
+ mParticipants.put(wc, new ChangeInfo());
+ }
+
+ /**
+ * Call this when all known changes related to this transition have been applied. Until
+ * all participants have finished drawing, the transition can still collect participants.
+ *
+ * If this is called before the transition is started, it will be deferred until start.
+ */
+ void setReady() {
+ if (mSyncId < 0) return;
+ if (mState < STATE_STARTED) {
+ mReadyCalled = true;
+ return;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Finish collecting in transition %d", mSyncId);
+ mController.mSyncEngine.setReady(mSyncId);
+ mController.mAtm.mWindowManager.mWindowPlacerLocked.requestTraversal();
+ }
+
+ /** The transition has finished animating and is ready to finalize WM state */
+ void finishTransition() {
+ if (mState < STATE_PLAYING) {
+ throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
+ }
+ for (int i = 0; i < mParticipants.size(); ++i) {
+ final ActivityRecord ar = mParticipants.keyAt(i).asActivityRecord();
+ if (ar == null || ar.mVisibleRequested) {
+ continue;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit activity becoming invisible: %s", ar);
+ ar.commitVisibility(false /* visible */, false /* performLayout */);
+ }
+ }
+
+ @Override
+ public void onTransactionReady(int syncId, Set<WindowContainer> windowContainersReady) {
+ if (syncId != mSyncId) {
+ Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
+ return;
+ }
+ mState = STATE_PLAYING;
+ mController.moveToPlaying(this);
+ final TransitionInfo info = calculateTransitionInfo(mType, mParticipants);
+
+ SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
+ int displayId = Display.DEFAULT_DISPLAY;
+ for (WindowContainer container : windowContainersReady) {
+ container.mergeBlastSyncTransaction(mergedTransaction);
+ displayId = container.mDisplayContent.getDisplayId();
+ }
+
+ handleNonAppWindowsInTransition(displayId, mType, mFlags);
+
+ if (mController.getTransitionPlayer() != null) {
+ try {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Calling onTransitionReady: %s", info);
+ mController.getTransitionPlayer().onTransitionReady(this, info, mergedTransaction);
+ } catch (RemoteException e) {
+ // If there's an exception when trying to send the mergedTransaction to the
+ // client, we should immediately apply it here so the transactions aren't lost.
+ mergedTransaction.apply();
+ }
+ } else {
+ mergedTransaction.apply();
+ }
+ mSyncId = -1;
+ }
+
+ private void handleNonAppWindowsInTransition(int displayId, int transit, int flags) {
+ final DisplayContent dc =
+ mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) {
+ return;
+ }
+ if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
+ if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
+ && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
+ Animation anim = mController.mAtm.mWindowManager.mPolicy
+ .createKeyguardWallpaperExit(
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
+ if (anim != null) {
+ anim.scaleCurrentDuration(
+ mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
+ dc.mWallpaperController.startWallpaperAnimation(anim);
+ }
+ }
+ }
+ if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+ dc.startKeyguardExitOnNonAppWindows(
+ transit == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
+ (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
+ mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(transit, 0);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("TransitionRecord{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" id=" + mSyncId);
+ sb.append(" type=" + mType);
+ sb.append(" flags=" + mFlags);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private static boolean reportIfNotTop(WindowContainer wc) {
+ // Organized tasks need to be reported anyways because Core won't show() their surfaces
+ // and we can't rely on onTaskAppeared because it isn't in sync.
+ // TODO(shell-transitions): switch onTaskAppeared usage over to transitions OPEN.
+ return wc.isOrganized();
+ }
+
+ /** @return the depth of child within ancestor, 0 if child == ancestor, or -1 if not a child. */
+ private static int getChildDepth(WindowContainer child, WindowContainer ancestor) {
+ WindowContainer parent = child;
+ int depth = 0;
+ while (parent != null) {
+ if (parent == ancestor) {
+ return depth;
+ }
+ parent = parent.getParent();
+ ++depth;
+ }
+ return -1;
+ }
+
+ private static @TransitionInfo.TransitionMode int getModeFor(WindowContainer wc) {
+ if (wc.isVisibleRequested()) {
+ final Task t = wc.asTask();
+ if (t != null && t.getHasBeenVisible()) {
+ return TransitionInfo.TRANSIT_SHOW;
+ }
+ return TransitionInfo.TRANSIT_OPEN;
+ }
+ return TransitionInfo.TRANSIT_CLOSE;
+ }
+
+ /**
+ * Under some conditions (eg. all visible targets within a parent container are transitioning
+ * the same way) the transition can be "promoted" to the parent container. This means an
+ * animation can play just on the parent rather than all the individual children.
+ *
+ * @return {@code true} if transition in target can be promoted to its parent.
+ */
+ private static boolean canPromote(
+ WindowContainer target, ArraySet<WindowContainer> topTargets) {
+ final WindowContainer parent = target.getParent();
+ if (parent == null || !parent.canCreateRemoteAnimationTarget()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " SKIP: %s",
+ parent == null ? "no parent" : ("parent can't be target " + parent));
+ return false;
+ }
+ @TransitionInfo.TransitionMode int mode = TransitionInfo.TRANSIT_NONE;
+ // Go through all siblings of this target to see if any of them would prevent
+ // the target from promoting.
+ siblingLoop:
+ for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer sibling = parent.getChildAt(i);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " check sibling %s",
+ sibling);
+ // Check if any topTargets are the sibling or within it
+ for (int j = topTargets.size() - 1; j >= 0; --j) {
+ final int depth = getChildDepth(topTargets.valueAt(j), sibling);
+ if (depth < 0) continue;
+ if (depth == 0) {
+ final int siblingMode = sibling.isVisibleRequested()
+ ? TransitionInfo.TRANSIT_OPEN : TransitionInfo.TRANSIT_CLOSE;
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " sibling is a top target with mode %s",
+ TransitionInfo.modeToString(siblingMode));
+ if (mode == TransitionInfo.TRANSIT_NONE) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " no common mode yet, so set it");
+ mode = siblingMode;
+ } else if (mode != siblingMode) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: common mode mismatch. was %s",
+ TransitionInfo.modeToString(mode));
+ return false;
+ }
+ continue siblingLoop;
+ } else {
+ // Sibling subtree may not be promotable.
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: sibling contains top target %s",
+ topTargets.valueAt(j));
+ return false;
+ }
+ }
+ // No other animations are playing in this sibling
+ if (sibling.isVisibleRequested()) {
+ // Sibling is visible but not animating, so no promote.
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " SKIP: sibling is visible but not part of transition");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Go through topTargets and try to promote (see {@link #canPromote}) one of them.
+ *
+ * @param topTargets set of just the top-most targets in the hierarchy of participants.
+ * @param targets all targets that will be sent to the player.
+ * @return {@code true} if something was promoted.
+ */
+ private static boolean tryPromote(ArraySet<WindowContainer> topTargets,
+ ArrayMap<WindowContainer, ChangeInfo> targets) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " --- Start combine pass ---");
+ // Go through each target until we find one that can be promoted.
+ targetLoop:
+ for (WindowContainer targ : topTargets) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " checking %s", targ);
+ if (!canPromote(targ, topTargets)) {
+ continue;
+ }
+ final WindowContainer parent = targ.getParent();
+ // No obstructions found to promotion, so promote
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " CAN PROMOTE: promoting to parent %s", parent);
+ final ChangeInfo parentInfo = new ChangeInfo();
+ targets.put(parent, parentInfo);
+ // Go through all children of newly-promoted container and remove them from
+ // the top-targets.
+ for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer child = parent.getChildAt(i);
+ int idx = targets.indexOfKey(child);
+ if (idx >= 0) {
+ if (reportIfNotTop(child)) {
+ targets.valueAt(idx).mParent = parent;
+ parentInfo.addChild(child);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " keep as target %s", child);
+ } else {
+ if (targets.valueAt(idx).mChildren != null) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " merging children in from %s: %s", child,
+ targets.valueAt(idx).mChildren);
+ parentInfo.addChildren(targets.valueAt(idx).mChildren);
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " remove from targets %s", child);
+ targets.removeAt(idx);
+ }
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " remove from topTargets %s", child);
+ topTargets.remove(child);
+ }
+ topTargets.add(parent);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Find WindowContainers to be animated from a set of opening and closing apps. We will promote
+ * animation targets to higher level in the window hierarchy if possible.
+ */
+ @VisibleForTesting
+ static TransitionInfo calculateTransitionInfo(
+ int type, Map<WindowContainer, ChangeInfo> participants) {
+ final TransitionInfo out = new TransitionInfo(type);
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Start calculating TransitionInfo based on participants: %s",
+ new ArraySet<>(participants.keySet()));
+
+ final ArraySet<WindowContainer> topTargets = new ArraySet<>();
+ // The final animation targets which cannot promote to higher level anymore.
+ final ArrayMap<WindowContainer, ChangeInfo> targets = new ArrayMap<>();
+
+ final ArrayList<WindowContainer> tmpList = new ArrayList<>();
+
+ // Build initial set of top-level participants by removing any participants that are
+ // children of other participants or are otherwise invalid.
+ for (Map.Entry<WindowContainer, ChangeInfo> entry : participants.entrySet()) {
+ final WindowContainer wc = entry.getKey();
+ // Don't include detached windows.
+ if (!wc.isAttached()) continue;
+
+ final ChangeInfo changeInfo = entry.getValue();
+ WindowContainer parent = wc.getParent();
+ WindowContainer topParent = null;
+ // Keep track of always-report parents in bottom-to-top order
+ tmpList.clear();
+ while (parent != null) {
+ if (participants.containsKey(parent)) {
+ topParent = parent;
+ } else if (reportIfNotTop(parent)) {
+ tmpList.add(parent);
+ }
+ parent = parent.getParent();
+ }
+ if (topParent != null) {
+ // Add always-report parents along the way
+ parent = topParent;
+ for (int i = tmpList.size() - 1; i >= 0; --i) {
+ if (!participants.containsKey(tmpList.get(i))) {
+ final ChangeInfo info = new ChangeInfo();
+ info.mParent = parent;
+ targets.put(tmpList.get(i), info);
+ }
+ parent = tmpList.get(i);
+ }
+ continue;
+ }
+ targets.put(wc, changeInfo);
+ topTargets.add(wc);
+ }
+
+ // Populate children lists
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ if (targets.valueAt(i).mParent != null) {
+ targets.get(targets.valueAt(i).mParent).addChild(targets.keyAt(i));
+ }
+ }
+
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Initial targets: %s", new ArraySet<>(targets.keySet()));
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Top targets: %s", topTargets);
+
+ // Combine targets by repeatedly going through the topTargets to see if they can be
+ // promoted until there aren't any promotions possible.
+ while (tryPromote(topTargets, targets)) {
+ // Empty on purpose
+ }
+
+ // Convert all the resolved ChangeInfos into a TransactionInfo object.
+ for (int i = targets.size() - 1; i >= 0; --i) {
+ final WindowContainer target = targets.keyAt(i);
+ final ChangeInfo info = targets.valueAt(i);
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ target.mRemoteToken.toWindowContainerToken(), target.getSurfaceControl());
+ if (info.mParent != null) {
+ change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+ }
+ change.setMode(getModeFor(target));
+ out.addChange(change);
+ }
+
+ return out;
+ }
+
+ static Transition fromBinder(IBinder binder) {
+ return (Transition) binder;
+ }
+
+ @VisibleForTesting
+ static class ChangeInfo {
+ WindowContainer mParent;
+ ArraySet<WindowContainer> mChildren;
+ // TODO(shell-transitions): other tracking like before state and bounds
+ void addChild(@NonNull WindowContainer wc) {
+ if (mChildren == null) {
+ mChildren = new ArraySet<>();
+ }
+ mChildren.add(wc);
+ }
+ void addChildren(@NonNull ArraySet<WindowContainer> wcs) {
+ if (mChildren == null) {
+ mChildren = new ArraySet<>();
+ }
+ mChildren.addAll(wcs);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
new file mode 100644
index 000000000000..d102c19bfff9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -0,0 +1,225 @@
+/*
+ * 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 android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
+import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.window.ITransitionPlayer;
+
+import com.android.internal.protolog.ProtoLogGroup;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Handles all the aspects of recording and synchronizing transitions.
+ */
+class TransitionController {
+ private static final String TAG = "TransitionController";
+
+ private static final int[] SUPPORTED_LEGACY_TRANSIT_TYPES = {TRANSIT_TASK_OPEN,
+ TRANSIT_TASK_CLOSE, TRANSIT_TASK_TO_FRONT, TRANSIT_TASK_TO_BACK,
+ TRANSIT_TASK_OPEN_BEHIND, TRANSIT_KEYGUARD_GOING_AWAY};
+ static {
+ Arrays.sort(SUPPORTED_LEGACY_TRANSIT_TYPES);
+ }
+
+ final BLASTSyncEngine mSyncEngine = new BLASTSyncEngine();
+ private ITransitionPlayer mTransitionPlayer;
+ private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null;
+ final ActivityTaskManagerService mAtm;
+
+ /** Currently playing transitions. When finished, records are removed from this list. */
+ private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
+
+ /**
+ * The transition currently being constructed (collecting participants).
+ * TODO(shell-transitions): When simultaneous transitions are supported, merge this with
+ * mPlayingTransitions.
+ */
+ private Transition mCollectingTransition = null;
+
+ TransitionController(ActivityTaskManagerService atm) {
+ mAtm = atm;
+ }
+
+ /** @see #createTransition(int, int) */
+ @NonNull
+ Transition createTransition(int type) {
+ return createTransition(type, 0 /* flags */);
+ }
+
+ /**
+ * Creates a transition. It can immediately collect participants.
+ */
+ @NonNull
+ Transition createTransition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ if (mCollectingTransition != null) {
+ throw new IllegalStateException("Simultaneous transitions not supported yet.");
+ }
+ mCollectingTransition = new Transition(type, flags, this);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
+ mCollectingTransition);
+ return mCollectingTransition;
+ }
+
+ void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ try {
+ if (mTransitionPlayer != null) {
+ mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
+ mTransitionPlayer = null;
+ }
+ player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
+ mTransitionPlayer = player;
+ } catch (RemoteException e) {
+ throw new RuntimeException("Unable to set transition player");
+ }
+ }
+
+ @Nullable ITransitionPlayer getTransitionPlayer() {
+ return mTransitionPlayer;
+ }
+
+ boolean isShellTransitionsEnabled() {
+ return mTransitionPlayer != null;
+ }
+
+ /** @return {@code true} if a transition is running */
+ boolean inTransition() {
+ // TODO(shell-transitions): eventually properly support multiple
+ return mCollectingTransition != null || !mPlayingTransitions.isEmpty();
+ }
+
+ /** @return {@code true} if wc is in a participant subtree */
+ boolean inTransition(@NonNull WindowContainer wc) {
+ if (mCollectingTransition != null && mCollectingTransition.mParticipants.containsKey(wc)) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mPlayingTransitions.get(i).mParticipants.containsKey(p)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates a transition and asks the TransitionPlayer (Shell) to start it.
+ * @return the created transition. Collection can start immediately.
+ */
+ @NonNull
+ Transition requestTransition(@WindowManager.TransitionType int type) {
+ return requestTransition(type, 0 /* flags */);
+ }
+
+ /** @see #requestTransition */
+ @NonNull
+ Transition requestTransition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags) {
+ if (mTransitionPlayer == null) {
+ throw new IllegalStateException("Shell Transitions not enabled");
+ }
+ final Transition transition = createTransition(type, flags);
+ try {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Requesting StartTransition: %s", transition);
+ mTransitionPlayer.requestStartTransition(type, transition);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error requesting transition", e);
+ transition.start();
+ }
+ return transition;
+ }
+
+ /**
+ * Temporary adapter that converts the legacy AppTransition's prepareAppTransition call into
+ * a Shell transition request. If shell transitions are enabled, this will take priority in
+ * handling transition types that it supports. All other transitions will be ignored and thus
+ * be handled by the legacy apptransition system. This allows both worlds to live in tandem
+ * during migration.
+ *
+ * @return {@code true} if the transition is handled.
+ */
+ boolean adaptLegacyPrepare(@WindowManager.TransitionType int transit,
+ @WindowManager.TransitionFlags int flags, boolean forceOverride) {
+ if (!isShellTransitionsEnabled()
+ || Arrays.binarySearch(SUPPORTED_LEGACY_TRANSIT_TYPES, transit) < 0) {
+ return false;
+ }
+ if (inTransition()) {
+ if (AppTransition.isKeyguardTransit(transit)) {
+ // TODO(shell-transitions): add to flags
+ } else if (forceOverride) {
+ // TODO(shell-transitions): sort out these flags
+ } else if (transit == TRANSIT_CRASHING_ACTIVITY_CLOSE) {
+ // TODO(shell-transitions): record crashing
+ }
+ } else {
+ requestTransition(transit, flags);
+ }
+ return true;
+ }
+
+ /** @see Transition#collect */
+ void collect(@NonNull WindowContainer wc) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.collect(wc);
+ }
+
+ /** @see Transition#setReady */
+ void setReady() {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setReady();
+ }
+
+ /** @see Transition#finishTransition */
+ void finishTransition(@NonNull IBinder token) {
+ final Transition record = Transition.fromBinder(token);
+ if (record == null || !mPlayingTransitions.contains(record)) {
+ Slog.e(TAG, "Trying to finish a non-playing transition " + token);
+ return;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
+ mPlayingTransitions.remove(record);
+ record.finishTransition();
+ }
+
+ void moveToPlaying(Transition transition) {
+ if (transition != mCollectingTransition) {
+ throw new IllegalStateException("Trying to move non-collecting transition to playing");
+ }
+ mCollectingTransition = null;
+ mPlayingTransitions.add(transition);
+ }
+
+}
diff --git a/services/core/java/com/android/server/wm/VrController.java b/services/core/java/com/android/server/wm/VrController.java
index 54cac3845798..9e159aba4d77 100644
--- a/services/core/java/com/android/server/wm/VrController.java
+++ b/services/core/java/com/android/server/wm/VrController.java
@@ -101,7 +101,7 @@ final class VrController {
// - Calls to setPersistentVrThread will fail.
// - No threads will have elevated scheduling priority for VR.
//
- private int mVrState = FLAG_NON_VR_MODE;
+ private volatile int mVrState = FLAG_NON_VR_MODE;
// The single VR render thread on the device that is given elevated scheduling priority.
private int mVrRenderThreadTid = 0;
@@ -146,6 +146,14 @@ final class VrController {
}
/**
+ * Called without lock to determine whether to call {@link #onTopProcChangedLocked} in lock. It
+ * is used to optimize performance for the path that may have lock contention frequently.
+ */
+ boolean isInterestingToSchedGroup() {
+ return (mVrState & (FLAG_VR_MODE | FLAG_PERSISTENT_VR_MODE)) != 0;
+ }
+
+ /**
* Called when ActivityManagerService's TOP_APP process has changed.
*
* <p>Note: This must be called with the global ActivityManagerService lock held.
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 38bff9ed3c31..416b9dfe50b4 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -93,7 +93,7 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
RemoteAnimationTarget createRemoteAnimationTarget() {
mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
mWallpaperToken.getPrefixOrderIndex(), new Point(), null, null,
- mWallpaperToken.getWindowConfiguration(), true, null, null);
+ mWallpaperToken.getWindowConfiguration(), true, null, null, null);
return mTarget;
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 5c6266a2f8c4..ff5c17487413 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -103,7 +103,8 @@ public class WindowAnimator {
mAnimationFrameCallback = frameTimeNs -> {
synchronized (mService.mGlobalLock) {
mAnimationFrameCallbackScheduled = false;
- animate(frameTimeNs);
+ final long vsyncId = mChoreographer.getVsyncId();
+ animate(frameTimeNs, vsyncId);
if (mNotifyWhenNoAnimation && !mLastRootAnimating) {
mService.mGlobalLock.notifyAll();
}
@@ -125,7 +126,7 @@ public class WindowAnimator {
mInitialized = true;
}
- private void animate(long frameTimeNs) {
+ private void animate(long frameTimeNs, long vsyncId) {
if (!mInitialized) {
return;
}
@@ -133,6 +134,8 @@ public class WindowAnimator {
// Schedule next frame already such that back-pressure happens continuously.
scheduleAnimation();
+ mTransaction.setFrameTimelineVsync(vsyncId);
+
mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
if (DEBUG_WINDOW_TRACE) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ae8f7a556ffd..0edaa1d821df 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
+import static android.content.pm.ActivityInfo.reverseOrientation;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -32,6 +33,7 @@ import static android.view.SurfaceControl.Transaction;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -416,7 +418,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void setInitialSurfaceControlProperties(SurfaceControl.Builder b) {
setSurfaceControl(b.setCallsite("WindowContainer.setInitialSurfaceControlProperties").build());
- getSyncTransaction().show(mSurfaceControl);
+ if (showSurfaceOnCreation()) {
+ getSyncTransaction().show(mSurfaceControl);
+ }
onSurfaceShown(getSyncTransaction());
updateSurfacePositionNonOrganized();
}
@@ -806,6 +810,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return parent != null ? parent.getDisplayArea() : null;
}
+ /** Get the first node of type {@link RootDisplayArea} above or at this node. */
+ @Nullable
+ RootDisplayArea getRootDisplayArea() {
+ WindowContainer parent = getParent();
+ return parent != null ? parent.getRootDisplayArea() : null;
+ }
+
boolean isAttached() {
return getDisplayArea() != null;
}
@@ -850,13 +861,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
- void forceWindowsScaleableInTransaction(boolean force) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final WindowContainer wc = mChildren.get(i);
- wc.forceWindowsScaleableInTransaction(force);
- }
- }
-
/**
* @return {@code true} when an application can override an app transition animation on this
* container.
@@ -998,6 +1002,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
+ * Is this window's surface needed? This is almost like isVisible, except when participating
+ * in a transition, this will reflect the final visibility while isVisible won't change until
+ * the transition is finished.
+ */
+ boolean isVisibleRequested() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (child.isVisibleRequested()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Called when the visibility of a child is asked to change. This is before visibility actually
* changes (eg. a transition animation might play out first).
*/
@@ -1136,17 +1155,30 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* {@link Configuration#ORIENTATION_UNDEFINED}).
*/
int getRequestedConfigurationOrientation() {
- if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+ int requestedOrientation = mOrientation;
+ final RootDisplayArea root = getRootDisplayArea();
+ if (root != null && root.isOrientationDifferentFromDisplay()) {
+ // Reverse the requested orientation if the orientation of its root is different from
+ // the display, so that when the display rotates to the reversed orientation, the
+ // requested app will be in the requested orientation.
+ // For example, if the display is 1200x900 (landscape), and the DAG is 600x900
+ // (portrait).
+ // When an app below the DAG is requesting landscape, it should actually request the
+ // display to be portrait, so that the DAG and the app will be in landscape.
+ requestedOrientation = reverseOrientation(mOrientation);
+ }
+
+ if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// NOSENSOR means the display's "natural" orientation, so return that.
if (mDisplayContent != null) {
return mDisplayContent.getNaturalOrientation();
}
- } else if (mOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
+ } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// LOCKED means the activity's orientation remains unchanged, so return existing value.
return getConfiguration().orientation;
- } else if (isFixedOrientationLandscape(mOrientation)) {
+ } else if (isFixedOrientationLandscape(requestedOrientation)) {
return ORIENTATION_LANDSCAPE;
- } else if (isFixedOrientationPortrait(mOrientation)) {
+ } else if (isFixedOrientationPortrait(requestedOrientation)) {
return ORIENTATION_PORTRAIT;
}
return ORIENTATION_UNDEFINED;
@@ -2705,8 +2737,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
pw.print(prefix); pw.println("ContainerAnimator:");
mSurfaceAnimator.dump(pw, prefix + " ");
}
- if (mLastOrientationSource != null) {
+ if (mLastOrientationSource != null && this == mDisplayContent) {
pw.println(prefix + "mLastOrientationSource=" + mLastOrientationSource);
+ pw.println(prefix + "deepestLastOrientationSource=" + getLastOrientationSource());
}
}
@@ -2815,6 +2848,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ /**
+ * @return {@code true} if this container's surface should be shown when it is created.
+ */
+ boolean showSurfaceOnCreation() {
+ return true;
+ }
+
static WindowContainer fromBinder(IBinder binder) {
return RemoteToken.fromBinder(binder).getContainer();
}
@@ -2886,6 +2926,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// If we are invisible, no need to sync, likewise if we are already engaged in a sync,
// we can't support overlapping syncs on a single container yet.
if (!isVisible() || mWaitingListener != null) {
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "- NOT adding to sync: visible=%b "
+ + "hasListener=%b", isVisible(), mWaitingListener != null);
return false;
}
mUsingBLASTSyncTransaction = true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 315014c1b248..66cb674f0acd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -597,4 +597,9 @@ public abstract class WindowManagerInternal {
* @return The corresponding {@link WindowState#getName()}
*/
public abstract @Nullable String getImeTargetNameForLogging(int displayId);
+
+ /**
+ * Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}.
+ */
+ public abstract void moveWindowTokenToDisplay(IBinder binder, int displayId);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3faed2573157..63c1908c5faa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -120,6 +120,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_DISPLAY_ID;
import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_WINDOW;
+import static com.android.server.wm.WindowManagerServiceDumpProto.HARD_KEYBOARD_AVAILABLE;
import static com.android.server.wm.WindowManagerServiceDumpProto.INPUT_METHOD_WINDOW;
import static com.android.server.wm.WindowManagerServiceDumpProto.LAST_ORIENTATION;
import static com.android.server.wm.WindowManagerServiceDumpProto.POLICY;
@@ -413,7 +414,7 @@ public class WindowManagerService extends IWindowManager.Stub
* @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
*/
static boolean sDisableCustomTaskAnimationProperty =
- SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, false);
+ SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
"ro.sf.disable_triple_buffer";
@@ -669,6 +670,8 @@ public class WindowManagerService extends IWindowManager.Stub
// Whether the system should use BLAST for ViewRootImpl
final boolean mUseBLAST;
+ // Whether to enable BLASTSyncEngine Transaction passing.
+ final boolean mUseBLASTSync = false;
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
@@ -868,8 +871,7 @@ public class WindowManagerService extends IWindowManager.Stub
void updateSystemUiSettings() {
boolean changed;
synchronized (mGlobalLock) {
- changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext)
- || PolicyControl.reloadFromSetting(mContext);
+ changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext);
}
if (changed) {
updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */);
@@ -1009,7 +1011,7 @@ public class WindowManagerService extends IWindowManager.Stub
final Configuration mTempConfiguration = new Configuration();
- final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
+ final HighRefreshRateDenylist mHighRefreshRateDenylist;
// If true, only the core apps and services are being launched because the device
// is in a special boot mode, such as being encrypted or waiting for a decryption password.
@@ -1306,7 +1308,7 @@ public class WindowManagerService extends IWindowManager.Stub
this, mInputManager, mActivityTaskManager, mH.getLooper());
mDragDropController = new DragDropController(this, mH.getLooper());
- mHighRefreshRateBlacklist = HighRefreshRateBlacklist.create(context.getResources());
+ mHighRefreshRateDenylist = HighRefreshRateDenylist.create(context.getResources());
mConstants = new WindowManagerConstants(this, DeviceConfigInterface.REAL);
mConstants.start(new HandlerExecutor(mH));
@@ -1378,9 +1380,8 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
- public int addWindow(Session session, IWindow client, int seq,
- LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
- Rect outContentInsets, Rect outStableInsets,
+ public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
+ int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
int requestUserId) {
@@ -1561,7 +1562,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
final WindowState win = new WindowState(this, session, client, token, parentWindow,
- appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
+ appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
@@ -2017,7 +2018,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
void setTransparentRegionWindow(Session session, IWindow client, Region region) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
WindowState w = windowForClientLocked(session, client, false);
@@ -2035,7 +2036,7 @@ public class WindowManagerService extends IWindowManager.Stub
void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
Rect visibleInsets, Region touchableRegion) {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
WindowState w = windowForClientLocked(session, client, false);
@@ -2103,7 +2104,7 @@ public class WindowManagerService extends IWindowManager.Stub
== PackageManager.PERMISSION_GRANTED;
}
- public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
+ public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
@@ -2114,7 +2115,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean configChanged;
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
@@ -2135,7 +2136,7 @@ public class WindowManagerService extends IWindowManager.Stub
win.finishSeamlessRotation(false /* timeout */);
}
- if (win.useBLASTSync()) {
+ if (mUseBLASTSync && win.useBLASTSync()) {
result |= RELAYOUT_RES_BLAST_SYNC;
}
@@ -2145,17 +2146,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs, pid, uid);
win.mToken.adjustWindowParams(win, attrs);
- // if they don't have the permission, mask out the status bar bits
- if (seq == win.mSeq) {
- int systemUiVisibility = attrs.systemUiVisibility
- | attrs.subtreeSystemUiVisibility;
- if ((systemUiVisibility & DISABLE_MASK) != 0) {
- if (!hasStatusBarPermission(pid, uid)) {
- systemUiVisibility &= ~DISABLE_MASK;
- }
+ int systemUiVisibility = attrs.systemUiVisibility
+ | attrs.subtreeSystemUiVisibility;
+ if ((systemUiVisibility & DISABLE_MASK) != 0) {
+ // if they don't have the permission, mask out the status bar bits
+ if (!hasStatusBarPermission(pid, uid)) {
+ systemUiVisibility &= ~DISABLE_MASK;
}
- win.mSystemUiVisibility = systemUiVisibility;
}
+ win.mSystemUiVisibility = systemUiVisibility;
if (win.mAttrs.type != attrs.type) {
throw new IllegalArgumentException(
"Window type can not be changed after the window is added.");
@@ -2431,8 +2430,8 @@ public class WindowManagerService extends IWindowManager.Stub
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (winAnimator.mSurfaceController != null) {
- outSurfaceSize.set(winAnimator.mSurfaceController.getWidth(),
- winAnimator.mSurfaceController.getHeight());
+ win.calculateSurfaceBounds(win.getAttrs(), mTmpRect);
+ outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
}
getInsetsSourceControls(win, outActiveControls);
}
@@ -2468,7 +2467,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
+ if (mAtmService.getTransitionController().inTransition(win)) {
+ focusMayChange = true;
+ win.mAnimatingExit = true;
+ } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
focusMayChange = true;
win.mAnimatingExit = true;
} else if (win.isAnimating(TRANSITION | PARENTS)) {
@@ -2726,6 +2728,32 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /** @see WindowManagerInternal#moveWindowTokenToDisplay(IBinder, int) */
+ public void moveWindowTokenToDisplay(IBinder binder, int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId);
+ if (dc == null) {
+ ProtoLog.w(WM_ERROR, "moveWindowTokenToDisplay: Attempted to move token: %s"
+ + " to non-exiting displayId=%d", binder, displayId);
+ return;
+ }
+ final WindowToken token = mRoot.getWindowToken(binder);
+ if (token == null) {
+ ProtoLog.w(WM_ERROR,
+ "moveWindowTokenToDisplay: Attempted to move non-existing token: %s",
+ binder);
+ return;
+ }
+ if (token.getDisplayContent() == dc) {
+ ProtoLog.w(WM_ERROR,
+ "moveWindowTokenToDisplay: Cannot move to the original display "
+ + "for token: %s", binder);
+ return;
+ }
+ dc.reParentWindowToken(token);
+ }
+ }
+
void setNewDisplayOverrideConfiguration(Configuration overrideConfig,
@NonNull DisplayContent dc) {
if (dc.mWaitingForConfig) {
@@ -3060,7 +3088,7 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires INTERACT_ACROSS_USERS permission");
}
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
return mPolicy.isKeyguardSecure(userId);
} finally {
@@ -3742,7 +3770,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ "rotation constant.");
}
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final DisplayContent display = mRoot.getDisplayContent(displayId);
@@ -3777,7 +3805,7 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ORIENTATION, "thawRotation: mRotation=%d", getDefaultDisplayRotation());
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
final DisplayContent display = mRoot.getDisplayContent(displayId);
@@ -3830,7 +3858,7 @@ public class WindowManagerService extends IWindowManager.Stub
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
@@ -4123,7 +4151,7 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
}
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
mPolicy.setOverrideFoldedArea(area);
@@ -4137,7 +4165,7 @@ public class WindowManagerService extends IWindowManager.Stub
* Get the display folded area.
*/
@NonNull Rect getFoldedArea() {
- long origId = Binder.clearCallingIdentity();
+ final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
return mPolicy.getFoldedArea();
@@ -4154,7 +4182,7 @@ public class WindowManagerService extends IWindowManager.Stub
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Must hold permission " + MANAGE_ACTIVITY_STACKS);
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mDisplayNotificationController.registerListener(listener);
} finally {
@@ -5151,6 +5179,10 @@ public class WindowManagerService extends IWindowManager.Stub
return mUseBLAST;
}
+ public boolean useBLASTSync() {
+ return mUseBLASTSync;
+ }
+
@Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mGlobalLock) {
@@ -5760,31 +5792,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void statusBarVisibilityChanged(int displayId, int visibility) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold permission "
- + android.Manifest.permission.STATUS_BAR);
- }
-
- synchronized (mGlobalLock) {
- final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
- if (displayContent != null) {
- displayContent.statusBarVisibilityChanged(visibility);
- } else {
- Slog.w(TAG, "statusBarVisibilityChanged with invalid displayId=" + displayId);
- }
- }
- }
-
- @Override
public void hideTransientBars(int displayId) {
mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR,
"hideTransientBars()");
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent != null) {
- displayContent.hideTransientBars();
+ displayContent.getInsetsPolicy().hideTransient();
} else {
Slog.w(TAG, "hideTransientBars with invalid displayId=" + displayId);
}
@@ -5968,7 +5982,7 @@ public class WindowManagerService extends IWindowManager.Stub
private void dumpHighRefreshRateBlacklist(PrintWriter pw) {
pw.println("WINDOW MANAGER HIGH REFRESH RATE BLACKLIST (dumpsys window refresh)");
- mHighRefreshRateBlacklist.dump(pw);
+ mHighRefreshRateDenylist.dump(pw);
}
private void dumpTraceStatus(PrintWriter pw) {
@@ -6016,6 +6030,7 @@ public class WindowManagerService extends IWindowManager.Stub
proto.write(ROTATION, defaultDisplayContent.getRotation());
proto.write(LAST_ORIENTATION, defaultDisplayContent.getLastOrientation());
proto.write(FOCUSED_DISPLAY_ID, topFocusedDisplayContent.getDisplayId());
+ proto.write(HARD_KEYBOARD_AVAILABLE, mHardKeyboardAvailable);
}
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
@@ -6141,7 +6156,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (inputMethodControlTarget != null) {
pw.print(" inputMethodControlTarget in display# "); pw.print(displayId);
- pw.print(' '); pw.println(inputMethodControlTarget.getWindow());
+ pw.print(' '); pw.println(inputMethodControlTarget);
}
});
pw.print(" mInTouchMode="); pw.println(mInTouchMode);
@@ -6193,7 +6208,6 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mRecentsAnimationController="); pw.println(mRecentsAnimationController);
mRecentsAnimationController.dump(pw, " ");
}
- PolicyControl.dump(" ", pw);
}
}
@@ -7398,6 +7412,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
+ public void moveWindowTokenToDisplay(IBinder binder, int displayId) {
+ WindowManagerService.this.moveWindowTokenToDisplay(binder, displayId);
+ }
+
// TODO(multi-display): currently only used by PWM to notify keyguard transitions as well
// forwarding it to SystemUI for synchronizing status and navigation bar animations.
@Override
@@ -7899,7 +7918,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void syncInputTransactions() {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
waitForAnimationsToComplete();
@@ -8026,8 +8045,7 @@ public class WindowManagerService extends IWindowManager.Stub
updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
name, applicationHandle, flags, privateFlags, type, null /* region */);
- clientChannel.transferTo(outInputChannel);
- clientChannel.dispose();
+ clientChannel.copyTo(outInputChannel);
}
private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid,
@@ -8098,7 +8116,7 @@ public class WindowManagerService extends IWindowManager.Stub
public boolean isLayerTracing() {
mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
"isLayerTracing");
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
Parcel data = null;
Parcel reply = null;
@@ -8131,7 +8149,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void setLayerTracing(boolean enabled) {
mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
"setLayerTracing");
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
Parcel data = null;
try {
@@ -8158,7 +8176,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void setLayerTracingFlags(int flags) {
mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
"setLayerTracingFlags");
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
Parcel data = null;
try {
@@ -8300,4 +8318,14 @@ public class WindowManagerService extends IWindowManager.Stub
embeddedWindow.getName(), grantFocus);
}
}
+
+ @Override
+ public void holdLock(int durationMs) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INJECT_EVENTS, "holdLock requires shell identity");
+
+ synchronized (mGlobalLock) {
+ SystemClock.sleep(durationMs);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c7cad2f76486..5e07f5187c54 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -26,6 +26,8 @@ import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -40,6 +42,7 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
import android.window.WindowContainerToken;
@@ -88,28 +91,92 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final TaskOrganizerController mTaskOrganizerController;
final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
+ final TransitionController mTransitionController;
+
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
mTaskOrganizerController = new TaskOrganizerController(mService);
mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
+ mTransitionController = new TransitionController(atm);
+ }
+
+ TransitionController getTransitionController() {
+ return mTransitionController;
}
@Override
public void applyTransaction(WindowContainerTransaction t) {
- applySyncTransaction(t, null /*callback*/);
+ applyTransaction(t, null /*callback*/, null /*transition*/);
}
@Override
public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) {
+ return applyTransaction(t, callback, null /*transition*/);
+ }
+
+ @Override
+ public IBinder startTransition(int type, @Nullable IBinder transitionToken,
+ @Nullable WindowContainerTransaction t) {
+ enforceStackPermission("startTransition()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ Transition transition = Transition.fromBinder(transitionToken);
+ if (transition == null) {
+ if (type < 0) {
+ throw new IllegalArgumentException("Can't create transition with no type");
+ }
+ transition = mTransitionController.createTransition(type);
+ }
+ transition.start();
+ if (t == null) {
+ t = new WindowContainerTransaction();
+ }
+ applyTransaction(t, null /*callback*/, transition);
+ return transition;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
+ public int finishTransition(@NonNull IBinder transitionToken,
+ @Nullable WindowContainerTransaction t,
+ @Nullable IWindowContainerTransactionCallback callback) {
+ enforceStackPermission("finishTransition()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ int syncId = -1;
+ if (t != null) {
+ syncId = applyTransaction(t, callback, null /*transition*/);
+ }
+ getTransitionController().finishTransition(transitionToken);
+ return syncId;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * @param callback If non-null, this will be a sync-transaction.
+ * @param transition A transition to collect changes into.
+ * @return a BLAST sync-id if this is a non-transition, sync transaction.
+ */
+ private int applyTransaction(@NonNull WindowContainerTransaction t,
+ @Nullable IWindowContainerTransactionCallback callback,
+ @Nullable Transition transition) {
enforceStackPermission("applySyncTransaction()");
int syncId = -1;
if (t == null) {
throw new IllegalArgumentException(
"Null transaction passed to applySyncTransaction");
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
int effects = 0;
@@ -140,7 +207,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
entries.next();
final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
- if (!wc.isAttached()) {
+ if (wc == null || !wc.isAttached()) {
Slog.e(TAG, "Attempt to operate on detached container: " + wc);
continue;
}
@@ -152,6 +219,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
int containerEffect = applyWindowContainerChange(wc, entry.getValue());
+ if (transition != null) transition.collect(wc);
effects |= containerEffect;
// Lifecycle changes will trigger ensureConfig for everything.
@@ -173,6 +241,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
addToSyncSet(syncId, wc);
}
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+ if (transition != null) {
+ transition.collect(wc);
+ if (hop.isReparent() && hop.getNewParent() != null) {
+ transition.collect(WindowContainer.fromBinder(hop.getNewParent()));
+ }
+ }
}
// Queue-up bounds-change transactions for tasks which are now organized. Do
// this after hierarchy ops so we have the final organized state.
@@ -306,11 +380,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
- private int applyDisplayAreaChanges(WindowContainer container,
+ private int applyDisplayAreaChanges(DisplayArea displayArea,
WindowContainerTransaction.Change c) {
final int[] effects = new int[1];
- container.forAllTasks(task -> {
+ if ((c.getChangeMask()
+ & WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
+ if (displayArea.setIgnoreOrientationRequest(c.getIgnoreOrientationRequest())) {
+ effects[0] |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+
+ displayArea.forAllTasks(task -> {
Task tr = (Task) task;
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
@@ -389,7 +470,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
int effects = applyChanges(wc, c);
if (wc instanceof DisplayArea) {
- effects |= applyDisplayAreaChanges(wc, c);
+ effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
} else if (wc instanceof Task) {
effects |= applyTaskChanges(wc.asTask(), c);
}
@@ -497,6 +578,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return true;
}
+ @Override
+ public void registerTransitionPlayer(ITransitionPlayer player) {
+ enforceStackPermission("registerTransitionPlayer()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ mTransitionController.registerTransitionPlayer(player);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private void enforceStackPermission(String func) {
mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8bf0820c7dad..e8a7a9c1df92 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -41,7 +41,6 @@ import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.Task.ActivityState.STARTED;
import static com.android.server.wm.Task.ActivityState.STOPPING;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -179,10 +178,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
private volatile boolean mHasImeService;
+ /** Whether {@link #mActivities} is not empty. */
+ private volatile boolean mHasActivities;
/** All activities running in the process (exclude destroying). */
private final ArrayList<ActivityRecord> mActivities = new ArrayList<>();
/** The activities will be removed but still belong to this process. */
private ArrayList<ActivityRecord> mInactiveActivities;
+ /** Whether {@link #mRecentTasks} is not empty. */
+ private volatile boolean mHasRecentTasks;
// any tasks this process had run root activities in
private final ArrayList<Task> mRecentTasks = new ArrayList<>();
// The most recent top-most activity that was resumed in the process for pre-Q app.
@@ -195,13 +198,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Last configuration that was reported to the process.
private final Configuration mLastReportedConfiguration = new Configuration();
- // Configuration that is waiting to be dispatched to the process.
- private Configuration mPendingConfiguration;
+ /** Whether the process configuration is waiting to be dispatched to the process. */
+ private boolean mHasPendingConfigurationChange;
// Registered display id as a listener to override config change
private int mDisplayId;
private ActivityRecord mConfigActivityRecord;
// Whether the activity config override is allowed for this process.
private volatile boolean mIsActivityConfigOverrideAllowed = true;
+ /** Non-zero to pause dispatching process configuration change. */
+ private int mPauseConfigurationDispatchCount;
+
/**
* Activities that hosts some UI drawn by the current process. The activities live
* in another process. This is used to check if the process is currently showing anything
@@ -217,10 +223,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private boolean mRunningRemoteAnimation;
@Nullable
- private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
+ private final BackgroundActivityStartCallback mBackgroundActivityStartCallback;
+
+ /** The state for oom-adjustment calculation. */
+ private final OomScoreReferenceState mOomRefState;
public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info,
- String name, int uid, int userId, Object owner, WindowProcessListener listener) {
+ String name, int uid, int userId, Object owner,
+ @NonNull WindowProcessListener listener) {
mInfo = info;
mName = name;
mUid = uid;
@@ -230,6 +240,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mAtm = atm;
mDisplayId = INVALID_DISPLAY;
mBackgroundActivityStartCallback = mAtm.getBackgroundActivityStartCallback();
+ mOomRefState = new OomScoreReferenceState(this);
boolean isSysUiPackage = info.packageName.equals(
mAtm.getSysUiServiceComponentLocked().getPackageName());
@@ -380,7 +391,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void postPendingUiCleanMsg(boolean pendingUiClean) {
- if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
final Message m = PooledLambda.obtainMessage(
WindowProcessListener::setPendingUiClean, mListener, pendingUiClean);
@@ -465,8 +475,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
* Allows background activity starts using token {@code entity}. Optionally, you can provide
* {@code originatingToken} if you have one such originating token, this is useful for tracing
* back the grant in the case of the notification token.
+ *
+ * If {@code entity} is already added, this method will update its {@code originatingToken}.
*/
- public void addAllowBackgroundActivityStartsToken(Binder entity,
+ public void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
@Nullable IBinder originatingToken) {
synchronized (mAtm.mGlobalLock) {
mBackgroundActivityStartTokens.put(entity, originatingToken);
@@ -475,7 +487,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
/**
* Removes token {@code entity} that allowed background activity starts added via {@link
- * #addAllowBackgroundActivityStartsToken(Binder, IBinder)}.
+ * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}.
*/
public void removeAllowBackgroundActivityStartsToken(Binder entity) {
synchronized (mAtm.mGlobalLock) {
@@ -485,7 +497,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
/**
* Returns true if background activity starts are allowed by any token added via {@link
- * #addAllowBackgroundActivityStartsToken(Binder, IBinder)}.
+ * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}.
*/
public boolean areBackgroundActivityStartsAllowedByToken() {
synchronized (mAtm.mGlobalLock) {
@@ -543,8 +555,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return true;
}
// allow if the flag was explicitly set
- if (!mBackgroundActivityStartTokens.isEmpty()) {
- onBackgroundStartAllowedByToken();
+ if (isBackgroundStartAllowedByToken()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "[WindowProcessController(" + mPid
+ ")] Activity start allowed: process allowed by token");
@@ -554,18 +565,29 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return false;
}
- private void onBackgroundStartAllowedByToken() {
+ /**
+ * If there are no tokens, we don't allow *by token*. If there are tokens, we need to check if
+ * the callback handles all the tokens, if so we ask the callback if the activity should be
+ * started, otherwise we allow.
+ */
+ private boolean isBackgroundStartAllowedByToken() {
+ if (mBackgroundActivityStartTokens.isEmpty()) {
+ return false;
+ }
if (mBackgroundActivityStartCallback == null) {
- return;
+ // We have tokens but no callback to decide => allow
+ return true;
}
IBinder callbackToken = mBackgroundActivityStartCallback.getToken();
for (IBinder token : mBackgroundActivityStartTokens.values()) {
if (token != callbackToken) {
- return;
+ // The callback doesn't handle all the tokens => allow
+ return true;
}
}
- mAtm.mH.post(() ->
- mBackgroundActivityStartCallback.onExclusiveTokenActivityStart(mInfo.packageName));
+ // The callback handles all the tokens => callback decides
+ return mBackgroundActivityStartCallback.isActivityStartAllowed(mInfo.uid,
+ mInfo.packageName);
}
private boolean isBoundByForegroundUid() {
@@ -638,6 +660,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return;
}
mActivities.add(r);
+ mHasActivities = true;
if (mInactiveActivities != null) {
mInactiveActivities.remove(r);
}
@@ -666,40 +689,30 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mInactiveActivities.remove(r);
}
mActivities.remove(r);
+ mHasActivities = !mActivities.isEmpty();
updateActivityConfigurationListener();
}
void clearActivities() {
mInactiveActivities = null;
mActivities.clear();
+ mHasActivities = false;
updateActivityConfigurationListener();
}
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public boolean hasActivities() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- return !mActivities.isEmpty();
- }
+ return mHasActivities;
}
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public boolean hasVisibleActivities() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
- if (r.mVisibleRequested) {
- return true;
- }
- }
- }
- return false;
+ return (mOomRefState.mActivityStateFlags & OomScoreReferenceState.FLAG_IS_VISIBLE) != 0;
}
@HotPath(caller = HotPath.LRU_UPDATE)
public boolean hasActivitiesOrRecentTasks() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- return !mActivities.isEmpty() || !mRecentTasks.isEmpty();
- }
+ return mHasActivities || mHasRecentTasks;
}
private boolean hasActivityInVisibleTask() {
@@ -768,7 +781,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
final Task stack = mPreQTopResumedActivity.getRootTask();
if (stack != null) {
stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
- activity);
+ activity, "top-resumed-changed");
}
}
mPreQTopResumedActivity = activity;
@@ -987,6 +1000,34 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mHostActivities.remove(r);
}
+ private static class OomScoreReferenceState extends RootWindowContainer.LockedScheduler {
+ private static final int FLAG_IS_VISIBLE = 0x10000000;
+ private static final int FLAG_IS_PAUSING = 0x20000000;
+ private static final int FLAG_IS_STOPPING = 0x40000000;
+ private static final int FLAG_IS_STOPPING_FINISHING = 0x80000000;
+ /** @see Task#mLayerRank */
+ private static final int MASK_MIN_TASK_LAYER = 0x0000ffff;
+
+ private final WindowProcessController mOwner;
+ boolean mChanged;
+
+ /**
+ * The higher 16 bits are the activity states, and the lower 16 bits are the task layer
+ * rank. This field is written by window manager and read by activity manager.
+ */
+ volatile int mActivityStateFlags = MASK_MIN_TASK_LAYER;
+
+ OomScoreReferenceState(WindowProcessController owner) {
+ super(owner.mAtm);
+ mOwner = owner;
+ }
+
+ @Override
+ public void execute() {
+ mOwner.computeOomScoreReferenceStateIfNeeded();
+ }
+ }
+
public interface ComputeOomAdjCallback {
void onVisibleActivity();
void onPausedActivity();
@@ -994,64 +1035,102 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
void onOtherActivity();
}
+ /**
+ * Returns the minimum task layer rank. It should only be called if {@link #hasActivities}
+ * returns {@code true}.
+ */
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
- public int computeOomAdjFromActivities(int minTaskLayer, ComputeOomAdjCallback callback) {
+ public int computeOomAdjFromActivities(ComputeOomAdjCallback callback) {
+ final int flags = mOomRefState.mActivityStateFlags;
+ if ((flags & OomScoreReferenceState.FLAG_IS_VISIBLE) != 0) {
+ callback.onVisibleActivity();
+ } else if ((flags & OomScoreReferenceState.FLAG_IS_PAUSING) != 0) {
+ callback.onPausedActivity();
+ } else if ((flags & OomScoreReferenceState.FLAG_IS_STOPPING) != 0) {
+ callback.onStoppingActivity(
+ (flags & OomScoreReferenceState.FLAG_IS_STOPPING_FINISHING) != 0);
+ } else {
+ callback.onOtherActivity();
+ }
+ return flags & OomScoreReferenceState.MASK_MIN_TASK_LAYER;
+ }
+
+ void computeOomScoreReferenceStateIfNeeded() {
+ if (!mOomRefState.mChanged) {
+ return;
+ }
+ mOomRefState.mChanged = false;
+
// Since there could be more than one activities in a process record, we don't need to
// compute the OomAdj with each of them, just need to find out the activity with the
// "best" state, the order would be visible, pausing, stopping...
Task.ActivityState best = DESTROYED;
boolean finishing = true;
boolean visible = false;
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- final int activitiesSize = mActivities.size();
- for (int j = 0; j < activitiesSize; j++) {
- final ActivityRecord r = mActivities.get(j);
- if (r.app != this) {
- Log.e(TAG, "Found activity " + r + " in proc activity list using " + r.app
- + " instead of expected " + this);
- if (r.app == null || (r.app.mUid == mUid)) {
- // Only fix things up when they look sane
- r.setProcess(this);
- } else {
- continue;
- }
- }
- if (r.mVisibleRequested) {
- final Task task = r.getTask();
- if (task != null && minTaskLayer > 0) {
- final int layer = task.mLayerRank;
- if (layer >= 0 && minTaskLayer > layer) {
- minTaskLayer = layer;
- }
- }
- visible = true;
- // continue the loop, in case there are multiple visible activities in
- // this process, we'd find out the one with the minimal layer, thus it'll
- // get a higher adj score.
+ int minTaskLayer = Integer.MAX_VALUE;
+ for (int i = mActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mActivities.get(i);
+ if (r.app != this) {
+ Slog.e(TAG, "Found activity " + r + " in proc activity list using " + r.app
+ + " instead of expected " + this);
+ if (r.app == null || (r.app.mUid == mUid)) {
+ // Only fix things up when they look valid.
+ r.setProcess(this);
} else {
- if (best != PAUSING && best != PAUSED) {
- if (r.isState(PAUSING, PAUSED)) {
- best = PAUSING;
- } else if (r.isState(STOPPING)) {
- best = STOPPING;
- // Not "finishing" if any of activity isn't finishing.
- finishing &= r.finishing;
- }
+ continue;
+ }
+ }
+ if (r.mVisibleRequested) {
+ final Task task = r.getTask();
+ if (task != null && minTaskLayer > 0) {
+ final int layer = task.mLayerRank;
+ if (layer >= 0 && minTaskLayer > layer) {
+ minTaskLayer = layer;
}
}
+ visible = true;
+ // continue the loop, in case there are multiple visible activities in
+ // this process, we'd find out the one with the minimal layer, thus it'll
+ // get a higher adj score.
+ } else if (best != PAUSING && best != PAUSED) {
+ if (r.isState(PAUSING, PAUSED)) {
+ best = PAUSING;
+ } else if (r.isState(STOPPING)) {
+ best = STOPPING;
+ // Not "finishing" if any of activity isn't finishing.
+ finishing &= r.finishing;
+ }
}
+
+ int stateFlags = minTaskLayer & OomScoreReferenceState.MASK_MIN_TASK_LAYER;
+ if (visible) {
+ stateFlags |= OomScoreReferenceState.FLAG_IS_VISIBLE;
+ } else if (best == PAUSING) {
+ stateFlags |= OomScoreReferenceState.FLAG_IS_PAUSING;
+ } else if (best == STOPPING) {
+ stateFlags |= OomScoreReferenceState.FLAG_IS_STOPPING;
+ if (finishing) {
+ stateFlags |= OomScoreReferenceState.FLAG_IS_STOPPING_FINISHING;
+ }
+ }
+ mOomRefState.mActivityStateFlags = stateFlags;
}
- if (visible) {
- callback.onVisibleActivity();
- } else if (best == PAUSING) {
- callback.onPausedActivity();
- } else if (best == STOPPING) {
- callback.onStoppingActivity(finishing);
- } else {
- callback.onOtherActivity();
+ }
+
+ void invalidateOomScoreReferenceState(boolean computeNow) {
+ mOomRefState.mChanged = true;
+ if (computeNow) {
+ computeOomScoreReferenceStateIfNeeded();
+ return;
}
+ mOomRefState.scheduleIfNeeded();
+ }
- return minTaskLayer;
+ /** Called when the process has some oom related changes and it is going to update oom-adj. */
+ private void prepareOomAdjustment() {
+ mAtm.mRootWindowContainer.rankTaskLayersIfNeeded();
+ // The task layer may not change but the activity state in the same task may change.
+ computeOomScoreReferenceStateIfNeeded();
}
public int computeRelaunchReason() {
@@ -1081,7 +1160,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void clearProfilerIfNeeded() {
- if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
WindowProcessListener::clearProfilerIfNeeded, mListener));
@@ -1089,10 +1167,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
void updateProcessInfo(boolean updateServiceConnectionActivities, boolean activityChange,
boolean updateOomAdj, boolean addPendingTopUid) {
- if (mListener == null) return;
if (addPendingTopUid) {
mAtm.mAmInternal.addPendingTopUid(mUid, mPid);
}
+ if (updateOomAdj) {
+ prepareOomAdjustment();
+ }
// Posting on handler so WM lock isn't held when we call into AM.
final Message m = PooledLambda.obtainMessage(WindowProcessListener::updateProcessInfo,
mListener, updateServiceConnectionActivities, activityChange, updateOomAdj);
@@ -1100,14 +1180,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void updateServiceConnectionActivities() {
- if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
WindowProcessListener::updateServiceConnectionActivities, mListener));
}
void setPendingUiCleanAndForceProcessStateUpTo(int newState) {
- if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
final Message m = PooledLambda.obtainMessage(
WindowProcessListener::setPendingUiCleanAndForceProcessStateUpTo,
@@ -1116,7 +1194,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
boolean isRemoved() {
- return mListener == null ? false : mListener.isRemoved();
+ return mListener.isRemoved();
}
private boolean shouldSetProfileProc() {
@@ -1141,7 +1219,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void onStartActivity(int topProcessState, ActivityInfo info) {
- if (mListener == null) return;
String packageName = null;
if ((info.flags & ActivityInfo.FLAG_MULTIPROCESS) == 0
|| !"android".equals(info.packageName)) {
@@ -1154,6 +1231,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
if (topProcessState == ActivityManager.PROCESS_STATE_TOP) {
mAtm.mAmInternal.addPendingTopUid(mUid, mPid);
}
+ prepareOomAdjustment();
// Posting the message at the front of queue so WM lock isn't held when we call into AM,
// and the process state of starting activity can be updated quicker which will give it a
// higher scheduling group.
@@ -1164,7 +1242,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void appDied(String reason) {
- if (mListener == null) return;
// Posting on handler so WM lock isn't held when we call into AM.
final Message m = PooledLambda.obtainMessage(
WindowProcessListener::appDied, mListener, reason);
@@ -1242,8 +1319,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
onMergedOverrideConfigurationChanged(Configuration.EMPTY);
}
- private void registerActivityConfigurationListener(ActivityRecord activityRecord) {
- if (activityRecord == null || activityRecord.containsListener(this)) {
+ void registerActivityConfigurationListener(ActivityRecord activityRecord) {
+ if (activityRecord == null || activityRecord.containsListener(this)
+ // Check for the caller from outside of this class.
+ || !mIsActivityConfigOverrideAllowed) {
return;
}
// A process can only register to one activityRecord to listen to the override configuration
@@ -1295,25 +1374,25 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
@Override
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
- super.onRequestedOverrideConfigurationChanged(
- sanitizeProcessConfiguration(overrideConfiguration));
+ super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
}
@Override
public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
- super.onRequestedOverrideConfigurationChanged(
- sanitizeProcessConfiguration(mergedOverrideConfig));
+ super.onRequestedOverrideConfigurationChanged(mergedOverrideConfig);
}
- private static Configuration sanitizeProcessConfiguration(Configuration config) {
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfig) {
+ super.resolveOverrideConfiguration(newParentConfig);
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
// Make sure that we don't accidentally override the activity type.
- if (config.windowConfiguration.getActivityType() != ACTIVITY_TYPE_UNDEFINED) {
- final Configuration sanitizedConfig = new Configuration(config);
- sanitizedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
- return sanitizedConfig;
- }
-
- return config;
+ resolvedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ // Activity has an independent ActivityRecord#mConfigurationSeq. If this process registers
+ // activity configuration, its config seq shouldn't go backwards by activity configuration.
+ // Otherwise if other places send wpc.getConfiguration() to client, the configuration may
+ // be ignored due to the seq is older.
+ resolvedConfig.seq = newParentConfig.seq;
}
private void updateConfiguration() {
@@ -1328,21 +1407,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return;
}
- if (mListener.isCached()) {
- // This process is in a cached state. We will delay delivering the config change to the
- // process until the process is no longer cached.
- if (mPendingConfiguration == null) {
- mPendingConfiguration = new Configuration(config);
- } else {
- mPendingConfiguration.setTo(config);
- }
+ if (mPauseConfigurationDispatchCount > 0) {
+ mHasPendingConfigurationChange = true;
return;
}
-
- dispatchConfigurationChange(config);
- }
-
- private void dispatchConfigurationChange(Configuration config) {
+ mHasPendingConfigurationChange = false;
if (mThread == null) {
if (Build.IS_DEBUGGABLE && mHasImeService) {
// TODO (b/135719017): Temporary log for debugging IME service.
@@ -1368,7 +1437,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
}
- private void setLastReportedConfiguration(Configuration config) {
+ void setLastReportedConfiguration(Configuration config) {
mLastReportedConfiguration.setTo(config);
}
@@ -1376,24 +1445,48 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mLastReportedConfiguration;
}
+ void pauseConfigurationDispatch() {
+ mPauseConfigurationDispatchCount++;
+ }
+
+ void resumeConfigurationDispatch() {
+ mPauseConfigurationDispatchCount--;
+ }
+
+ /**
+ * This is called for sending {@link android.app.servertransaction.LaunchActivityItem}.
+ * The caller must call {@link #setLastReportedConfiguration} if the delivered configuration
+ * is newer.
+ */
+ Configuration prepareConfigurationForLaunchingActivity() {
+ final Configuration config = getConfiguration();
+ if (mHasPendingConfigurationChange) {
+ mHasPendingConfigurationChange = false;
+ // The global configuration may not change, so the client process may have the same
+ // config seq. This increment ensures that the client won't ignore the configuration.
+ config.seq = mAtm.increaseConfigurationSeqLocked();
+ }
+ return config;
+ }
+
/** Returns the total time (in milliseconds) spent executing in both user and system code. */
public long getCpuTime() {
- return (mListener != null) ? mListener.getCpuTime() : 0;
+ return mListener.getCpuTime();
}
void addRecentTask(Task task) {
mRecentTasks.add(task);
+ mHasRecentTasks = true;
}
void removeRecentTask(Task task) {
mRecentTasks.remove(task);
+ mHasRecentTasks = !mRecentTasks.isEmpty();
}
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public boolean hasRecentTasks() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- return !mRecentTasks.isEmpty();
- }
+ return mHasRecentTasks;
}
void clearRecentTasks() {
@@ -1401,6 +1494,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mRecentTasks.get(i).clearRootProcess();
}
mRecentTasks.clear();
+ mHasRecentTasks = false;
}
public void appEarlyNotResponding(String annotation, Runnable killAppCallback) {
@@ -1459,24 +1553,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
/**
- * Called to notify WindowProcessController of a change in the process's cached state.
- *
- * @param isCached whether or not the process is cached.
- */
- @HotPath(caller = HotPath.OOM_ADJUSTMENT)
- public void onProcCachedStateChanged(boolean isCached) {
- if (!isCached) {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- if (mPendingConfiguration != null) {
- final Configuration config = mPendingConfiguration;
- mPendingConfiguration = null;
- dispatchConfigurationChange(config);
- }
- }
- }
- }
-
- /**
* Called to notify {@link WindowProcessController} of a started service.
*
* @param serviceInfo information describing the started service.
@@ -1507,23 +1583,28 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void onTopProcChanged() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- mAtm.mVrController.onTopProcChangedLocked(this);
+ if (mAtm.mVrController.isInterestingToSchedGroup()) {
+ mAtm.mH.post(() -> {
+ synchronized (mAtm.mGlobalLock) {
+ mAtm.mVrController.onTopProcChangedLocked(this);
+ }
+ });
}
}
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public boolean isHomeProcess() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- return this == mAtm.mHomeProcess;
- }
+ return this == mAtm.mHomeProcess;
}
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public boolean isPreviousProcess() {
- synchronized (mAtm.mGlobalLockWithoutBoost) {
- return this == mAtm.mPreviousProcess;
- }
+ return this == mAtm.mPreviousProcess;
+ }
+
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
+ public boolean isHeavyWeightProcess() {
+ return this == mAtm.mHeavyWeightProcess;
}
void setRunningRecentsAnimation(boolean running) {
@@ -1575,10 +1656,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
pw.print(prefix); pw.print("mVrThreadTid="); pw.println(mVrThreadTid);
}
if (mBackgroundActivityStartTokens.size() > 0) {
- pw.print(prefix); pw.println("Background activity start tokens:");
+ pw.print(prefix);
+ pw.println("Background activity start tokens (token: originating token):");
for (int i = 0; i < mBackgroundActivityStartTokens.size(); i++) {
pw.print(prefix); pw.print(" - ");
- pw.println(mBackgroundActivityStartTokens.keyAt(i));
+ pw.print(mBackgroundActivityStartTokens.keyAt(i));
+ pw.print(": ");
+ pw.println(mBackgroundActivityStartTokens.valueAt(i));
+
}
}
}
@@ -1588,8 +1673,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
- if (mListener != null) {
- mListener.dumpDebug(proto, fieldId);
- }
+ mListener.dumpDebug(proto, fieldId);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f2f3cefae8a5..3b79241a3c3e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
+import static android.hardware.input.InputManager.BLOCK_UNTRUSTED_TOUCHES;
import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -182,6 +183,7 @@ import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
+import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -198,6 +200,7 @@ import android.os.PowerManager.WakeReason;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.TouchOcclusionMode;
import android.os.Trace;
import android.os.WorkSource;
import android.provider.Settings;
@@ -302,7 +305,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final boolean mIsImWindow;
final boolean mIsWallpaper;
private final boolean mIsFloatingLayer;
- int mSeq;
int mViewVisibility;
int mSystemUiVisibility;
@@ -435,7 +437,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/**
* Usually empty. Set to the task's tempInsetFrame. See
- *{@link android.app.IActivityTaskManager#resizeDockedStack}.
+ *{@link android.app.IActivityTaskManager#resizePrimarySplitScreen}.
*/
private final Rect mInsetFrame = new Rect();
@@ -556,7 +558,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
- private InputChannel mClientChannel;
// Used to improve performance of toString()
private String mStringNameCache;
@@ -835,10 +836,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
- int viewVisibility, int ownerId, int showUserId,
- boolean ownerCanAddInternalSystemWindow) {
- this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId,
+ WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
+ int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
+ this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
@Override
public void wakeUp(long time, @WakeReason int reason, String details) {
@@ -853,9 +853,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
- WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
- int viewVisibility, int ownerId, int showUserId,
- boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
+ WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
+ int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
+ PowerManagerWrapper powerManagerWrapper) {
super(service);
mSession = s;
mClient = c;
@@ -872,7 +872,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
DeathRecipient deathRecipient = new DeathRecipient();
- mSeq = seq;
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
if (DEBUG) {
@@ -962,6 +961,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
: service.mAtmService.getProcessController(s.mPid, s.mUid);
}
+ int getTouchOcclusionMode() {
+ if (!CompatChanges.isChangeEnabled(BLOCK_UNTRUSTED_TOUCHES, mOwnerUid)) {
+ return TouchOcclusionMode.ALLOW;
+ }
+ if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
+ return TouchOcclusionMode.USE_OPACITY;
+ }
+ return TouchOcclusionMode.BLOCK_UNTRUSTED;
+ }
+
void attach() {
if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
mSession.windowAddedLocked(mAttrs.packageName);
@@ -1413,15 +1422,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
if (didFrameInsetsChange
- || winAnimator.mSurfaceResized
|| configChanged
|| dragResizingChanged
|| mReportOrientationChanged
|| shouldSendRedrawForSync()) {
ProtoLog.v(WM_DEBUG_RESIZE,
- "Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b "
+ "Resize reasons for w=%s: %s configChanged=%b "
+ "dragResizingChanged=%b reportOrientationChanged=%b",
- this, mWindowFrames.getInsetsChangedInfo(), winAnimator.mSurfaceResized,
+ this, mWindowFrames.getInsetsChangedInfo(),
configChanged, dragResizingChanged, mReportOrientationChanged);
// If it's a dead window left on screen, and the configuration changed, there is nothing
@@ -1531,6 +1539,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return getDisplayContent().getDisplayInfo();
}
+ /**
+ * Returns the insets state for the client. Its sources may be the copies with visibility
+ * modification according to the state of transient bars.
+ */
InsetsState getInsetsState() {
return getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
}
@@ -1684,6 +1696,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| mControllableInsetProvider.isClientVisible());
}
+ @Override
+ boolean isVisibleRequested() {
+ return isVisible();
+ }
+
/**
* Ensures that all the policy visibility bits are set.
* @return {@code true} if all flags about visiblity are set
@@ -1772,7 +1789,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
- return ((!isParentWindowHidden() && atoken.mVisibleRequested)
+ return ((!isParentWindowHidden() && atoken.isVisible())
|| isAnimating(TRANSITION | PARENTS));
}
return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS);
@@ -2155,16 +2172,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- @Override
- void forceWindowsScaleableInTransaction(boolean force) {
- if (mWinAnimator != null && mWinAnimator.hasSurface()) {
- mWinAnimator.mSurfaceController.forceScaleableInTransaction(force);
- }
-
- super.forceWindowsScaleableInTransaction(force);
- }
-
- @Override
+ @Override
void removeImmediately() {
super.removeImmediately();
@@ -2474,20 +2482,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
throw new IllegalStateException("Window already has an input channel.");
}
String name = getName();
- InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
- mInputChannel = inputChannels[0];
- mClientChannel = inputChannels[1];
- mWmService.mInputManager.registerInputChannel(mInputChannel);
+ mInputChannel = mWmService.mInputManager.createInputChannel(name);
mInputWindowHandle.token = mInputChannel.getToken();
if (outInputChannel != null) {
- mClientChannel.transferTo(outInputChannel);
- mClientChannel.dispose();
- mClientChannel = null;
+ mInputChannel.copyTo(outInputChannel);
} else {
// If the window died visible, we setup a fake input channel, so that taps
// can still detected by input monitor channel, and we can relaunch the app.
// Create fake event receiver that simply reports all events as handled.
- mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
+ mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
}
mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}
@@ -2500,15 +2503,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// unregister server channel first otherwise it complains about broken channel
if (mInputChannel != null) {
- mWmService.mInputManager.unregisterInputChannel(mInputChannel.getToken());
+ mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
mInputChannel.dispose();
mInputChannel = null;
}
- if (mClientChannel != null) {
- mClientChannel.dispose();
- mClientChannel = null;
- }
mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
mWmService.mInputToWindowMap.remove(mInputWindowHandle.token);
mInputWindowHandle.token = null;
@@ -3642,7 +3641,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// that may cause WINDOW_FREEZE_TIMEOUT because resizing the client keeps failing.
mReportOrientationChanged = false;
mDragResizingChangeReported = true;
- mWinAnimator.mSurfaceResized = false;
mWindowFrames.resetInsetsChanged();
final MergedConfiguration mergedConfiguration = mLastReportedConfiguration;
@@ -4037,7 +4035,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "mViewVisibility=0x" + Integer.toHexString(mViewVisibility)
+ " mHaveFrame=" + mHaveFrame
+ " mObscured=" + mObscured);
- pw.println(prefix + "mSeq=" + mSeq
+ pw.println(prefix
+ " mSystemUiVisibility=0x" + Integer.toHexString(mSystemUiVisibility));
}
if (!isVisibleByPolicy() || !mLegacyPolicyVisibilityAfterAnim || !mAppOpVisibility
@@ -4961,93 +4959,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * Calculate the window crop according to system decor policy. In general this is
- * the system decor rect (see #calculateSystemDecorRect), but we also have some
- * special cases. This rectangle is in screen space.
- */
- void calculatePolicyCrop(Rect policyCrop) {
- final DisplayContent displayContent = getDisplayContent();
-
- if (!displayContent.isDefaultDisplay && !displayContent.supportsSystemDecorations()) {
- // On a different display there is no system decor. Crop the window
- // by the screen boundaries.
- final DisplayInfo displayInfo = getDisplayInfo();
- policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(),
- mWindowFrames.mCompatFrame.height());
- policyCrop.intersect(-mWindowFrames.mCompatFrame.left, -mWindowFrames.mCompatFrame.top,
- displayInfo.logicalWidth - mWindowFrames.mCompatFrame.left,
- displayInfo.logicalHeight - mWindowFrames.mCompatFrame.top);
- } else if (skipDecorCrop()) {
- // Windows without policy decor aren't cropped.
- policyCrop.set(0, 0, mWindowFrames.mCompatFrame.width(),
- mWindowFrames.mCompatFrame.height());
- } else {
- // Crop to the system decor specified by policy.
- calculateSystemDecorRect(policyCrop);
- }
- }
-
- /**
- * The system decor rect is the region of the window which is not covered
- * by system decorations.
- */
- private void calculateSystemDecorRect(Rect systemDecorRect) {
- final Rect decorRect = mWindowFrames.mDecorFrame;
- final int width = mWindowFrames.mFrame.width();
- final int height = mWindowFrames.mFrame.height();
-
- final int left = mWindowFrames.mFrame.left;
- final int top = mWindowFrames.mFrame.top;
-
- // Initialize the decor rect to the entire frame.
- if (isDockedResizing()) {
- // If we are resizing with the divider, the task bounds might be smaller than the
- // stack bounds. The system decor is used to clip to the task bounds, which we don't
- // want in this case in order to avoid holes.
- //
- // We take care to not shrink the width, for surfaces which are larger than
- // the display region. Of course this area will not eventually be visible
- // but if we truncate the width now, we will calculate incorrectly
- // when adjusting to the stack bounds.
- final DisplayInfo displayInfo = getDisplayContent().getDisplayInfo();
- systemDecorRect.set(0, 0,
- Math.max(width, displayInfo.logicalWidth),
- Math.max(height, displayInfo.logicalHeight));
- } else {
- systemDecorRect.set(0, 0, width, height);
- }
-
- // If a freeform window is animating from a position where it would be cutoff, it would be
- // cutoff during the animation. We don't want that, so for the duration of the animation
- // we ignore the decor cropping and depend on layering to position windows correctly.
-
- // We also ignore cropping when the window is currently being drag resized in split screen
- // to prevent issues with the crop for screenshot.
- final boolean cropToDecor =
- !(inFreeformWindowingMode() && isAnimatingLw()) && !isDockedResizing();
- if (cropToDecor) {
- // Intersect with the decor rect, offsetted by window position.
- systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
- decorRect.right - left, decorRect.bottom - top);
- }
-
- // If size compatibility is being applied to the window, the
- // surface is scaled relative to the screen. Also apply this
- // scaling to the crop rect. We aren't using the standard rect
- // scale function because we want to round things to make the crop
- // always round to a larger rect to ensure we don't crop too
- // much and hide part of the window that should be seen.
- if (mInvGlobalScale != 1.0f && inSizeCompatMode()) {
- final float scale = mInvGlobalScale;
- systemDecorRect.left = (int) (systemDecorRect.left * scale - 0.5f);
- systemDecorRect.top = (int) (systemDecorRect.top * scale - 0.5f);
- systemDecorRect.right = (int) ((systemDecorRect.right + 1) * scale - 0.5f);
- systemDecorRect.bottom = (int) ((systemDecorRect.bottom + 1) * scale - 0.5f);
- }
-
- }
-
- /**
* Expand the given rectangle by this windows surface insets. This
* takes you from the 'window size' to the 'surface size'.
* The surface insets are positive in each direction, so we inset by
@@ -5104,9 +5015,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// on the new one. This prevents blinking when we change elevation of freeform and
// pinned windows.
if (!mWinAnimator.tryChangeFormatInPlaceLocked()) {
- mWinAnimator.preserveSurfaceLocked();
+ mWinAnimator.preserveSurfaceLocked(getPendingTransaction());
result |= RELAYOUT_RES_SURFACE_CHANGED
| RELAYOUT_RES_FIRST_TIME;
+ scheduleAnimation();
}
}
@@ -5122,9 +5034,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to preserve and destroy windows which are attached to another, they
// will keep their surface and its size may change over time.
if (mHasSurface && !isChildWindow()) {
- mWinAnimator.preserveSurfaceLocked();
+ mWinAnimator.preserveSurfaceLocked(getPendingTransaction());
result |= RELAYOUT_RES_SURFACE_CHANGED |
RELAYOUT_RES_FIRST_TIME;
+ scheduleAnimation();
}
}
final boolean freeformResizing = isDragResizing()
@@ -5415,7 +5328,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Send information to SufaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
- mWinAnimator.prepareSurfaceLocked(true);
+ mWinAnimator.prepareSurfaceLocked(SurfaceControl.getGlobalTransaction(), true);
notifyBlastSyncTransaction();
super.prepareSurfaces();
}
@@ -5899,7 +5812,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
mNotifyBlastOnSurfacePlacement = true;
- return mWinAnimator.finishDrawingLocked(null);
+ mWinAnimator.finishDrawingLocked(null);
+ // We always want to force a traversal after a finish draw for blast sync.
+ return true;
}
private void notifyBlastSyncTransaction() {
@@ -5910,6 +5825,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return;
}
+ final Task task = getTask();
+ if (task != null) {
+ final SurfaceControl.Transaction t = task.getMainWindowSizeChangeTransaction();
+ if (t != null) {
+ mBLASTSyncTransaction.merge(t);
+ }
+ task.setMainWindowSizeChangeTransaction(null);
+ }
+
// If localSyncId is >0 then we are syncing with children and will
// invoke transaction ready from our own #transactionReady callback
// we just need to signal our side of the sync (setReady). But if we
@@ -5948,4 +5872,37 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void requestRedrawForSync() {
mRedrawForSyncReported = false;
}
+
+ void calculateSurfaceBounds(WindowManager.LayoutParams attrs, Rect outSize) {
+ outSize.setEmpty();
+ if ((attrs.flags & FLAG_SCALED) != 0) {
+ // For a scaled surface, we always want the requested size.
+ outSize.right = mRequestedWidth;
+ outSize.bottom = mRequestedHeight;
+ } else {
+ // When we're doing a drag-resizing, request a surface that's fullscreen size,
+ // so that we don't need to reallocate during the process. This also prevents
+ // buffer drops due to size mismatch.
+ if (isDragResizing()) {
+ final DisplayInfo displayInfo = getDisplayInfo();
+ outSize.right = displayInfo.logicalWidth;
+ outSize.bottom = displayInfo.logicalHeight;
+ } else {
+ getCompatFrameSize(outSize);
+ }
+ }
+
+ // This doesn't necessarily mean that there is an error in the system. The sizes might be
+ // incorrect, because it is before the first layout or draw.
+ if (outSize.width() < 1) {
+ outSize.right = 1;
+ }
+ if (outSize.height() < 1) {
+ outSize.bottom = 1;
+ }
+
+ // Adjust for surface insets.
+ outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
+ -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5e814005a5e2..6349e6d36ae1 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -23,12 +23,10 @@ import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DRAW;
@@ -46,19 +44,16 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
-import static com.android.server.wm.WindowStateAnimatorProto.LAST_CLIP_RECT;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
@@ -121,12 +116,6 @@ class WindowStateAnimator {
boolean mAnimationIsEntrance;
- /**
- * Set when we have changed the size of the surface, to know that
- * we must tell them application to resize (and thus redraw itself).
- */
- boolean mSurfaceResized;
-
WindowSurfaceController mSurfaceController;
private WindowSurfaceController mPendingDestroySurface;
@@ -141,9 +130,6 @@ class WindowStateAnimator {
float mAlpha = 0;
float mLastAlpha = 0;
- Rect mTmpClipRect = new Rect();
- Rect mLastClipRect = new Rect();
- Rect mLastFinalClipRect = new Rect();
Rect mTmpStackBounds = new Rect();
private Rect mTmpAnimatingBounds = new Rect();
private Rect mTmpSourceBounds = new Rect();
@@ -358,7 +344,7 @@ class WindowStateAnimator {
return result;
}
- void preserveSurfaceLocked() {
+ void preserveSurfaceLocked(SurfaceControl.Transaction t) {
if (mDestroyPreservedSurfaceUponRedraw) {
// This could happen when switching the surface mode very fast. For example,
// we preserved a surface when dragResizing changed to true. Then before the
@@ -374,7 +360,7 @@ class WindowStateAnimator {
if (mSurfaceController != null && mPendingDestroySurface != null) {
mPostDrawTransaction.reparentChildren(
mSurfaceController.getClientViewRootSurface(),
- mPendingDestroySurface.mSurfaceControl).apply();
+ mPendingDestroySurface.getClientViewRootSurface()).apply();
}
destroySurfaceLocked();
mSurfaceDestroyDeferred = true;
@@ -385,7 +371,7 @@ class WindowStateAnimator {
// Our SurfaceControl is always at layer 0 within the parent Surface managed by
// window-state. We want this old Surface to stay on top of the new one
// until we do the swap, so we place it at a positive layer.
- mSurfaceController.mSurfaceControl.setLayer(PRESERVED_SURFACE_LAYER);
+ t.setLayer(mSurfaceController.getClientViewRootSurface(), PRESERVED_SURFACE_LAYER);
}
mDestroyPreservedSurfaceUponRedraw = true;
mSurfaceDestroyDeferred = true;
@@ -408,7 +394,7 @@ class WindowStateAnimator {
&& (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) {
mPostDrawTransaction.reparentChildren(
mPendingDestroySurface.getClientViewRootSurface(),
- mSurfaceController.mSurfaceControl).apply();
+ mSurfaceController.getClientViewRootSurface()).apply();
}
destroyDeferredSurfaceLocked();
@@ -441,10 +427,6 @@ class WindowStateAnimator {
return mSurfaceController;
}
- if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
- windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT;
- }
-
w.setHasSurface(false);
if (DEBUG_ANIM) {
@@ -462,7 +444,12 @@ class WindowStateAnimator {
flags |= SurfaceControl.SECURE;
}
- calculateSurfaceBounds(w, attrs, mTmpSize);
+ if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
+ flags |= SurfaceControl.SKIP_SCREENSHOT;
+ }
+
+ w.calculateSurfaceBounds(attrs, mTmpSize);
+
final int width = mTmpSize.width();
final int height = mTmpSize.height();
@@ -474,9 +461,6 @@ class WindowStateAnimator {
+ " format=" + attrs.format + " flags=" + flags);
}
- // We may abort, so initialize to defaults.
- mLastClipRect.set(0, 0, 0, 0);
-
// Set up surface control with initial size.
try {
@@ -538,40 +522,6 @@ class WindowStateAnimator {
return mSurfaceController;
}
- private void calculateSurfaceBounds(WindowState w, LayoutParams attrs, Rect outSize) {
- outSize.setEmpty();
- if ((attrs.flags & FLAG_SCALED) != 0) {
- // For a scaled surface, we always want the requested size.
- outSize.right = w.mRequestedWidth;
- outSize.bottom = w.mRequestedHeight;
- } else {
- // When we're doing a drag-resizing, request a surface that's fullscreen size,
- // so that we don't need to reallocate during the process. This also prevents
- // buffer drops due to size mismatch.
- if (w.isDragResizing()) {
- final DisplayInfo displayInfo = w.getDisplayInfo();
- outSize.right = displayInfo.logicalWidth;
- outSize.bottom = displayInfo.logicalHeight;
- } else {
- w.getCompatFrameSize(outSize);
- }
- }
-
- // Something is wrong and SurfaceFlinger will not like this, try to revert to reasonable
- // values. This doesn't necessarily mean that there is an error in the system. The sizes
- // might be incorrect, because it is before the first layout or draw.
- if (outSize.width() < 1) {
- outSize.right = 1;
- }
- if (outSize.height() < 1) {
- outSize.bottom = 1;
- }
-
- // Adjust for surface insets.
- outSize.inset(-attrs.surfaceInsets.left, -attrs.surfaceInsets.top,
- -attrs.surfaceInsets.right, -attrs.surfaceInsets.bottom);
- }
-
boolean hasSurface() {
return mSurfaceController != null && mSurfaceController.hasSurface();
}
@@ -680,78 +630,12 @@ class WindowStateAnimator {
mDsDy = mWin.mGlobalScale;
}
- /**
- * Calculate the window-space crop rect and fill clipRect.
- * @return true if clipRect has been filled otherwise, no window space crop should be applied.
- */
- private boolean calculateCrop(Rect clipRect) {
- final WindowState w = mWin;
- final DisplayContent displayContent = w.getDisplayContent();
- clipRect.setEmpty();
-
- if (displayContent == null) {
- return false;
- }
-
- if (w.getWindowConfiguration().tasksAreFloating()
- || WindowConfiguration.isSplitScreenWindowingMode(w.getWindowingMode())) {
- return false;
- }
-
- // During forced seamless rotation, the surface bounds get updated with the crop in the
- // new rotation, which is not compatible with showing the surface in the old rotation.
- // To work around that we disable cropping for such windows, as it is not necessary anyways.
- if (w.mForceSeamlesslyRotate) {
- return false;
- }
-
- // If we're animating, the wallpaper should only
- // be updated at the end of the animation.
- if (w.mAttrs.type == TYPE_WALLPAPER) {
- return false;
- }
-
- if (DEBUG_WINDOW_CROP) Slog.d(TAG,
- "Updating crop win=" + w + " mLastCrop=" + mLastClipRect);
-
- w.calculatePolicyCrop(mSystemDecorRect);
-
- if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame="
- + w.getDecorFrame() + " mSystemDecorRect=" + mSystemDecorRect);
-
- // We use the clip rect as provided by the tranformation for non-fullscreen windows to
- // avoid premature clipping with the system decor rect.
- clipRect.set(mSystemDecorRect);
- if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect);
-
- w.expandForSurfaceInsets(clipRect);
-
- // The clip rect was generated assuming (0,0) as the window origin,
- // so we need to translate to match the actual surface coordinates.
- clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top);
-
- if (DEBUG_WINDOW_CROP) Slog.d(TAG,
- "win=" + w + " Clip rect after stack adjustment=" + clipRect);
-
- w.transformClipRectFromScreenToSurfaceSpace(clipRect);
-
- return true;
- }
-
- private void applyCrop(Rect clipRect, boolean recoveringMemory) {
- if (DEBUG_WINDOW_CROP) Slog.d(TAG, "applyCrop: win=" + mWin
- + " clipRect=" + clipRect);
- if (clipRect != null) {
- if (!clipRect.equals(mLastClipRect)) {
- mLastClipRect.set(clipRect);
- mSurfaceController.setCropInTransaction(clipRect, recoveringMemory);
- }
- } else {
- mSurfaceController.clearCropInTransaction(recoveringMemory);
- }
- }
-
private boolean shouldConsumeMainWindowSizeTransaction() {
+ // If we use BLASTSync we always consume the transaction when finishing
+ // the sync.
+ if (mService.useBLASTSync()) {
+ return false;
+ }
// We only consume the transaction when the client is calling relayout
// because this is the only time we know the frameNumber will be valid
// due to the client renderer being paused. Put otherwise, only when
@@ -768,7 +652,7 @@ class WindowStateAnimator {
return true;
}
- void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
+ void setSurfaceBoundariesLocked(SurfaceControl.Transaction t, final boolean recoveringMemory) {
if (mSurfaceController == null) {
return;
}
@@ -777,37 +661,12 @@ class WindowStateAnimator {
final LayoutParams attrs = mWin.getAttrs();
final Task task = w.getTask();
- calculateSurfaceBounds(w, attrs, mTmpSize);
-
- // Once relayout has been called at least once, we need to make sure
- // we only resize the client surface during calls to relayout. For
- // clients which use indeterminate measure specs (MATCH_PARENT),
- // we may try and change their window size without a call to relayout.
- // However, this would be unsafe, as the client may be in the middle
- // of producing a frame at the old size, having just completed layout
- // to find the surface size changed underneath it.
- final boolean relayout = !w.mRelayoutCalled || w.mInRelayout;
- if (relayout) {
- mSurfaceResized = mSurfaceController.setBufferSizeInTransaction(
- mTmpSize.width(), mTmpSize.height(), recoveringMemory);
- } else {
- mSurfaceResized = false;
- }
- // If we are undergoing seamless rotation, the surface has already
- // been set up to persist at it's old location. We need to freeze
- // updates until a resize occurs.
-
- Rect clipRect = null;
- if (calculateCrop(mTmpClipRect)) {
- clipRect = mTmpClipRect;
- }
-
if (shouldConsumeMainWindowSizeTransaction()) {
- task.getMainWindowSizeChangeTask().getSurfaceControl().deferTransactionUntil(
- mWin.getClientViewRootSurface(), mWin.getFrameNumber());
- mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
- mWin.getFrameNumber());
- SurfaceControl.mergeToGlobalTransaction(task.getMainWindowSizeChangeTransaction());
+ t.deferTransactionUntil(task.getMainWindowSizeChangeTask().getSurfaceControl(),
+ mWin.getClientViewRootSurface(), mWin.getFrameNumber());
+ t.deferTransactionUntil(mSurfaceController.mSurfaceControl,
+ mWin.getClientViewRootSurface(), mWin.getFrameNumber());
+ t.merge(task.getMainWindowSizeChangeTransaction());
task.setMainWindowSizeChangeTransaction(null);
}
@@ -816,6 +675,11 @@ class WindowStateAnimator {
final Rect insets = attrs.surfaceInsets;
+ // getFrameNumber is only valid in the call-stack of relayoutWindow
+ // as this is the only-time we know the client renderer
+ // is paused.
+ final boolean relayout = !w.mRelayoutCalled || w.mInRelayout;
+
if (!w.mSeamlesslyRotated) {
// Used to offset the WSA when stack position changes before a resize.
int xOffset = mXOffset;
@@ -827,8 +691,8 @@ class WindowStateAnimator {
// the WS position is reset (so the stack position is shown) at the same
// time that the buffer size changes.
setOffsetPositionForStackResize(false);
- mSurfaceController.deferTransactionUntil(mWin.getClientViewRootSurface(),
- mWin.getFrameNumber());
+ t.deferTransactionUntil(mSurfaceController.mSurfaceControl,
+ mWin.getClientViewRootSurface(), mWin.getFrameNumber());
} else {
final Task stack = mWin.getRootTask();
mTmpPos.x = 0;
@@ -838,18 +702,12 @@ class WindowStateAnimator {
}
xOffset = -mTmpPos.x;
yOffset = -mTmpPos.y;
- // Crop also needs to be extended so the bottom isn't cut off when the WSA
- // position is moved.
- if (clipRect != null) {
- clipRect.right += mTmpPos.x;
- clipRect.bottom += mTmpPos.y;
- }
}
}
if (!mIsWallpaper) {
- mSurfaceController.setPositionInTransaction(xOffset, yOffset, recoveringMemory);
+ mSurfaceController.setPosition(t, xOffset, yOffset, recoveringMemory);
} else {
- setWallpaperPositionAndScale(
+ setWallpaperPositionAndScale(t,
xOffset, yOffset, mWallpaperScale, recoveringMemory);
}
}
@@ -858,18 +716,13 @@ class WindowStateAnimator {
// Wallpaper is already updated above when calling setWallpaperPositionAndScale so
// we only need to consider the non-wallpaper case here.
if (!mIsWallpaper) {
- applyCrop(clipRect, recoveringMemory);
- mSurfaceController.setMatrixInTransaction(
+ mSurfaceController.setMatrix(t,
mDsDx * w.mHScale,
mDtDx * w.mVScale,
mDtDy * w.mHScale,
mDsDy * w.mVScale, recoveringMemory);
}
}
-
- if (mSurfaceResized) {
- mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- }
}
/**
@@ -885,7 +738,7 @@ class WindowStateAnimator {
}
}
- void prepareSurfaceLocked(final boolean recoveringMemory) {
+ void prepareSurfaceLocked(SurfaceControl.Transaction t, final boolean recoveringMemory) {
final WindowState w = mWin;
if (!hasSurface()) {
@@ -902,7 +755,7 @@ class WindowStateAnimator {
computeShownFrameLocked();
- setSurfaceBoundariesLocked(recoveringMemory);
+ setSurfaceBoundariesLocked(t, recoveringMemory);
if (mIsWallpaper && !w.mWallpaperVisible) {
// Wallpaper is no longer visible and there is no wp target => hide it.
@@ -944,7 +797,7 @@ class WindowStateAnimator {
boolean prepared = true;
if (mIsWallpaper) {
- setWallpaperPositionAndScale(
+ setWallpaperPositionAndScale(t,
mXOffset, mYOffset, mWallpaperScale, recoveringMemory);
} else {
prepared =
@@ -1031,7 +884,8 @@ class WindowStateAnimator {
Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
}
mService.openSurfaceTransaction();
- setWallpaperPositionAndScale(dx, dy, scale, false);
+ setWallpaperPositionAndScale(SurfaceControl.getGlobalTransaction(),
+ dx, dy, scale, false);
} catch (RuntimeException e) {
Slog.w(TAG, "Error positioning surface of " + mWin
+ " pos=(" + dx + "," + dy + ")", e);
@@ -1046,8 +900,8 @@ class WindowStateAnimator {
return true;
}
- private void setWallpaperPositionAndScale(int dx, int dy, float scale,
- boolean recoveringMemory) {
+ private void setWallpaperPositionAndScale(SurfaceControl.Transaction t,
+ int dx, int dy, float scale, boolean recoveringMemory) {
DisplayInfo displayInfo = mWin.getDisplayInfo();
Matrix matrix = mWin.mTmpMatrix;
matrix.setTranslate(dx, dy);
@@ -1056,15 +910,14 @@ class WindowStateAnimator {
matrix.getValues(mWin.mTmpMatrixArray);
matrix.reset();
- mSurfaceController.setPositionInTransaction(mWin.mTmpMatrixArray[MTRANS_X],
+ mSurfaceController.setPosition(t,mWin.mTmpMatrixArray[MTRANS_X],
mWin.mTmpMatrixArray[MTRANS_Y], recoveringMemory);
- mSurfaceController.setMatrixInTransaction(
+ mSurfaceController.setMatrix(t,
mDsDx * mWin.mTmpMatrixArray[MSCALE_X] * mWin.mHScale,
mDtDx * mWin.mTmpMatrixArray[MSKEW_Y] * mWin.mVScale,
mDtDy * mWin.mTmpMatrixArray[MSKEW_X] * mWin.mHScale,
mDsDy * mWin.mTmpMatrixArray[MSCALE_Y] * mWin.mVScale,
recoveringMemory);
- applyCrop(null, recoveringMemory);
}
/**
@@ -1118,10 +971,6 @@ class WindowStateAnimator {
* @return Returns true if the surface was successfully shown.
*/
private boolean showSurfaceRobustlyLocked() {
- if (mWin.getWindowConfiguration().windowsAreScaleable()) {
- mSurfaceController.forceScaleableInTransaction(true);
- }
-
boolean shown = mSurfaceController.showRobustlyInTransaction();
if (!shown)
return false;
@@ -1136,7 +985,7 @@ class WindowStateAnimator {
if (!mPendingDestroySurface.mChildrenDetached) {
mPostDrawTransaction.reparentChildren(
mPendingDestroySurface.getClientViewRootSurface(),
- mSurfaceController.mSurfaceControl);
+ mSurfaceController.getClientViewRootSurface());
}
}
@@ -1255,7 +1104,6 @@ class WindowStateAnimator {
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- mLastClipRect.dumpDebug(proto, LAST_CLIP_RECT);
if (mSurfaceController != null) {
mSurfaceController.dumpDebug(proto, SURFACE);
}
@@ -1276,11 +1124,7 @@ class WindowStateAnimator {
pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden);
pw.print(prefix); pw.print("mEnterAnimationPending=" + mEnterAnimationPending);
pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
- pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw);
- if (!mLastFinalClipRect.isEmpty()) {
- pw.print(" mLastFinalClipRect="); mLastFinalClipRect.printShortString(pw);
- }
pw.println();
}
@@ -1288,8 +1132,7 @@ class WindowStateAnimator {
pw.print(prefix); pw.print("mPendingDestroySurface=");
pw.println(mPendingDestroySurface);
}
- if (mSurfaceResized || mSurfaceDestroyDeferred) {
- pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
+ if (mSurfaceDestroyDeferred) {
pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred);
}
if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 9b40822c8ab5..d2c36e2bf347 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -30,7 +30,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowSurfaceControllerProto.LAYER;
import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
-import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
import android.os.Trace;
@@ -62,7 +61,6 @@ class WindowSurfaceController {
private float mSurfaceY = 0;
private int mSurfaceW = 0;
private int mSurfaceH = 0;
- private Rect mSurfaceCrop = new Rect(0, 0, -1, -1);
// Initialize to the identity matrix.
private float mLastDsdx = 1;
@@ -74,13 +72,6 @@ class WindowSurfaceController {
private int mSurfaceLayer = 0;
- // Surface flinger doesn't support crop rectangles where width or height is non-positive.
- // However, we need to somehow handle the situation where the cropping would completely hide
- // the window. We achieve this by explicitly hiding the surface and not letting it be shown.
- private boolean mHiddenForCrop = false;
-
- // Initially a surface is hidden after just being created.
- private boolean mHiddenForOtherReasons = true;
private final String title;
private final WindowManagerService mService;
@@ -140,13 +131,6 @@ class WindowSurfaceController {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- void reparentChildrenInTransaction(WindowSurfaceController other) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "REPARENT from: %s to: %s", this, other);
- if ((mSurfaceControl != null) && (other.mSurfaceControl != null)) {
- mSurfaceControl.reparentChildren(other.mSurfaceControl);
- }
- }
-
void detachChildren() {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SEVER CHILDREN");
mChildrenDetached = true;
@@ -157,7 +141,6 @@ class WindowSurfaceController {
void hide(SurfaceControl.Transaction transaction, String reason) {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
- mHiddenForOtherReasons = true;
mAnimator.destroyPreservedSurfaceLocked();
if (mSurfaceShown) {
@@ -195,51 +178,6 @@ class WindowSurfaceController {
}
}
- void setCropInTransaction(Rect clipRect, boolean recoveringMemory) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE CROP %s: %s", clipRect.toShortString(), title);
- try {
- if (clipRect.width() > 0 && clipRect.height() > 0) {
- if (!clipRect.equals(mSurfaceCrop)) {
- mSurfaceControl.setWindowCrop(clipRect);
- mSurfaceCrop.set(clipRect);
- }
- mHiddenForCrop = false;
- updateVisibility();
- } else {
- mHiddenForCrop = true;
- mAnimator.destroyPreservedSurfaceLocked();
- updateVisibility();
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error setting crop surface of " + this
- + " crop=" + clipRect.toShortString(), e);
- if (!recoveringMemory) {
- mAnimator.reclaimSomeSurfaceMemory("crop", true);
- }
- }
- }
-
- void clearCropInTransaction(boolean recoveringMemory) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE CLEAR CROP: %s", title);
- try {
- Rect clipRect = new Rect(0, 0, -1, -1);
- if (mSurfaceCrop.equals(clipRect)) {
- return;
- }
- mSurfaceControl.setWindowCrop(clipRect);
- mSurfaceCrop.set(clipRect);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error setting clearing crop of " + this, e);
- if (!recoveringMemory) {
- mAnimator.reclaimSomeSurfaceMemory("crop", true);
- }
- }
- }
-
- void setPositionInTransaction(float left, float top, boolean recoveringMemory) {
- setPosition(null, left, top, recoveringMemory);
- }
-
void setPosition(SurfaceControl.Transaction t, float left, float top,
boolean recoveringMemory) {
final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
@@ -251,11 +189,7 @@ class WindowSurfaceController {
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE POS (setPositionInTransaction) @ (%f,%f): %s", left, top, title);
- if (t == null) {
- mSurfaceControl.setPosition(left, top);
- } else {
- t.setPosition(mSurfaceControl, left, top);
- }
+ t.setPosition(mSurfaceControl, left, top);
} catch (RuntimeException e) {
Slog.w(TAG, "Error positioning surface of " + this
+ " pos=(" + left + "," + top + ")", e);
@@ -266,11 +200,6 @@ class WindowSurfaceController {
}
}
- void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
- boolean recoveringMemory) {
- setMatrix(null, dsdx, dtdx, dtdy, dsdy, false);
- }
-
void setMatrix(SurfaceControl.Transaction t, float dsdx, float dtdx,
float dtdy, float dsdy, boolean recoveringMemory) {
final boolean matrixChanged = mLastDsdx != dsdx || mLastDtdx != dtdx ||
@@ -287,11 +216,7 @@ class WindowSurfaceController {
try {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE MATRIX [%f,%f,%f,%f]: %s",
dsdx, dtdx, dtdy, dsdy, title);
- if (t == null) {
- mSurfaceControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
- } else {
- t.setMatrix(mSurfaceControl, dsdx, dtdx, dtdy, dsdy);
- }
+ t.setMatrix(mSurfaceControl, dsdx, dtdx, dtdy, dsdy);
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
// as running out of memory), don't take down the
@@ -304,31 +229,6 @@ class WindowSurfaceController {
}
}
- boolean setBufferSizeInTransaction(int width, int height, boolean recoveringMemory) {
- final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
- if (surfaceResized) {
- mSurfaceW = width;
- mSurfaceH = height;
-
- try {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SIZE %dx%d: %s", width, height, title);
- mSurfaceControl.setBufferSize(width, height);
- } catch (RuntimeException e) {
- // If something goes wrong with the surface (such
- // as running out of memory), don't take down the
- // entire system.
- Slog.e(TAG, "Error resizing surface of " + title
- + " size=(" + width + "x" + height + ")", e);
- if (!recoveringMemory) {
- mAnimator.reclaimSomeSurfaceMemory("size", true);
- }
- return false;
- }
- return true;
- }
- return false;
- }
-
boolean prepareToShowInTransaction(float alpha,
float dsdx, float dtdx, float dsdy,
float dtdy, boolean recoveringMemory) {
@@ -421,35 +321,15 @@ class WindowSurfaceController {
}
}
- void getContainerRect(Rect rect) {
- mAnimator.getContainerRect(rect);
- }
-
boolean showRobustlyInTransaction() {
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
+ " during relayout");
- mHiddenForOtherReasons = false;
- return updateVisibility();
- }
- private boolean updateVisibility() {
- if (mHiddenForCrop || mHiddenForOtherReasons) {
- if (mSurfaceShown) {
- hideSurface(mTmpTransaction);
- SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
- }
- return false;
- } else {
- if (!mSurfaceShown) {
- return showSurface();
- } else {
- return true;
- }
+ if (mSurfaceShown) {
+ return true;
}
- }
- private boolean showSurface() {
try {
setShown(true);
mSurfaceControl.show();
@@ -459,7 +339,6 @@ class WindowSurfaceController {
}
mAnimator.reclaimSomeSurfaceMemory("show", true);
-
return false;
}
@@ -468,13 +347,6 @@ class WindowSurfaceController {
mSurfaceControl.deferTransactionUntil(barrier, frame);
}
- void forceScaleableInTransaction(boolean force) {
- // -1 means we don't override the default or client specified
- // scaling mode.
- int scalingMode = force ? SCALING_MODE_SCALE_TO_WINDOW : -1;
- mSurfaceControl.setOverrideScalingMode(scalingMode);
- }
-
boolean clearWindowContentFrameStats() {
if (mSurfaceControl == null) {
return false;
@@ -489,7 +361,6 @@ class WindowSurfaceController {
return mSurfaceControl.getContentFrameStats(outStats);
}
-
boolean hasSurface() {
return mSurfaceControl != null;
}
@@ -504,10 +375,6 @@ class WindowSurfaceController {
}
}
- int getLayer() {
- return mSurfaceLayer;
- }
-
boolean getShown() {
return mSurfaceShown;
}
@@ -524,14 +391,6 @@ class WindowSurfaceController {
}
}
- float getX() {
- return mSurfaceX;
- }
-
- float getY() {
- return mSurfaceY;
- }
-
int getWidth() {
return mSurfaceW;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index bc4d9a973ecb..fa0b8cce5f70 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -181,6 +181,12 @@ class WindowToken extends WindowContainer<WindowState> {
}
}
}
+
+ /** The state may not only be used by self. Make sure to leave the influence by others. */
+ void disassociate(WindowToken token) {
+ mAssociatedTokens.remove(token);
+ mRotatedContainers.remove(token);
+ }
}
private class DeathRecipient implements IBinder.DeathRecipient {
@@ -548,7 +554,7 @@ class WindowToken extends WindowContainer<WindowState> {
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
Configuration config) {
if (mFixedRotationTransformState != null) {
- cleanUpFixedRotationTransformState(true /* replacing */);
+ mFixedRotationTransformState.disassociate(this);
}
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
new Configuration(config), mDisplayContent.getRotation());
@@ -556,8 +562,7 @@ class WindowToken extends WindowContainer<WindowState> {
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames,
mFixedRotationTransformState.mInsetsState,
mFixedRotationTransformState.mBarContentFrames);
- onConfigurationChanged(getParent().getConfiguration());
- notifyFixedRotationTransform(true /* enabled */);
+ onFixedRotationStatePrepared();
}
/**
@@ -570,12 +575,29 @@ class WindowToken extends WindowContainer<WindowState> {
return;
}
if (mFixedRotationTransformState != null) {
- cleanUpFixedRotationTransformState(true /* replacing */);
+ mFixedRotationTransformState.disassociate(this);
}
mFixedRotationTransformState = fixedRotationState;
fixedRotationState.mAssociatedTokens.add(this);
- onConfigurationChanged(getParent().getConfiguration());
+ onFixedRotationStatePrepared();
+ }
+
+ /**
+ * Makes the rotated states take effect for this window container and its client process.
+ * This should only be called when {@link #mFixedRotationTransformState} is non-null.
+ */
+ private void onFixedRotationStatePrepared() {
+ // Send the adjustment info first so when the client receives configuration change, it can
+ // get the rotated display metrics.
notifyFixedRotationTransform(true /* enabled */);
+ // Resolve the rotated configuration.
+ onConfigurationChanged(getParent().getConfiguration());
+ final ActivityRecord r = asActivityRecord();
+ if (r != null && r.hasProcess()) {
+ // The application needs to be configured as in a rotated environment for compatibility.
+ // This registration will send the rotated configuration to its process.
+ r.app.registerActivityConfigurationListener(r);
+ }
}
/**
@@ -626,21 +648,12 @@ class WindowToken extends WindowContainer<WindowState> {
// The state is cleared at the end, because it is used to indicate that other windows can
// use seamless rotation when applying rotation to display.
for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
- state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState(
- false /* replacing */);
+ final WindowToken token = state.mAssociatedTokens.get(i);
+ token.mFixedRotationTransformState = null;
+ token.notifyFixedRotationTransform(false /* enabled */);
}
}
- private void cleanUpFixedRotationTransformState(boolean replacing) {
- if (replacing && mFixedRotationTransformState.mAssociatedTokens.size() > 1) {
- // The state is not only used by self. Make sure to leave the influence by others.
- mFixedRotationTransformState.mAssociatedTokens.remove(this);
- mFixedRotationTransformState.mRotatedContainers.remove(this);
- }
- mFixedRotationTransformState = null;
- notifyFixedRotationTransform(false /* enabled */);
- }
-
/** Notifies application side to enable or disable the rotation adjustment of display info. */
private void notifyFixedRotationTransform(boolean enabled) {
FixedRotationAdjustments adjustments = null;
@@ -704,8 +717,9 @@ class WindowToken extends WindowContainer<WindowState> {
if (!isFixedRotationTransforming()) {
return null;
}
- return new FixedRotationAdjustments(mFixedRotationTransformState.mDisplayInfo.rotation,
- mFixedRotationTransformState.mDisplayInfo.displayCutout);
+ final DisplayInfo displayInfo = mFixedRotationTransformState.mDisplayInfo;
+ return new FixedRotationAdjustments(displayInfo.rotation, displayInfo.appWidth,
+ displayInfo.appHeight, displayInfo.displayCutout);
}
@Override
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 6a65cd76a280..ce1d9aa14a9c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -54,6 +54,7 @@ cc_library_static {
"com_android_server_UsbDescriptorParser.cpp",
"com_android_server_UsbMidiDevice.cpp",
"com_android_server_UsbHostManager.cpp",
+ "com_android_server_VibratorManagerService.cpp",
"com_android_server_VibratorService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_am_CachedAppOptimizer.cpp",
@@ -133,6 +134,7 @@ cc_defaults {
"android.hardware.broadcastradio@1.0",
"android.hardware.broadcastradio@1.1",
"android.hardware.contexthub@1.0",
+ "android.hardware.gnss-cpp",
"android.hardware.gnss@1.0",
"android.hardware.gnss@1.1",
"android.hardware.gnss@2.0",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 8646a53f3390..4c017f5513a2 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -2,6 +2,7 @@
per-file com_android_server_lights_LightsService.cpp = michaelwr@google.com, santoscordon@google.com
# Haptics
+per-file com_android_server_VibratorManagerService.cpp = michaelwr@google.com
per-file com_android_server_VibratorService.cpp = michaelwr@google.com
# Input
diff --git a/services/core/jni/com_android_server_VibratorManagerService.cpp b/services/core/jni/com_android_server_VibratorManagerService.cpp
new file mode 100644
index 000000000000..dae9cefdd1f4
--- /dev/null
+++ b/services/core/jni/com_android_server_VibratorManagerService.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VibratorManagerService"
+
+#include <nativehelper/JNIHelp.h>
+#include "android_runtime/AndroidRuntime.h"
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+#include <utils/Log.h>
+#include <utils/misc.h>
+
+#include <vibratorservice/VibratorManagerHalWrapper.h>
+
+namespace android {
+
+class NativeVibratorManagerService {
+public:
+ NativeVibratorManagerService() : mHal(std::make_unique<vibrator::LegacyManagerHalWrapper>()) {}
+ ~NativeVibratorManagerService() = default;
+
+ vibrator::ManagerHalWrapper* hal() const { return mHal.get(); }
+
+private:
+ const std::unique_ptr<vibrator::ManagerHalWrapper> mHal;
+};
+
+static void destroyNativeService(void* ptr) {
+ NativeVibratorManagerService* service = reinterpret_cast<NativeVibratorManagerService*>(ptr);
+ if (service) {
+ delete service;
+ }
+}
+
+static jlong nativeInit(JNIEnv* /* env */, jclass /* clazz */) {
+ std::unique_ptr<NativeVibratorManagerService> service =
+ std::make_unique<NativeVibratorManagerService>();
+ return reinterpret_cast<jlong>(service.release());
+}
+
+static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeService));
+}
+
+static jintArray nativeGetVibratorIds(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+ NativeVibratorManagerService* service =
+ reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+ if (service == nullptr) {
+ ALOGE("nativeGetVibratorIds failed because native service was not initialized");
+ return nullptr;
+ }
+ auto result = service->hal()->getVibratorIds();
+ if (!result.isOk()) {
+ return nullptr;
+ }
+ std::vector<int32_t> vibratorIds = result.value();
+ jintArray ids = env->NewIntArray(vibratorIds.size());
+ env->SetIntArrayRegion(ids, 0, vibratorIds.size(), reinterpret_cast<jint*>(vibratorIds.data()));
+ return ids;
+}
+
+static const JNINativeMethod method_table[] = {
+ {"nativeInit", "()J", (void*)nativeInit},
+ {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer},
+ {"nativeGetVibratorIds", "(J)[I", (void*)nativeGetVibratorIds},
+};
+
+int register_android_server_VibratorManagerService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/VibratorManagerService", method_table,
+ NELEM(method_table));
+}
+
+}; // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 46136ca0647d..c1d5f19fd256 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -74,6 +74,9 @@
using android::base::ParseUint;
using android::base::StringPrintf;
+using android::os::BlockUntrustedTouchesMode;
+using android::os::InputEventInjectionResult;
+using android::os::InputEventInjectionSync;
// Maximum allowable delay value in a vibration pattern before
// which the delay will be truncated.
@@ -96,6 +99,7 @@ static struct {
jmethodID notifyInputChannelBroken;
jmethodID notifyANR;
jmethodID notifyFocusChanged;
+ jmethodID notifyUntrustedTouch;
jmethodID filterInputEvent;
jmethodID interceptKeyBeforeQueueing;
jmethodID interceptMotionBeforeQueueingNonInteractive;
@@ -206,17 +210,19 @@ public:
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- status_t registerInputChannel(JNIEnv* env, const std::shared_ptr<InputChannel>& inputChannel);
- status_t registerInputMonitor(JNIEnv* env, const std::shared_ptr<InputChannel>& inputChannel,
- int32_t displayId, bool isGestureMonitor);
- status_t unregisterInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
+ base::Result<std::unique_ptr<InputChannel>> createInputChannel(JNIEnv* env,
+ const std::string& name);
+ base::Result<std::unique_ptr<InputChannel>> createInputMonitor(JNIEnv* env, int32_t displayId,
+ bool isGestureMonitor,
+ const std::string& name);
+ status_t removeInputChannel(JNIEnv* env, const sp<IBinder>& connectionToken);
status_t pilferPointers(const sp<IBinder>& token);
void displayRemoved(JNIEnv* env, int32_t displayId);
void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
void setFocusedDisplay(JNIEnv* env, int32_t displayId);
void setInputDispatchMode(bool enabled, bool frozen);
- void setSystemUiVisibility(int32_t visibility);
+ void setSystemUiLightsOut(bool lightsOut);
void setPointerSpeed(int32_t speed);
void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
void setShowTouches(bool enabled);
@@ -251,6 +257,7 @@ public:
const sp<IBinder>& token, const std::string& reason) override;
void notifyInputChannelBroken(const sp<IBinder>& token) override;
void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override;
+ void notifyUntrustedTouch(const std::string& obscuringPackage) override;
bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override;
void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override;
void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) override;
@@ -284,8 +291,8 @@ private:
// Display size information.
std::vector<DisplayViewport> viewports;
- // System UI visibility.
- int32_t systemUiVisibility;
+ // True if System UI is less noticeable.
+ bool systemUiLightsOut;
// Pointer speed.
int32_t pointerSpeed;
@@ -337,7 +344,7 @@ NativeInputManager::NativeInputManager(jobject contextObj,
{
AutoMutex _l(mLock);
- mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
+ mLocked.systemUiLightsOut = false;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
@@ -364,8 +371,8 @@ void NativeInputManager::dump(std::string& dump) {
}
{
AutoMutex _l(mLock);
- dump += StringPrintf(INDENT "System UI Visibility: 0x%0" PRIx32 "\n",
- mLocked.systemUiVisibility);
+ dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
+ toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
@@ -432,24 +439,22 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO
InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
-status_t NativeInputManager::registerInputChannel(
- JNIEnv* /* env */, const std::shared_ptr<InputChannel>& inputChannel) {
+base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
+ JNIEnv* /* env */, const std::string& name) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
+ return mInputManager->getDispatcher()->createInputChannel(name);
}
-status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,
- const std::shared_ptr<InputChannel>& inputChannel,
- int32_t displayId, bool isGestureMonitor) {
+base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(
+ JNIEnv* /* env */, int32_t displayId, bool isGestureMonitor, const std::string& name) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->registerInputMonitor(
- inputChannel, displayId, isGestureMonitor);
+ return mInputManager->getDispatcher()->createInputMonitor(displayId, isGestureMonitor, name);
}
-status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
- const sp<IBinder>& connectionToken) {
+status_t NativeInputManager::removeInputChannel(JNIEnv* /* env */,
+ const sp<IBinder>& connectionToken) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->unregisterInputChannel(connectionToken);
+ return mInputManager->getDispatcher()->removeInputChannel(connectionToken);
}
status_t NativeInputManager::pilferPointers(const sp<IBinder>& token) {
@@ -752,6 +757,17 @@ void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) {
}
}
+void NativeInputManager::notifyUntrustedTouch(const std::string& obscuringPackage) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ ALOGD("notifyUntrustedTouch - obscuringPackage=%s", obscuringPackage.c_str());
+#endif
+ ATRACE_CALL();
+ JNIEnv* env = jniEnv();
+ jstring jPackage = env->NewStringUTF(obscuringPackage.c_str());
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyUntrustedTouch, jPackage);
+ checkAndClearExceptionFromCallback(env, "notifyUntrustedTouch");
+}
+
void NativeInputManager::notifyFocusChanged(const sp<IBinder>& oldToken,
const sp<IBinder>& newToken) {
#if DEBUG_INPUT_DISPATCHER_POLICY
@@ -811,11 +827,11 @@ void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
mInputManager->getDispatcher()->setInputDispatchMode(enabled, frozen);
}
-void NativeInputManager::setSystemUiVisibility(int32_t visibility) {
+void NativeInputManager::setSystemUiLightsOut(bool lightsOut) {
AutoMutex _l(mLock);
- if (mLocked.systemUiVisibility != visibility) {
- mLocked.systemUiVisibility = visibility;
+ if (mLocked.systemUiLightsOut != lightsOut) {
+ mLocked.systemUiLightsOut = lightsOut;
updateInactivityTimeoutLocked();
}
}
@@ -826,9 +842,8 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) {
return;
}
- bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
- controller->setInactivityTimeout(lightsOut ? InactivityTimeout::SHORT
- : InactivityTimeout::NORMAL);
+ controller->setInactivityTimeout(mLocked.systemUiLightsOut ? InactivityTimeout::SHORT
+ : InactivityTimeout::NORMAL);
}
void NativeInputManager::setPointerSpeed(int32_t speed) {
@@ -1352,80 +1367,83 @@ static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */,
return result;
}
-static void throwInputChannelNotInitialized(JNIEnv* env) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "inputChannel is not initialized");
-}
-
static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */,
const std::shared_ptr<InputChannel>& inputChannel,
void* data) {
NativeInputManager* im = static_cast<NativeInputManager*>(data);
- ALOGW("Input channel object '%s' was disposed without first being unregistered with "
- "the input manager!", inputChannel->getName().c_str());
- im->unregisterInputChannel(env, inputChannel->getConnectionToken());
+ ALOGW("Input channel object '%s' was disposed without first being removed with "
+ "the input manager!",
+ inputChannel->getName().c_str());
+ im->removeInputChannel(env, inputChannel->getConnectionToken());
}
-static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputChannelObj) {
+static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jstring nameObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
- std::shared_ptr<InputChannel> inputChannel =
- android_view_InputChannel_getInputChannel(env, inputChannelObj);
- if (inputChannel == nullptr) {
- throwInputChannelNotInitialized(env);
- return;
- }
+ ScopedUtfChars nameChars(env, nameObj);
+ std::string name = nameChars.c_str();
- status_t status = im->registerInputChannel(env, inputChannel);
+ base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputChannel(env, name);
- if (status) {
- std::string message;
- message += StringPrintf("Failed to register input channel. status=%d", status);
+ if (!inputChannel) {
+ std::string message = inputChannel.error().message();
+ message += StringPrintf(" Status=%d", inputChannel.error().code());
jniThrowRuntimeException(env, message.c_str());
- return;
+ return nullptr;
+ }
+
+ jobject inputChannelObj =
+ android_view_InputChannel_createJavaObject(env, std::move(*inputChannel));
+ if (!inputChannelObj) {
+ return nullptr;
}
android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
handleInputChannelDisposed, im);
+ return inputChannelObj;
}
-static void nativeRegisterInputMonitor(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputChannelObj, jint displayId, jboolean isGestureMonitor) {
+static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId,
+ jboolean isGestureMonitor, jstring nameObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
- std::shared_ptr<InputChannel> inputChannel =
- android_view_InputChannel_getInputChannel(env, inputChannelObj);
- if (inputChannel == nullptr) {
- throwInputChannelNotInitialized(env);
- return;
- }
-
if (displayId == ADISPLAY_ID_NONE) {
std::string message = "InputChannel used as a monitor must be associated with a display";
jniThrowRuntimeException(env, message.c_str());
- return;
+ return nullptr;
}
- status_t status = im->registerInputMonitor(env, inputChannel, displayId, isGestureMonitor);
+ ScopedUtfChars nameChars(env, nameObj);
+ std::string name = nameChars.c_str();
+
+ base::Result<std::unique_ptr<InputChannel>> inputChannel =
+ im->createInputMonitor(env, displayId, isGestureMonitor, name);
- if (status) {
- std::string message = StringPrintf("Failed to register input channel. status=%d", status);
+ if (!inputChannel) {
+ std::string message = inputChannel.error().message();
+ message += StringPrintf(" Status=%d", inputChannel.error().code());
jniThrowRuntimeException(env, message.c_str());
- return;
+ return nullptr;
+ }
+
+ jobject inputChannelObj =
+ android_view_InputChannel_createJavaObject(env, std::move(*inputChannel));
+ if (!inputChannelObj) {
+ return nullptr;
}
+ return inputChannelObj;
}
-static void nativeUnregisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
- jobject tokenObj) {
+static void nativeRemoveInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
- status_t status = im->unregisterInputChannel(env, token);
- if (status && status != BAD_VALUE) { // ignore already unregistered channel
+ status_t status = im->removeInputChannel(env, token);
+ if (status && status != BAD_VALUE) { // ignore already removed channel
std::string message;
- message += StringPrintf("Failed to unregister input channel. status=%d", status);
+ message += StringPrintf("Failed to remove input channel. status=%d", status);
jniThrowRuntimeException(env, message.c_str());
}
}
@@ -1451,22 +1469,40 @@ static void nativeSetInTouchMode(JNIEnv* /* env */, jclass /* clazz */,
im->getInputManager()->getDispatcher()->setInTouchMode(inTouchMode);
}
+static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* /* env */, jclass /* clazz */,
+ jlong ptr, jfloat opacity) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getDispatcher()->setMaximumObscuringOpacityForTouch(opacity);
+}
+
+static void nativeSetBlockUntrustedTouchesMode(JNIEnv* env, jclass /* clazz */, jlong ptr,
+ jint mode) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getDispatcher()->setBlockUntrustedTouchesMode(
+ static_cast<BlockUntrustedTouchesMode>(mode));
+}
+
static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
jint syncMode, jint timeoutMillis, jint policyFlags) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ // static_cast is safe because the value was already checked at the Java layer
+ InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
+
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
KeyEvent keyEvent;
status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
if (status) {
jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
- return INPUT_EVENT_INJECTION_FAILED;
+ return static_cast<jint>(InputEventInjectionResult::FAILED);
}
- const int32_t result =
+ const InputEventInjectionResult result =
im->getInputManager()->getDispatcher()->injectInputEvent(&keyEvent, injectorPid,
- injectorUid, syncMode,
+ injectorUid, mode,
std::chrono::milliseconds(
timeoutMillis),
uint32_t(policyFlags));
@@ -1475,19 +1511,19 @@ static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
if (!motionEvent) {
jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
- return INPUT_EVENT_INJECTION_FAILED;
+ return static_cast<jint>(InputEventInjectionResult::FAILED);
}
- const int32_t result =
- (jint)im->getInputManager()
- ->getDispatcher()
- ->injectInputEvent(motionEvent, injectorPid, injectorUid, syncMode,
- std::chrono::milliseconds(timeoutMillis),
- uint32_t(policyFlags));
+ const InputEventInjectionResult result =
+ im->getInputManager()->getDispatcher()->injectInputEvent(motionEvent, injectorPid,
+ injectorUid, mode,
+ std::chrono::milliseconds(
+ timeoutMillis),
+ uint32_t(policyFlags));
return static_cast<jint>(result);
} else {
jniThrowRuntimeException(env, "Invalid input event type.");
- return INPUT_EVENT_INJECTION_FAILED;
+ return static_cast<jint>(InputEventInjectionResult::FAILED);
}
}
@@ -1575,11 +1611,11 @@ static void nativeSetInputDispatchMode(JNIEnv* /* env */,
im->setInputDispatchMode(enabled, frozen);
}
-static void nativeSetSystemUiVisibility(JNIEnv* /* env */,
- jclass /* clazz */, jlong ptr, jint visibility) {
+static void nativeSetSystemUiLightsOut(JNIEnv* /* env */, jclass /* clazz */, jlong ptr,
+ jboolean lightsOut) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
- im->setSystemUiVisibility(visibility);
+ im->setSystemUiLightsOut(lightsOut);
}
static jboolean nativeTransferTouchFocus(JNIEnv* env,
@@ -1780,15 +1816,17 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState},
{"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState},
{"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys},
- {"nativeRegisterInputChannel", "(JLandroid/view/InputChannel;)V",
- (void*)nativeRegisterInputChannel},
- {"nativeRegisterInputMonitor", "(JLandroid/view/InputChannel;IZ)V",
- (void*)nativeRegisterInputMonitor},
- {"nativeUnregisterInputChannel", "(JLandroid/os/IBinder;)V",
- (void*)nativeUnregisterInputChannel},
+ {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;",
+ (void*)nativeCreateInputChannel},
+ {"nativeCreateInputMonitor", "(JIZLjava/lang/String;)Landroid/view/InputChannel;",
+ (void*)nativeCreateInputMonitor},
+ {"nativeRemoveInputChannel", "(JLandroid/os/IBinder;)V", (void*)nativeRemoveInputChannel},
{"nativePilferPointers", "(JLandroid/os/IBinder;)V", (void*)nativePilferPointers},
{"nativeSetInputFilterEnabled", "(JZ)V", (void*)nativeSetInputFilterEnabled},
{"nativeSetInTouchMode", "(JZ)V", (void*)nativeSetInTouchMode},
+ {"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
+ (void*)nativeSetMaximumObscuringOpacityForTouch},
+ {"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
{"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
(void*)nativeInjectInputEvent},
{"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
@@ -1800,7 +1838,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"nativeSetFocusedDisplay", "(JI)V", (void*)nativeSetFocusedDisplay},
{"nativeSetPointerCapture", "(JZ)V", (void*)nativeSetPointerCapture},
{"nativeSetInputDispatchMode", "(JZZ)V", (void*)nativeSetInputDispatchMode},
- {"nativeSetSystemUiVisibility", "(JI)V", (void*)nativeSetSystemUiVisibility},
+ {"nativeSetSystemUiLightsOut", "(JZ)V", (void*)nativeSetSystemUiLightsOut},
{"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z",
(void*)nativeTransferTouchFocus},
{"nativeSetPointerSpeed", "(JI)V", (void*)nativeSetPointerSpeed},
@@ -1868,6 +1906,9 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz,
"notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V");
+ GET_METHOD_ID(gServiceClassInfo.notifyUntrustedTouch, clazz, "notifyUntrustedTouch",
+ "(Ljava/lang/String;)V");
+
GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz,
"notifyANR",
"(Landroid/view/InputApplicationHandle;Landroid/os/IBinder;Ljava/lang/String;)J");
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 4e53aa218b71..91645ba1a9b9 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -30,9 +30,12 @@
#include <android/hardware/gnss/2.1/IGnssAntennaInfo.h>
#include <android/hardware/gnss/2.1/IGnssMeasurement.h>
#include <android/hardware/gnss/3.0/IGnssPsds.h>
+#include <android/hardware/gnss/BnGnss.h>
+#include <android/hardware/gnss/BnGnssPsdsCallback.h>
#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
#include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
#include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h>
+#include <binder/IServiceManager.h>
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
@@ -142,9 +145,10 @@ static JavaVM* sJvm;
using android::OK;
using android::sp;
-using android::wp;
using android::status_t;
using android::String16;
+using android::wp;
+using android::binder::Status;
using android::hardware::Return;
using android::hardware::Void;
@@ -152,6 +156,7 @@ using android::hardware::hidl_vec;
using android::hardware::hidl_string;
using android::hardware::hidl_death_recipient;
+using android::hardware::gnss::PsdsType;
using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V1_0::IAGnssRilCallback;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -161,11 +166,10 @@ using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback;
using android::hardware::gnss::V1_0::IGnssNi;
using android::hardware::gnss::V1_0::IGnssNiCallback;
using android::hardware::gnss::V1_0::IGnssXtra;
+using android::hardware::gnss::V2_0::ElapsedRealtimeFlags;
using android::hardware::gnss::V3_0::IGnssPsds;
using android::hardware::gnss::V3_0::IGnssPsdsCallback;
-using android::hardware::gnss::V2_0::ElapsedRealtimeFlags;
-
using MeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::MeasurementCorrections;
using MeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::MeasurementCorrections;
@@ -224,6 +228,10 @@ using android::hardware::gnss::measurement_corrections::V1_0::GnssSingleSatCorre
using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl;
using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControlCallback;
+using IGnssAidl = android::hardware::gnss::IGnss;
+using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
+using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
+
struct GnssDeathRecipient : virtual public hidl_death_recipient
{
// hidl_death_recipient interface
@@ -245,7 +253,9 @@ sp<IGnss_V1_1> gnssHal_V1_1 = nullptr;
sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
sp<IGnss_V3_0> gnssHal_V3_0 = nullptr;
+sp<IGnssAidl> gnssHalAidl = nullptr;
sp<IGnssPsds> gnssPsdsIface = nullptr;
+sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
@@ -445,6 +455,19 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa
}
}
+static jboolean checkAidlStatus(const Status& status, const char* errorMessage,
+ const bool success) {
+ if (!status.isOk()) {
+ ALOGE("%s AIDL transport error: %s", errorMessage, status.toString8().c_str());
+ return JNI_FALSE;
+ }
+ if (!success) {
+ ALOGE("AIDL return failure: %s", errorMessage);
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
static jobject createHalInterfaceVersionJavaObject(JNIEnv* env, jint major, jint minor) {
jobject version = env->NewObject(class_gnssConfiguration_halInterfaceVersion,
method_halInterfaceVersionCtor, major, minor);
@@ -928,20 +951,31 @@ Return<void> GnssCallback::gnssSetSystemInfoCb(const IGnssCallback_V2_0::GnssSys
* GnssPsdsCallback class implements the callback methods for the IGnssPsds
* interface.
*/
+struct GnssPsdsCallbackAidl : public android::hardware::gnss::BnGnssPsdsCallback {
+ Status downloadRequestCb(PsdsType psdsType) override {
+ ALOGD("%s. psdsType: %d", __func__, static_cast<int32_t>(psdsType));
+ JNIEnv* env = getJniEnv();
+ env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ return Status::ok();
+ }
+};
+
+/*
+ * GnssPsdsCallback class implements the callback methods for the IGnssPsds
+ * interface.
+ */
struct GnssPsdsCallback : public IGnssPsdsCallback {
Return<void> downloadRequestCb() override;
Return<void> downloadRequestCb_3_0(int32_t psdsType) override;
};
Return<void> GnssPsdsCallback::downloadRequestCb() {
- JNIEnv* env = getJniEnv();
- env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, /* psdsType= */ 1);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return Void();
+ return downloadRequestCb_3_0(/* psdsType= */ 1);
}
Return<void> GnssPsdsCallback::downloadRequestCb_3_0(int32_t psdsType) {
- ALOGD("%s: %d", __func__, psdsType);
+ ALOGD("%s. psdsType: %d", __func__, psdsType);
JNIEnv* env = getJniEnv();
env->CallVoidMethod(mCallbacksObj, method_psdsDownloadRequest, psdsType);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
@@ -1917,6 +1951,11 @@ struct GnssBatchingCallback_V2_0 : public IGnssBatchingCallback_V2_0 {
/* Initializes the GNSS service handle. */
static void android_location_GnssLocationProvider_set_gps_service_handle() {
+ gnssHalAidl = waitForVintfService<IGnssAidl>();
+ if (gnssHalAidl != nullptr) {
+ ALOGD("Successfully got GNSS AIDL handle.");
+ }
+
ALOGD("Trying IGnss_V3_0::getService()");
gnssHal_V3_0 = IGnss_V3_0::getService();
if (gnssHal_V3_0 != nullptr) {
@@ -2168,7 +2207,15 @@ static void android_location_GnssNative_init_once(JNIEnv* env, jobject obj,
ALOGD("Link to death notification successful");
}
- if (gnssHal_V3_0 != nullptr) {
+ if (gnssHalAidl != nullptr) {
+ sp<IGnssPsdsAidl> gnssPsdsAidl;
+ auto status = gnssHalAidl->getExtensionPsds(&gnssPsdsAidl);
+ if (status.isOk()) {
+ gnssPsdsAidlIface = gnssPsdsAidl;
+ } else {
+ ALOGD("Unable to get a handle to PSDS AIDL interface.");
+ }
+ } else if (gnssHal_V3_0 != nullptr) {
auto gnssPsds = gnssHal_V3_0->getExtensionPsds();
if (!gnssPsds.isOk()) {
ALOGD("Unable to get a handle to Psds");
@@ -2470,19 +2517,28 @@ static jboolean android_location_GnssLocationProvider_init(JNIEnv* /* env */, jo
}
// Set IGnssPsds or IGnssXtra callback.
- sp<IGnssPsdsCallback> gnssPsdsCbIface = new GnssPsdsCallback();
- if (gnssPsdsIface != nullptr) {
- result = gnssPsdsIface->setCallback_3_0(gnssPsdsCbIface);
- if (!checkHidlReturn(result, "IGnssPsds setCallback() failed.")) {
- gnssPsdsIface = nullptr;
- }
- } else if (gnssXtraIface != nullptr) {
- result = gnssXtraIface->setCallback(gnssPsdsCbIface);
- if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) {
- gnssXtraIface = nullptr;
+ if (gnssPsdsAidlIface != nullptr) {
+ sp<IGnssPsdsCallbackAidl> gnssPsdsCallbackAidl = new GnssPsdsCallbackAidl();
+ bool success;
+ auto status = gnssPsdsAidlIface->setCallback(gnssPsdsCallbackAidl, &success);
+ if (!checkAidlStatus(status, "IGnssPsdsAidl setCallback() failed.", success)) {
+ gnssPsdsAidlIface = nullptr;
}
} else {
- ALOGI("Unable to initialize IGnssPsds/IGnssXtra interface.");
+ sp<IGnssPsdsCallback> gnssPsdsCbIface = new GnssPsdsCallback();
+ if (gnssPsdsIface != nullptr) {
+ result = gnssPsdsIface->setCallback_3_0(gnssPsdsCbIface);
+ if (!checkHidlReturn(result, "IGnssPsds setCallback() failed.")) {
+ gnssPsdsIface = nullptr;
+ }
+ } else if (gnssXtraIface != nullptr) {
+ result = gnssXtraIface->setCallback(gnssPsdsCbIface);
+ if (!checkHidlReturn(result, "IGnssXtra setCallback() failed.")) {
+ gnssXtraIface = nullptr;
+ }
+ } else {
+ ALOGI("Unable to initialize IGnssPsds/IGnssXtra interface.");
+ }
}
// Set IAGnss.hal callback.
@@ -2743,19 +2799,29 @@ static void android_location_GnssLocationProvider_inject_location(JNIEnv* /* env
static jboolean android_location_GnssLocationProvider_supports_psds(
JNIEnv* /* env */, jobject /* obj */) {
- return (gnssPsdsIface != nullptr || gnssXtraIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+ return (gnssPsdsAidlIface != nullptr || gnssPsdsIface != nullptr || gnssXtraIface != nullptr)
+ ? JNI_TRUE
+ : JNI_FALSE;
}
static void android_location_GnssLocationProvider_inject_psds_data(JNIEnv* env, jobject /* obj */,
jbyteArray data, jint length,
jint psdsType) {
- if (gnssPsdsIface == nullptr && gnssXtraIface == nullptr) {
+ if (gnssPsdsAidlIface == nullptr && gnssPsdsIface == nullptr && gnssXtraIface == nullptr) {
ALOGE("%s: IGnssPsds or IGnssXtra interface not available.", __func__);
return;
}
jbyte* bytes = reinterpret_cast<jbyte *>(env->GetPrimitiveArrayCritical(data, 0));
- if (gnssPsdsIface != nullptr) {
+ if (gnssPsdsAidlIface != nullptr) {
+ bool success;
+ auto status = gnssPsdsAidlIface->injectPsdsData(static_cast<PsdsType>(psdsType),
+ std::vector<uint8_t>((const uint8_t*)bytes,
+ (const uint8_t*)bytes +
+ length),
+ &success);
+ checkAidlStatus(status, "IGnssPsdsAidl injectPsdsData() failed.", success);
+ } else if (gnssPsdsIface != nullptr) {
auto result = gnssPsdsIface->injectPsdsData_3_0(psdsType,
std::string((const char*)bytes, length));
checkHidlReturn(result, "IGnssPsds injectPsdsData() failed.");
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 9e2bb45ab341..631e185fffce 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -225,6 +225,20 @@ std::optional<T> read(IncFsSpan& data) {
return res;
}
+static inline unique_fd openLocalFile(JNIEnv* env, const JniIds& jni, jobject shellCommand,
+ const std::string& path) {
+ if (shellCommand) {
+ return unique_fd{env->CallStaticIntMethod(jni.packageManagerShellCommandDataLoader,
+ jni.pmscdGetLocalFile, shellCommand,
+ env->NewStringUTF(path.c_str()))};
+ }
+ auto fd = unique_fd(::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!fd.ok()) {
+ PLOG(ERROR) << "Failed to open file: " << path << ", error code: " << fd.get();
+ }
+ return fd;
+}
+
static inline InputDescs openLocalFile(JNIEnv* env, const JniIds& jni, jobject shellCommand,
IncFsSize size, const std::string& filePath) {
InputDescs result;
@@ -232,9 +246,7 @@ static inline InputDescs openLocalFile(JNIEnv* env, const JniIds& jni, jobject s
const std::string idsigPath = filePath + ".idsig";
- unique_fd idsigFd{env->CallStaticIntMethod(jni.packageManagerShellCommandDataLoader,
- jni.pmscdGetLocalFile, shellCommand,
- env->NewStringUTF(idsigPath.c_str()))};
+ unique_fd idsigFd = openLocalFile(env, jni, shellCommand, idsigPath);
if (idsigFd.ok()) {
auto treeSize = verityTreeSizeForFile(size);
auto actualTreeSize = skipIdSigHeaders(idsigFd);
@@ -250,9 +262,7 @@ static inline InputDescs openLocalFile(JNIEnv* env, const JniIds& jni, jobject s
});
}
- unique_fd fileFd{env->CallStaticIntMethod(jni.packageManagerShellCommandDataLoader,
- jni.pmscdGetLocalFile, shellCommand,
- env->NewStringUTF(filePath.c_str()))};
+ unique_fd fileFd = openLocalFile(env, jni, shellCommand, filePath);
if (fileFd.ok()) {
result.push_back(InputDesc{
.fd = std::move(fileFd),
@@ -272,6 +282,11 @@ static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shel
std::string(metadata.data, metadata.size));
}
+ if (!shellCommand) {
+ ALOGE("Missing shell command.");
+ return {};
+ }
+
unique_fd fd{env->CallStaticIntMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdGetStdIn, shellCommand)};
if (!fd.ok()) {
@@ -396,10 +411,6 @@ private:
jobject shellCommand = env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdLookupShellCommand,
env->NewStringUTF(mArgs.c_str()));
- if (!shellCommand) {
- ALOGE("Missing shell command.");
- return false;
- }
std::vector<char> buffer;
buffer.reserve(BUFFER_SIZE);
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0f0dff51b782..3e41e9d0eddd 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -38,6 +38,7 @@ int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
+int register_android_server_VibratorManagerService(JNIEnv* env);
int register_android_server_VibratorService(JavaVM* vm, JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
@@ -93,6 +94,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_UsbAlsaJackDetector(env);
register_android_server_UsbHostManager(env);
register_android_server_vr_VrManagerService(env);
+ register_android_server_VibratorManagerService(env);
register_android_server_VibratorService(vm, env);
register_android_server_SystemServer(env);
register_android_server_location_GnssLocationProvider(env);
diff --git a/services/core/xsd/vts/Android.mk b/services/core/xsd/vts/Android.mk
deleted file mode 100644
index 6dc2c437a2f9..000000000000
--- a/services/core/xsd/vts/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := VtsValidateDefaultPermissions
-include test/vts/tools/build/Android.host_config.mk
diff --git a/services/core/xsd/vts/AndroidTest.xml b/services/core/xsd/vts/AndroidTest.xml
deleted file mode 100644
index 4f3b2ef1ace1..000000000000
--- a/services/core/xsd/vts/AndroidTest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.
--->
-<configuration description="Config for VTS VtsValidateDefaultPermissions.">
- <option name="config-descriptor:metadata" key="plan" value="vts-treble" />
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
- <option name="abort-on-push-failure" value="false"/>
- <option name="push-group" value="HostDrivenTest.push"/>
- <option name="push" value="DATA/etc/default-permissions.xsd->/data/local/tmp/default-permissions.xsd"/>
- </target_preparer>
- <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
- <option name="test-module-name" value="VtsValidateDefaultPermissions"/>
- <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_defaultPermissions_validate_test/vts_defaultPermissions_validate_test" />
- <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_defaultPermissions_validate_test/vts_defaultPermissions_validate_test" />
- <option name="binary-test-type" value="gtest"/>
- <option name="test-timeout" value="30s"/>
- </test>
-</configuration>
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
index 5193fa85d238..b6b4d8a04cb6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java
@@ -58,4 +58,12 @@ class CallerIdentity {
@Nullable public ComponentName getComponentName() {
return mComponentName;
}
+
+ public boolean hasAdminComponent() {
+ return mComponentName != null;
+ }
+
+ public boolean hasPackage() {
+ return mPackageName != null;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 183a1495b075..36ff97446349 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -411,37 +411,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private static final int STATUS_BAR_DISABLE2_MASK =
StatusBarManager.DISABLE2_QUICK_SETTINGS;
- private static final Set<String> SECURE_SETTINGS_WHITELIST;
- private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
- private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
+ private static final Set<String> SECURE_SETTINGS_ALLOWLIST;
+ private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST;
+ private static final Set<String> GLOBAL_SETTINGS_ALLOWLIST;
private static final Set<String> GLOBAL_SETTINGS_DEPRECATED;
- private static final Set<String> SYSTEM_SETTINGS_WHITELIST;
+ private static final Set<String> SYSTEM_SETTINGS_ALLOWLIST;
private static final Set<Integer> DA_DISALLOWED_POLICIES;
// A collection of user restrictions that are deprecated and should simply be ignored.
private static final String AB_DEVICE_KEY = "ro.build.ab_update";
static {
- SECURE_SETTINGS_WHITELIST = new ArraySet<>();
- SECURE_SETTINGS_WHITELIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
- SECURE_SETTINGS_WHITELIST.add(Settings.Secure.SKIP_FIRST_USE_HINTS);
- SECURE_SETTINGS_WHITELIST.add(Settings.Secure.INSTALL_NON_MARKET_APPS);
-
- SECURE_SETTINGS_DEVICEOWNER_WHITELIST = new ArraySet<>();
- SECURE_SETTINGS_DEVICEOWNER_WHITELIST.addAll(SECURE_SETTINGS_WHITELIST);
- SECURE_SETTINGS_DEVICEOWNER_WHITELIST.add(Settings.Secure.LOCATION_MODE);
-
- GLOBAL_SETTINGS_WHITELIST = new ArraySet<>();
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_ENABLED);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.ADB_WIFI_ENABLED);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.AUTO_TIME_ZONE);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.DATA_ROAMING);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_SLEEP_POLICY);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.PRIVATE_DNS_MODE);
- GLOBAL_SETTINGS_WHITELIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
+ SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
+ SECURE_SETTINGS_ALLOWLIST.add(Settings.Secure.DEFAULT_INPUT_METHOD);
+ SECURE_SETTINGS_ALLOWLIST.add(Settings.Secure.SKIP_FIRST_USE_HINTS);
+ SECURE_SETTINGS_ALLOWLIST.add(Settings.Secure.INSTALL_NON_MARKET_APPS);
+
+ SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST = new ArraySet<>();
+ SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.addAll(SECURE_SETTINGS_ALLOWLIST);
+ SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.LOCATION_MODE);
+
+ GLOBAL_SETTINGS_ALLOWLIST = new ArraySet<>();
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.ADB_WIFI_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.AUTO_TIME_ZONE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.DATA_ROAMING);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_SLEEP_POLICY);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_MODE);
+ GLOBAL_SETTINGS_ALLOWLIST.add(Settings.Global.PRIVATE_DNS_SPECIFIER);
GLOBAL_SETTINGS_DEPRECATED = new ArraySet<>();
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.BLUETOOTH_ON);
@@ -450,11 +450,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.NETWORK_PREFERENCE);
GLOBAL_SETTINGS_DEPRECATED.add(Settings.Global.WIFI_ON);
- SYSTEM_SETTINGS_WHITELIST = new ArraySet<>();
- SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS);
- SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
- SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_BRIGHTNESS_MODE);
- SYSTEM_SETTINGS_WHITELIST.add(Settings.System.SCREEN_OFF_TIMEOUT);
+ SYSTEM_SETTINGS_ALLOWLIST = new ArraySet<>();
+ SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS);
+ SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS_FLOAT);
+ SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_BRIGHTNESS_MODE);
+ SYSTEM_SETTINGS_ALLOWLIST.add(Settings.System.SCREEN_OFF_TIMEOUT);
DA_DISALLOWED_POLICIES = new ArraySet<>();
DA_DISALLOWED_POLICIES.add(DeviceAdminInfo.USES_POLICY_DISABLE_CAMERA);
@@ -1124,7 +1124,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
boolean storageManagerIsNonDefaultBlockEncrypted() {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return StorageManager.isNonDefaultBlockEncrypted();
} finally {
@@ -1148,10 +1148,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return mContext.getSystemService(WifiManager.class);
}
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
long binderClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
+ @SuppressWarnings("AndroidFrameworkBinderIdentity")
void binderRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
@@ -1567,6 +1569,54 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
/**
+ * Creates a new {@link CallerIdentity} object to represent the caller's identity. If no
+ * component name is provided, look up the component name and fill it in for the caller.
+ */
+ private CallerIdentity getCallerIdentityOptionalAdmin(@Nullable ComponentName adminComponent) {
+ if (adminComponent == null) {
+ ActiveAdmin admin = getActiveAdminOfCaller();
+ if (admin != null) {
+ return getCallerIdentity(admin.info.getComponent());
+ }
+ throw new SecurityException("Caller is not an active admin");
+ } else {
+ return getCallerIdentity(adminComponent);
+ }
+ }
+
+ /**
+ * Creates a new {@link CallerIdentity} object to represent the caller's identity. If no
+ * package name is provided, look up the package name and fill it in for the caller.
+ */
+ private CallerIdentity getCallerIdentityOptionalPackage(@Nullable String callerPackage) {
+ if (callerPackage == null) {
+ ActiveAdmin admin = getActiveAdminOfCaller();
+ if (admin != null) {
+ return getCallerIdentity(admin.info.getPackageName());
+ }
+ throw new SecurityException("Caller is not an active admin");
+ } else {
+ return getCallerIdentity(callerPackage);
+ }
+ }
+
+ /**
+ * Retrieves the active admin of the caller. This method should not be called directly and
+ * should only be called by {@link #getCallerIdentityOptionalAdmin} or
+ * {@link #getCallerIdentityOptionalPackage}.
+ */
+ private ActiveAdmin getActiveAdminOfCaller() {
+ final int callerUid = mInjector.binderGetCallingUid();
+ final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid));
+ for (ActiveAdmin admin : policy.mAdminList) {
+ if (admin.getUid() == callerUid) {
+ return admin;
+ }
+ }
+ return null;
+ }
+
+ /**
* Checks if the device is in COMP mode, and if so migrates it to managed profile on a
* corporate owned device.
*/
@@ -1683,7 +1733,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
- public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
final int status = intent.getIntExtra(
PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
@@ -2081,7 +2131,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who, int userHandle, boolean parent) {
ensureLocked();
if (parent) {
- enforceManagedProfile(userHandle, "call APIs on the parent profile");
+ Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
+ "You can not call APIs on the parent profile outside a managed profile, "
+ + "userId = %d", userHandle));
}
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
if (admin != null && parent) {
@@ -2218,10 +2270,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
throw new SecurityException("Admin " + admin.info.getComponent()
+ " does not own the profile");
}
- if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) {
- throw new SecurityException("Admin " + admin.info.getComponent()
- + " is not the profile owner on organization-owned device");
- }
if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) {
throw new SecurityException("Admin " + admin.info.getComponent()
+ " is not a device owner or profile owner, so may not use policy: "
@@ -2236,8 +2284,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy, boolean parent)
- throws SecurityException {
+ ActiveAdmin getActiveAdminForCallerLocked(@Nullable ComponentName who, int reqPolicy,
+ boolean parent) throws SecurityException {
return getActiveAdminOrCheckPermissionForCallerLocked(
who, reqPolicy, parent, /* permission= */ null);
}
@@ -2251,14 +2299,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Nullable
ActiveAdmin getActiveAdminOrCheckPermissionForCallerLocked(
- ComponentName who,
+ @Nullable ComponentName who,
int reqPolicy,
boolean parent,
@Nullable String permission) throws SecurityException {
ensureLocked();
if (parent) {
- enforceManagedProfile(mInjector.userHandleGetCallingUserId(),
- "call APIs on the parent profile");
+ Preconditions.checkCallingUser(isManagedProfile(getCallerIdentity().getUserId()));
}
ActiveAdmin admin = getActiveAdminOrCheckPermissionForCallerLocked(
who, reqPolicy, permission);
@@ -2333,8 +2380,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
return ownsDevice;
- } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) {
- return ownsDevice || ownsProfileOnOrganizationOwnedDevice;
} else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
// DO always has the PO power.
return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile;
@@ -2852,7 +2897,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(userHandle.getIdentifier());
@@ -3071,7 +3116,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity(adminReceiver);
+ final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -3630,6 +3675,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean addCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ Objects.requireNonNull(admin, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
List<String> changedProviders = null;
@@ -3663,6 +3710,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean removeCrossProfileWidgetProvider(ComponentName admin, String packageName) {
+ Objects.requireNonNull(admin, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
List<String> changedProviders = null;
@@ -3696,6 +3745,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List<String> getCrossProfileWidgetProviders(ComponentName admin) {
+ Objects.requireNonNull(admin, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
@@ -4042,10 +4093,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return true;
}
- final int userId = mInjector.userHandleGetCallingUserId();
- enforceProfileOrDeviceOwner(admin);
- enforceManagedProfile(userId, "query unified challenge status");
- return !isSeparateProfileChallengeEnabled(userId);
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+
+ return !isSeparateProfileChallengeEnabled(caller.getUserId());
}
@Override
@@ -4057,7 +4111,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- enforceManagedProfile(userHandle, "call APIs refering to the parent profile");
+ Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
+ "can not call APIs refering to the parent profile outside a managed profile, "
+ + "userId = %d", userHandle));
synchronized (getLockObject()) {
final int targetUser = getProfileParentId(userHandle);
@@ -4079,7 +4135,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
- enforceNotManagedProfile(userHandle, "check password sufficiency");
+ Preconditions.checkCallAuthorization(!isManagedProfile(userHandle), String.format(
+ "You can not check password sufficiency for a managed profile, userId = %d",
+ userHandle));
enforceUserUnlocked(userHandle);
synchronized (getLockObject()) {
@@ -4635,8 +4693,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature && !hasCallingPermission(permission.LOCK_DEVICE)) {
return;
}
+ final CallerIdentity caller = getCallerIdentity();
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final int callingUserId = caller.getUserId();
ComponentName adminComponent = null;
synchronized (getLockObject()) {
// Make sure the caller has any active admin with the right policy or
@@ -4653,16 +4712,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// For Profile Owners only, callers with only permission not allowed.
if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) {
// Evict key
- enforceManagedProfile(
- callingUserId, "set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY");
+ Preconditions.checkCallingUser(isManagedProfile(callingUserId));
+ Preconditions.checkArgument(!parent,
+ "Cannot set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY for the parent");
if (!isProfileOwner(adminComponent, callingUserId)) {
throw new SecurityException("Only profile owner admins can set "
+ "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY");
}
- if (parent) {
- throw new IllegalArgumentException(
- "Cannot set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY for the parent");
- }
if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
throw new UnsupportedOperationException(
"FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE devices");
@@ -4707,32 +4763,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
- if (who == null) {
- if (!isCallerDelegate(callerPackage, mInjector.binderGetCallingUid(),
- DELEGATION_CERT_INSTALL)) {
- mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
- }
- } else {
- enforceProfileOrDeviceOwner(who);
- }
- }
-
- private void enforceProfileOrDeviceOwner(ComponentName who) {
- synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- }
+ final CallerIdentity caller = getCallerIdentity(who, callerPackage);
+ Preconditions.checkCallAuthorization(canManageCaCerts(caller));
}
- private void enforceNetworkStackOrProfileOrDeviceOwner(ComponentName who) {
- if (hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)) {
- return;
- }
- enforceProfileOrDeviceOwner(who);
+ private boolean canManageCaCerts(CallerIdentity caller) {
+ return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL))
+ || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES);
}
@Override
public boolean approveCaCert(String alias, int userId, boolean approval) {
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
Set<String> certs = getUserData(userId).mAcceptedCaCertificates;
boolean changed = (approval ? certs.add(alias) : certs.remove(alias));
@@ -4747,7 +4791,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isCaCertApproved(String alias, int userId) {
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
return getUserData(userId).mAcceptedCaCertificates.contains(alias);
}
@@ -4772,21 +4817,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
- throws RemoteException {
+ public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer) {
if (!mHasFeature) {
return false;
}
- enforceCanManageCaCerts(admin, callerPackage);
+ final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
+ Preconditions.checkCallAuthorization(canManageCaCerts(caller));
- final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final String alias = mInjector.binderWithCleanCallingIdentity(() -> {
- String installedAlias = mCertificateMonitor.installCaCert(userHandle, certBuffer);
- final boolean isDelegate = (admin == null);
+ String installedAlias = mCertificateMonitor.installCaCert(
+ caller.getUserHandle(), certBuffer);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.INSTALL_CA_CERT)
- .setAdmin(callerPackage)
- .setBoolean(isDelegate)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(/* isDelegate */ admin == null)
.write();
return installedAlias;
});
@@ -4797,8 +4841,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
synchronized (getLockObject()) {
- getUserData(userHandle.getIdentifier()).mOwnerInstalledCaCerts.add(alias);
- saveSettingsLocked(userHandle.getIdentifier());
+ getUserData(caller.getUserId()).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(caller.getUserId());
}
return true;
}
@@ -4808,22 +4852,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- enforceCanManageCaCerts(admin, callerPackage);
+ final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
+ Preconditions.checkCallAuthorization(canManageCaCerts(caller));
- final int userId = mInjector.userHandleGetCallingUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
- mCertificateMonitor.uninstallCaCerts(UserHandle.of(userId), aliases);
- final boolean isDelegate = (admin == null);
+ mCertificateMonitor.uninstallCaCerts(caller.getUserHandle(), aliases);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.UNINSTALL_CA_CERTS)
- .setAdmin(callerPackage)
- .setBoolean(isDelegate)
+ .setAdmin(caller.getPackageName())
+ .setBoolean(/* isDelegate */ admin == null)
.write();
});
synchronized (getLockObject()) {
- if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
- saveSettingsLocked(userId);
+ if (getUserData(caller.getUserId()).mOwnerInstalledCaCerts.removeAll(
+ Arrays.asList(aliases))) {
+ saveSettingsLocked(caller.getUserId());
}
}
}
@@ -4833,8 +4877,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
byte[] cert, byte[] chain, String alias, boolean requestAccess,
boolean isUserSelectable) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
final long id = mInjector.binderClearCallingIdentity();
try {
@@ -4872,8 +4917,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean removeKeyPair(ComponentName who, String callerPackage, String alias) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
final long id = Binder.clearCallingIdentity();
try {
@@ -4908,8 +4954,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkStringNotEmpty(packageName, "Package to grant to cannot be empty");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
final int granteeUid;
try {
@@ -5052,8 +5099,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
enforceCallerCanRequestDeviceIdAttestation(caller);
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
}
// As the caller will be granted access to the key, ensure no UID was specified, as
@@ -5147,8 +5195,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
byte[] cert, byte[] chain, boolean isUserSelectable) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)));
final long id = mInjector.binderClearCallingIdentity();
try (final KeyChainConnection keyChainConnection =
@@ -5611,10 +5660,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Override
public boolean setAlwaysOnVpnPackage(ComponentName who, String vpnPackage, boolean lockdown,
- List<String> lockdownWhitelist)
+ List<String> lockdownAllowlist)
throws SecurityException {
- enforceProfileOrDeviceOwner(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
final int userId = caller.getUserId();
mInjector.binderWithCleanCallingIdentity(() -> {
@@ -5624,10 +5675,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, vpnPackage);
}
- if (vpnPackage != null && lockdown && lockdownWhitelist != null) {
- for (String packageName : lockdownWhitelist) {
+ if (vpnPackage != null && lockdown && lockdownAllowlist != null) {
+ for (String packageName : lockdownAllowlist) {
if (!isPackageInstalledForUser(packageName, userId)) {
- Slog.w(LOG_TAG, "Non-existent package in VPN whitelist: " + packageName);
+ Slog.w(LOG_TAG, "Non-existent package in VPN allowlist: " + packageName);
throw new ServiceSpecificException(
DevicePolicyManager.ERROR_VPN_PACKAGE_NOT_FOUND, packageName);
}
@@ -5635,15 +5686,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
// If some package is uninstalled after the check above, it will be ignored by CM.
if (!mInjector.getConnectivityManager().setAlwaysOnVpnPackageForUser(
- userId, vpnPackage, lockdown, lockdownWhitelist)) {
+ userId, vpnPackage, lockdown, lockdownAllowlist)) {
throw new UnsupportedOperationException();
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_ALWAYS_ON_VPN_PACKAGE)
- .setAdmin(who)
+ .setAdmin(caller.getComponentName())
.setStrings(vpnPackage)
.setBoolean(lockdown)
- .setInt(lockdownWhitelist != null ? lockdownWhitelist.size() : 0)
+ .setInt(lockdownAllowlist != null ? lockdownAllowlist.size() : 0)
.write();
});
synchronized (getLockObject()) {
@@ -5660,11 +5711,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public String getAlwaysOnVpnPackage(ComponentName admin) throws SecurityException {
- enforceProfileOrDeviceOwner(admin);
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(userId));
+ () -> mInjector.getConnectivityManager().getAlwaysOnVpnPackageForUser(
+ caller.getUserId()));
}
@Override
@@ -5678,11 +5732,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isAlwaysOnVpnLockdownEnabled(ComponentName admin) throws SecurityException {
- enforceNetworkStackOrProfileOrDeviceOwner(admin);
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
+ || hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK));
- final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(userId));
+ () -> mInjector.getConnectivityManager().isVpnLockdownEnabled(caller.getUserId()));
}
@Override
@@ -5695,13 +5752,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public List<String> getAlwaysOnVpnLockdownWhitelist(ComponentName admin)
+ public List<String> getAlwaysOnVpnLockdownAllowlist(ComponentName admin)
throws SecurityException {
- enforceProfileOrDeviceOwner(admin);
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- final int userId = mInjector.userHandleGetCallingUserId();
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId));
+ () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(
+ caller.getUserId()));
}
private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) {
@@ -5752,16 +5812,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
final CallerIdentity caller = getCallerIdentity();
boolean calledByProfileOwnerOnOrgOwnedDevice =
- isProfileOwnerOfOrganizationOwnedDevice(caller);
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId());
if (calledOnParentInstance) {
Preconditions.checkCallAuthorization(calledByProfileOwnerOnOrgOwnedDevice,
"Wiping the entire device can only be done by a profile owner on "
+ "organization-owned device.");
}
if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || calledByProfileOwnerOnOrgOwnedDevice,
- "Only device owners or proflie owners of organization-owned device can set "
+ Preconditions.checkCallAuthorization(isCallerDeviceOwner(caller.getUid())
+ || calledByProfileOwnerOnOrgOwnedDevice,
+ "Only device owners or profile owners of organization-owned device can set "
+ "WIPE_RESET_PROTECTION_DATA");
}
@@ -5884,14 +5944,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Preconditions.checkNotNull(who, "ComponentName is null");
+ CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
- final int userId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
- ActiveAdmin admin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
admin.mFactoryResetProtectionPolicy = policy;
- saveSettingsLocked(userId);
+ saveSettingsLocked(caller.getUserId());
}
final Intent intent = new Intent(
@@ -5916,21 +5977,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
- ActiveAdmin admin;
+ final ActiveAdmin admin;
synchronized (getLockObject()) {
if (who == null) {
- if ((frpManagementAgentUid != mInjector.binderGetCallingUid())
- && !hasCallingPermission(permission.MASTER_CLEAR)) {
- throw new SecurityException(
- "Must be called by the FRP management agent on device");
- }
+ Preconditions.checkCallAuthorization(
+ frpManagementAgentUid == mInjector.binderGetCallingUid()
+ || hasCallingPermission(permission.MASTER_CLEAR),
+ "Must be called by the FRP management agent on device");
admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
UserHandle.getUserId(frpManagementAgentUid));
} else {
- admin = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller);
}
}
+
return admin != null ? admin.mFactoryResetProtectionPolicy : null;
}
@@ -5960,9 +6023,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = comp != null
- ? getCallerIdentity(comp)
- : getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentityOptionalAdmin(comp);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
@@ -5990,11 +6051,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
- enforceSystemCaller("report password change");
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(isSystemUid(caller));
// Managed Profile password can only be changed when it has a separate challenge.
if (!isSeparateProfileChallengeEnabled(userId)) {
- enforceNotManagedProfile(userId, "set the active password");
+ Preconditions.checkCallAuthorization(!isManagedProfile(userId), String.format("You can "
+ + "not set the active password for a managed profile, userId = %d", userId));
}
DevicePolicyData policy = getUserData(userId);
@@ -6047,8 +6110,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
Preconditions.checkCallAuthorization(hasCallingOrSelfPermission(BIND_DEVICE_ADMIN));
if (!isSeparateProfileChallengeEnabled(userHandle)) {
- enforceNotManagedProfile(userHandle,
- "report failed password attempt if separate profile challenge is not in place");
+ Preconditions.checkCallAuthorization(!isManagedProfile(userHandle), String.format(
+ "You can not report failed password attempt if separate profile challenge is "
+ + "not in place for a managed profile, userId = %d", userHandle));
}
boolean wipeData = false;
@@ -6399,9 +6463,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = who != null
- ? getCallerIdentity(who)
- : getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentityOptionalAdmin(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -6435,9 +6497,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = callerPackage != null
- ? getCallerIdentity(callerPackage)
- : getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentityOptionalPackage(callerPackage);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
// It's not critical here, but let's make sure the package name is correct, in case
@@ -6546,9 +6606,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
if (parent) {
- Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity(who)));
+ isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
}
synchronized (getLockObject()) {
@@ -6934,9 +6993,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
if (parent) {
- Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity(who)));
+ isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
}
synchronized (getLockObject()) {
@@ -7070,8 +7128,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(packageList, "packageList is null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ || (caller.hasPackage()
+ && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
synchronized (getLockObject()) {
// Get the device owner
@@ -7097,8 +7156,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ || (caller.hasPackage()
+ && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES)));
// TODO In split system user mode, allow apps on user 0 to query the list
synchronized (getLockObject()) {
@@ -7277,7 +7337,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
if (!callingUserOnly) {
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
}
synchronized (getLockObject()) {
if (!mOwners.hasDeviceOwner()) {
@@ -7296,7 +7356,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return UserHandle.USER_NULL;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
return mOwners.hasDeviceOwner() ? mOwners.getDeviceOwnerUserId() : UserHandle.USER_NULL;
}
@@ -7311,7 +7372,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
if (!mOwners.hasDeviceOwner()) {
return null;
@@ -7536,8 +7598,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Objects.requireNonNull(who, "ComponentName is null");
- final int userId = mInjector.userHandleGetCallingUserId();
- enforceNotManagedProfile(userId, "clear profile owner");
+ final CallerIdentity caller = getCallerIdentity(who);
+ final int userId = caller.getUserId();
+ Preconditions.checkCallingUser(!isManagedProfile(userId));
+
enforceUserUnlocked(userId);
synchronized (getLockObject()) {
// Check if this is the profile owner who is calling
@@ -7654,9 +7718,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return DevicePolicyManager.STATE_USER_UNMANAGED;
}
- enforceManageUsers();
- int userHandle = mInjector.userHandleGetCallingUserId();
- return getUserProvisioningState(userHandle);
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
+
+ return getUserProvisioningState(caller.getUserId());
}
private int getUserProvisioningState(int userHandle) {
@@ -7694,8 +7759,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
transitionCheckNeeded = false;
} else {
- // For all other cases, caller must have MANAGE_PROFILE_AND_DEVICE_OWNERS.
- enforceCanManageProfileAndDeviceOwners();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
}
final DevicePolicyData policyData = getUserData(userHandle);
@@ -7750,11 +7815,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(who);
+ final int userId = caller.getUserId();
+ Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller));
+ Preconditions.checkCallingUser(isManagedProfile(userId));
+
synchronized (getLockObject()) {
- // Check if this is the profile owner who is calling
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- final int userId = UserHandle.getCallingUserId();
- enforceManagedProfile(userId, "enable the profile");
// Check if the profile is already enabled.
UserInfo managedProfile = getUserInfo(userId);
if (managedProfile.isEnabled()) {
@@ -7780,14 +7847,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setProfileName(ComponentName who, String profileName) {
Objects.requireNonNull(who, "ComponentName is null");
- enforceProfileOrDeviceOwner(who);
- final int userId = UserHandle.getCallingUserId();
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
mInjector.binderWithCleanCallingIdentity(() -> {
- mUserManager.setUserName(userId, profileName);
+ mUserManager.setUserName(caller.getUserId(), profileName);
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PROFILE_NAME)
- .setAdmin(who)
+ .setAdmin(caller.getComponentName())
.write();
});
}
@@ -7896,7 +7964,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
ComponentName profileOwner = getProfileOwner(userHandle);
if (profileOwner == null) {
return null;
@@ -8068,7 +8137,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
return;
}
- enforceCanManageProfileAndDeviceOwners();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
if ((mIsWatch || hasUserSetupCompleted(userHandle))) {
if (!isCallerWithSystemUid()) {
@@ -8104,7 +8174,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@UserIdInt int userId,
boolean hasIncompatibleAccountsOrNonAdb) {
if (!isAdb()) {
- enforceCanManageProfileAndDeviceOwners();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
}
final int code = checkDeviceOwnerProvisioningPreConditionLocked(
@@ -8158,11 +8229,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void enforceManageUsers() {
- final int callingUid = mInjector.binderGetCallingUid();
- if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
- }
+ private boolean canManageUsers(CallerIdentity caller) {
+ return isSystemUid(caller) || isRootUid(caller)
+ || hasCallingOrSelfPermission(permission.MANAGE_USERS);
}
private boolean hasCallingPermission(String permission) {
@@ -8192,20 +8261,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|| hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS);
}
- private void enforceManagedProfile(int userId, String message) {
- if (!isManagedProfile(userId)) {
- throw new SecurityException(String.format(
- "You can not %s outside a managed profile, userId = %d", message, userId));
- }
- }
-
- private void enforceNotManagedProfile(int userId, String message) {
- if (isManagedProfile(userId)) {
- throw new SecurityException(String.format(
- "You can not %s for a managed profile, userId = %d", message, userId));
- }
- }
-
private void enforceDeviceOwnerOrManageUsers() {
synchronized (getLockObject()) {
if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
@@ -8213,7 +8268,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
}
private void enforceProfileOwnerOrSystemUser() {
@@ -8517,8 +8572,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public void setApplicationRestrictions(ComponentName who, String callerPackage,
String packageName, Bundle settings) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
mInjector.binderWithCleanCallingIdentity(() -> {
mUserManager.setApplicationRestrictions(packageName, settings,
@@ -8558,9 +8614,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(agent, "agent null");
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = admin != null
- ? getCallerIdentity(admin)
- : getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentityOptionalAdmin(admin);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -8828,7 +8882,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return null;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
List<String> result = null;
// If we have multiple profiles we return the intersection of the
@@ -8913,6 +8968,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -8954,6 +9010,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -8965,13 +9022,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List getPermittedInputMethodsForCurrentUser() {
- enforceManageUsers();
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
- final int callingUserId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
List<String> result = null;
// Only device or profile owners can have permitted lists set.
- DevicePolicyData policy = getUserDataUnchecked(callingUserId);
+ DevicePolicyData policy = getUserDataUnchecked(caller.getUserId());
for (int i = 0; i < policy.mAdminList.size(); i++) {
ActiveAdmin admin = policy.mAdminList.get(i);
List<String> fromAdmin = admin.permittedInputMethods;
@@ -8986,8 +9043,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// If we have a permitted list add all system input methods.
if (result != null) {
- List<InputMethodInfo> imes =
- InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
+ List<InputMethodInfo> imes = InputMethodManagerInternal
+ .get().getInputMethodListAsUser(caller.getUserId());
if (imes != null) {
for (InputMethodInfo ime : imes) {
ServiceInfo serviceInfo = ime.getServiceInfo();
@@ -9431,19 +9488,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isEphemeralUser(ComponentName who) {
Objects.requireNonNull(who, "ComponentName is null");
- enforceProfileOrDeviceOwner(who);
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
return mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getUserManager().isUserEphemeral(callingUserId));
+ () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId()));
}
@Override
public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)));
return mInjector.binderWithCleanCallingIdentity(() -> {
Bundle bundle = mUserManager.getApplicationRestrictions(packageName,
@@ -9458,8 +9517,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public String[] setPackagesSuspended(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
String[] result = null;
synchronized (getLockObject()) {
@@ -9489,8 +9549,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
@@ -9650,15 +9711,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean setApplicationHidden(ComponentName who, String callerPackage, String packageName,
boolean hidden, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
boolean result;
synchronized (getLockObject()) {
if (parent) {
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent);
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(
+ caller.getUserId()) && isManagedProfile(caller.getUserId()));
// Ensure the package provided is a system package, this is to ensure that this
// API cannot be used to leak if certain non-system package exists in the person
// profile.
@@ -9682,14 +9744,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean isApplicationHidden(ComponentName who, String callerPackage,
String packageName, boolean parent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
synchronized (getLockObject()) {
if (parent) {
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, parent);
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId())
+ && isManagedProfile(caller.getUserId()));
// Ensure the package provided is a system package.
mInjector.binderWithCleanCallingIdentity(() ->
enforcePackageIsSystemPackage(packageName, userId));
@@ -9716,8 +9780,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void enableSystemApp(ComponentName who, String callerPackage, String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
synchronized (getLockObject()) {
final boolean isDemo = isCurrentUserDemo();
@@ -9759,8 +9824,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP)));
int numberOfAppsInstalled = 0;
synchronized (getLockObject()) {
@@ -9827,8 +9893,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public boolean installExistingPackage(ComponentName who, String callerPackage,
String packageName) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage()
+ && isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE)));
boolean result;
synchronized (getLockObject()) {
@@ -9941,8 +10009,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public void setUninstallBlocked(ComponentName who, String callerPackage, String packageName,
boolean uninstallBlocked) {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL)));
final int userId = caller.getUserId();
synchronized (getLockObject()) {
@@ -10163,6 +10232,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -10186,6 +10256,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
@@ -10208,12 +10279,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) {
- enforceCanSetSecondaryLockscreenEnabled(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ // Check can set secondary lockscreen enabled
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()),
+ String.format("User %d is not allowed to call setSecondaryLockscreenEnabled",
+ caller.getUserId()));
+ // Allow testOnly admins to bypass supervision config requirement.
+ Preconditions.checkCallAuthorization(isAdminTestOnlyLocked(who, caller.getUserId())
+ || isDefaultSupervisor(caller), String.format("Admin %s is not the "
+ + "default supervision component", caller.getComponentName()));
+
synchronized (getLockObject()) {
- final int userId = mInjector.userHandleGetCallingUserId();
- DevicePolicyData policy = getUserData(userId);
+ DevicePolicyData policy = getUserData(caller.getUserId());
policy.mSecondaryLockscreenEnabled = enabled;
- saveSettingsLocked(userId);
+ saveSettingsLocked(caller.getUserId());
}
}
@@ -10224,31 +10306,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void enforceCanSetSecondaryLockscreenEnabled(ComponentName who) {
- enforceProfileOrDeviceOwner(who);
- final int userId = mInjector.userHandleGetCallingUserId();
- if (isManagedProfile(userId)) {
- throw new SecurityException(
- "User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
- }
- synchronized (getLockObject()) {
- if (isAdminTestOnlyLocked(who, userId)) {
- // Allow testOnly admins to bypass supervision config requirement.
- return;
- }
- }
- // Only the default supervision app can use this API.
+ private boolean isDefaultSupervisor(CallerIdentity caller) {
final String supervisor = mContext.getResources().getString(
com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
if (supervisor == null) {
- throw new SecurityException("Unable to set secondary lockscreen setting, no "
- + "default supervision component defined");
+ return false;
}
final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
- if (!who.equals(supervisorComponent)) {
- throw new SecurityException(
- "Admin " + who + " is not the default supervision component");
- }
+ return caller.getComponentName().equals(supervisorComponent);
}
@Override
@@ -10410,7 +10475,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
- if (!GLOBAL_SETTINGS_WHITELIST.contains(setting)
+ if (!GLOBAL_SETTINGS_ALLOWLIST.contains(setting)
&& !UserManager.isDeviceInDemoMode(mContext)) {
throw new SecurityException(String.format(
"Permission denial: device owners cannot update %1$s", setting));
@@ -10438,7 +10503,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (!SYSTEM_SETTINGS_WHITELIST.contains(setting)) {
+ if (!SYSTEM_SETTINGS_ALLOWLIST.contains(setting)) {
throw new SecurityException(String.format(
"Permission denial: device owners cannot update %1$s", setting));
}
@@ -10489,6 +10554,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
@@ -10596,12 +10663,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (isDeviceOwner(who, callingUserId)) {
- if (!SECURE_SETTINGS_DEVICEOWNER_WHITELIST.contains(setting)
+ if (!SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.contains(setting)
&& !isCurrentUserDemo()) {
throw new SecurityException(String.format(
"Permission denial: Device owners cannot update %1$s", setting));
}
- } else if (!SECURE_SETTINGS_WHITELIST.contains(setting) && !isCurrentUserDemo()) {
+ } else if (!SECURE_SETTINGS_ALLOWLIST.contains(setting) && !isCurrentUserDemo()) {
throw new SecurityException(String.format(
"Permission denial: Profile owners cannot update %1$s", setting));
}
@@ -10980,9 +11047,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public boolean isActiveAdminWithPolicy(int uid, int reqPolicy) {
+ public boolean isActiveDeviceOwner(int uid) {
synchronized (getLockObject()) {
- return getActiveAdminWithPolicyForUidLocked(null, reqPolicy, uid) != null;
+ return getActiveAdminWithPolicyForUidLocked(
+ null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, uid) != null;
+ }
+ }
+
+ @Override
+ public boolean isActiveProfileOwner(int uid) {
+ synchronized (getLockObject()) {
+ return getActiveAdminWithPolicyForUidLocked(
+ null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, uid) != null;
}
}
@@ -11106,8 +11182,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid))
- && isActiveAdminWithPolicy(callerUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) {
+ && (isActiveProfileOwner(callerUid)
+ || isActiveDeviceOwner(callerUid))) {
// device owner or a profile owner affiliated with the device owner
return true;
}
@@ -11391,9 +11467,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
policy.validateAgainstPreviousFreezePeriod(record.first, record.second,
LocalDate.now());
}
+ final CallerIdentity caller = getCallerIdentity(who);
+
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isDeviceOwner(caller));
+
if (policy == null) {
mOwners.clearSystemUpdatePolicy();
} else {
@@ -11588,7 +11667,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) {
Objects.requireNonNull(admin, "ComponentName is null");
- enforceProfileOrDeviceOwner(admin);
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
return mOwners.getSystemUpdateInfo();
}
@@ -11596,8 +11677,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
DevicePolicyData userPolicy = getUserData(caller.getUserId());
@@ -11630,8 +11712,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(callback);
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
@@ -11694,9 +11777,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
public int getPermissionGrantState(ComponentName admin, String callerPackage,
String packageName, String permission) throws RemoteException {
final CallerIdentity caller = getCallerIdentity(admin, callerPackage);
- Preconditions.checkCallAuthorization(
- isSystemUid(caller) || isDeviceOwner(caller) || isProfileOwner(caller)
- || isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT));
+ Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDeviceOwner(caller)))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
synchronized (getLockObject()) {
return mInjector.binderWithCleanCallingIdentity(() -> {
@@ -11779,8 +11862,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public int checkProvisioningPreCondition(String action, String packageName) {
- Objects.requireNonNull(packageName);
- enforceCanManageProfileAndDeviceOwners();
+ Objects.requireNonNull(packageName, "packageName is null");
+
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
return checkProvisioningPreConditionSkipPermission(action, packageName);
}
@@ -12041,8 +12127,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isManagedProfile(ComponentName admin) {
- enforceProfileOrDeviceOwner(admin);
- return isManagedProfile(mInjector.userHandleGetCallingUserId());
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
+ return isManagedProfile(caller.getUserId());
}
@Override
@@ -12168,8 +12258,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
- enforceManagedProfile(caller.getUserId(), "set organization color");
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
admin.organizationColor = color;
@@ -12177,7 +12269,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_ORGANIZATION_COLOR)
- .setAdmin(who)
+ .setAdmin(caller.getComponentName())
.write();
}
@@ -12190,9 +12282,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
+ Preconditions.checkCallAuthorization(isManagedProfile(userId), String.format("You can not "
+ + "set organization color outside a managed profile, userId = %d", userId));
- enforceManageUsers();
- enforceManagedProfile(userId, "set organization color");
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
admin.organizationColor = color;
@@ -12206,8 +12299,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return ActiveAdmin.DEF_ORGANIZATION_COLOR;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
- enforceManagedProfile(caller.getUserId(), "get organization color");
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
return admin.organizationColor;
@@ -12223,8 +12318,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format("You can "
+ + "not get organization color outside a managed profile, userId = %d", userHandle));
- enforceManagedProfile(userHandle, "get organization color");
synchronized (getLockObject()) {
ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
return (profileOwner != null)
@@ -12257,8 +12353,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
- enforceManagedProfile(caller.getUserId(), "get organization name");
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+
synchronized (getLockObject()) {
ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
return admin.organizationName;
@@ -12286,8 +12384,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(isManagedProfile(userHandle), String.format(
+ "You can not get organization name outside a managed profile, userId = %d",
+ userHandle));
- enforceManagedProfile(userHandle, "get organization name");
synchronized (getLockObject()) {
ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userHandle);
return (profileOwner != null)
@@ -12384,7 +12484,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void markProfileOwnerOnOrganizationOwnedDevice(ComponentName who, int userId) {
// As the caller is the system, it must specify the component name of the profile owner
- // as a sanity / safety check.
+ // as a safety check.
Objects.requireNonNull(who);
if (!mHasFeature) {
@@ -12420,7 +12520,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@GuardedBy("getLockObject()")
private void markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(
ComponentName who, int userId) {
- // Sanity check: Make sure that the user has a profile owner and that the specified
+ // Make sure that the user has a profile owner and that the specified
// component is the profile owner of that user.
if (!isProfileOwner(who, userId)) {
throw new IllegalArgumentException(String.format(
@@ -12599,10 +12699,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
Objects.requireNonNull(admin);
+ final CallerIdentity caller = getCallerIdentity(admin);
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(admin,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isDeviceOwner(caller));
if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
return;
}
@@ -12630,8 +12731,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
if (!isCallerWithSystemUid()) {
Objects.requireNonNull(admin);
- getActiveAdminForCallerLocked(admin,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller));
}
return mInjector.securityLogGetLoggingEnabledProperty();
}
@@ -12722,16 +12824,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return mSecurityLogMonitor.forceLogs();
}
- private void enforceCanManageDeviceAdmin() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS,
- null);
- }
-
- private void enforceCanManageProfileAndDeviceOwners() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
- }
-
private void enforceCallerSystemUserHandle() {
final int callingUid = mInjector.binderGetCallingUid();
final int userId = UserHandle.getUserId(callingUid);
@@ -12742,9 +12834,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isUninstallInQueue(final String packageName) {
- enforceCanManageDeviceAdmin();
- final int userId = mInjector.userHandleGetCallingUserId();
- Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+
+ Pair<String, Integer> packageUserPair = new Pair<>(packageName, caller.getUserId());
synchronized (getLockObject()) {
return mPackagesToRemove.contains(packageUserPair);
}
@@ -12752,11 +12846,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void uninstallPackageWithActiveAdmins(final String packageName) {
- enforceCanManageDeviceAdmin();
Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
- final int userId = mInjector.userHandleGetCallingUserId();
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+ final int userId = caller.getUserId();
enforceUserUnlocked(userId);
final ComponentName profileOwner = getProfileOwner(userId);
@@ -12805,7 +12901,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isDeviceProvisioned() {
- enforceManageUsers();
+ final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
+
synchronized (getLockObject()) {
return getUserDataUnchecked(UserHandle.USER_SYSTEM).mUserSetupComplete;
}
@@ -12889,7 +12987,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setDeviceProvisioningConfigApplied() {
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
policy.mDeviceProvisioningConfigApplied = true;
@@ -12899,7 +12998,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isDeviceProvisioningConfigApplied() {
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
synchronized (getLockObject()) {
final DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
return policy.mDeviceProvisioningConfigApplied;
@@ -12915,8 +13015,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Override
public void forceUpdateUserSetupComplete() {
- enforceCanManageProfileAndDeviceOwners();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
enforceCallerSystemUserHandle();
+
// no effect if it's called from user build
if (!mInjector.isBuildDebuggable()) {
return;
@@ -12937,25 +13039,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
- Objects.requireNonNull(admin);
- enforceProfileOrDeviceOwner(admin);
- int userId = mInjector.userHandleGetCallingUserId();
- toggleBackupServiceActive(userId, enabled);
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+
+ toggleBackupServiceActive(caller.getUserId(), enabled);
}
@Override
public boolean isBackupServiceEnabled(ComponentName admin) {
- Objects.requireNonNull(admin);
if (!mHasFeature) {
return true;
}
+ Objects.requireNonNull(admin, "ComponentName is null");
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- enforceProfileOrDeviceOwner(admin);
synchronized (getLockObject()) {
try {
IBackupManager ibm = mInjector.getIBackupManager();
- return ibm != null && ibm.isBackupServiceActive(
- mInjector.userHandleGetCallingUserId());
+ return ibm != null && ibm.isBackupServiceActive(caller.getUserId());
} catch (RemoteException e) {
throw new IllegalStateException("Failed requesting backup service state.", e);
}
@@ -13149,8 +13254,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
synchronized (getLockObject()) {
if (enabled == isNetworkLoggingEnabledInternalLocked()) {
@@ -13267,9 +13372,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return false;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)
- || hasCallingOrSelfPermission(permission.MANAGE_USERS));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))
+ || hasCallingOrSelfPermission(permission.MANAGE_USERS));
synchronized (getLockObject()) {
return isNetworkLoggingEnabledInternalLocked();
@@ -13295,8 +13400,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return null;
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- Preconditions.checkCallAuthorization(isDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING));
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller))
+ || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)));
Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked());
synchronized (getLockObject()) {
@@ -13537,13 +13642,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(admin, "ComponentName is null");
Objects.requireNonNull(packageName, "packageName is null");
Objects.requireNonNull(callback, "callback is null");
- enforceProfileOrDeviceOwner(admin);
- final int userId = UserHandle.getCallingUserId();
+
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
long ident = mInjector.binderClearCallingIdentity();
try {
ActivityManager.getService().clearApplicationUserData(packageName, false, callback,
- userId);
+ caller.getUserId());
} catch(RemoteException re) {
// Same process, should not happen.
} catch (SecurityException se) {
@@ -13596,7 +13702,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List<String> getDisallowedSystemApps(ComponentName admin, int userId,
String provisioningAction) throws RemoteException {
- enforceCanManageProfileAndDeviceOwners();
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
return new ArrayList<>(
mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction));
}
@@ -13607,31 +13715,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return;
}
-
- Objects.requireNonNull(admin, "Admin cannot be null.");
+ Objects.requireNonNull(admin, "ComponentName is null");
Objects.requireNonNull(target, "Target cannot be null.");
+ Preconditions.checkArgument(!admin.equals(target),
+ "Provided administrator and target are the same object.");
+ Preconditions.checkArgument(!admin.getPackageName().equals(target.getPackageName()),
+ "Provided administrator and target have the same package name.");
- enforceProfileOrDeviceOwner(admin);
-
- if (admin.equals(target)) {
- throw new IllegalArgumentException("Provided administrator and target are "
- + "the same object.");
- }
-
- if (admin.getPackageName().equals(target.getPackageName())) {
- throw new IllegalArgumentException("Provided administrator and target have "
- + "the same package name.");
- }
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final int callingUserId = caller.getUserId();
final DevicePolicyData policy = getUserData(callingUserId);
final DeviceAdminInfo incomingDeviceInfo = findAdmin(target, callingUserId,
/* throwForMissingPermission= */ true);
checkActiveAdminPrecondition(target, incomingDeviceInfo, policy);
- if (!incomingDeviceInfo.supportsTransferOwnership()) {
- throw new IllegalArgumentException("Provided target does not support "
- + "ownership transfer.");
- }
+ Preconditions.checkArgument(incomingDeviceInfo.supportsTransferOwnership(),
+ "Provided target does not support ownership transfer.");
final long id = mInjector.binderClearCallingIdentity();
String ownerType = null;
@@ -13654,7 +13754,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (bundle == null) {
bundle = new PersistableBundle();
}
- if (isProfileOwner(admin, callingUserId)) {
+ if (isProfileOwner(caller)) {
ownerType = ADMIN_TYPE_PROFILE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_PROFILE_OWNER);
@@ -13665,7 +13765,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (isUserAffiliatedWithDeviceLocked(callingUserId)) {
notifyAffiliatedProfileTransferOwnershipComplete(callingUserId);
}
- } else if (isDeviceOwner(admin, callingUserId)) {
+ } else if (isDeviceOwner(caller)) {
ownerType = ADMIN_TYPE_DEVICE_OWNER;
prepareTransfer(admin, target, bundle, callingUserId,
ADMIN_TYPE_DEVICE_OWNER);
@@ -14335,7 +14435,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return false;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
long id = mInjector.binderClearCallingIdentity();
try {
return isManagedKioskInternal();
@@ -14360,7 +14461,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return false;
}
- enforceManageUsers();
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked());
}
@@ -14494,6 +14596,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public List<String> getUserControlDisabledPackages(ComponentName who) {
+ Objects.requireNonNull(who, "ComponentName is null");
+
final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
@@ -14506,12 +14610,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setCommonCriteriaModeEnabled(ComponentName who, boolean enabled) {
- final int userId = mInjector.userHandleGetCallingUserId();
+ Objects.requireNonNull(who, "Admin component name must be provided");
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Common Criteria mode can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
admin.mCommonCriteriaMode = enabled;
- saveSettingsLocked(userId);
+ saveSettingsLocked(caller.getUserId());
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_COMMON_CRITERIA_MODE)
@@ -14523,9 +14631,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public boolean isCommonCriteriaModeEnabled(ComponentName who) {
if (who != null) {
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Common Criteria mode can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
+
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller);
return admin.mCommonCriteriaMode;
}
}
@@ -14548,9 +14661,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
- false /* parent */);
+ final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller);
final long deadline = admin.mProfileOffDeadline;
final int result = makeSuspensionReasons(admin.mSuspendPersonalApps,
deadline != 0 && mInjector.systemCurrentTimeMillis() > deadline);
@@ -14583,9 +14694,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int callingUserId = caller.getUserId();
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
- false /* parent */);
+ final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller);
boolean shouldSaveSettings = false;
if (admin.mSuspendPersonalApps != suspended) {
admin.mSuspendPersonalApps = suspended;
@@ -14846,9 +14955,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final int userId = caller.getUserId();
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
- false /* parent */);
+ final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller);
// Ensure the timeout is long enough to avoid having bad user experience.
if (timeoutMillis > 0 && timeoutMillis < MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD
@@ -14893,9 +15000,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller));
synchronized (getLockObject()) {
- final ActiveAdmin admin = getActiveAdminForCallerLocked(who,
- DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
- false /* parent */);
+ final ActiveAdmin admin = getProfileOwnerOfCallerLocked(caller);
return admin.mProfileMaximumTimeOffMillis;
}
}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index bf3a8964465b..2f8825b064ce 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -200,16 +200,24 @@ static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams(
return {0, id, nfp};
}
+static std::span<const uint8_t> toSpan(const ::std::optional<::std::vector<uint8_t>>& content) {
+ if (!content) {
+ return {};
+ }
+ return {content->data(), (int)content->size()};
+}
+
binder::Status BinderIncrementalService::makeFile(
int32_t storageId, const std::string& path,
- const ::android::os::incremental::IncrementalNewFileParams& params, int32_t* _aidl_return) {
+ const ::android::os::incremental::IncrementalNewFileParams& params,
+ const ::std::optional<::std::vector<uint8_t>>& content, int32_t* _aidl_return) {
auto [err, fileId, nfp] = toMakeFileParams(params);
if (err) {
*_aidl_return = err;
return ok();
}
- *_aidl_return = mImpl.makeFile(storageId, path, 0777, fileId, nfp);
+ *_aidl_return = mImpl.makeFile(storageId, path, 0777, fileId, nfp, toSpan(content));
return ok();
}
binder::Status BinderIncrementalService::makeFileFromRange(int32_t storageId,
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 123849876095..0a89166f4868 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -58,7 +58,9 @@ public:
binder::Status makeDirectories(int32_t storageId, const std::string& path,
int32_t* _aidl_return) final;
binder::Status makeFile(int32_t storageId, const std::string& path,
- const IncrementalNewFileParams& params, int32_t* _aidl_return) final;
+ const IncrementalNewFileParams& params,
+ const ::std::optional<::std::vector<uint8_t>>& content,
+ int32_t* _aidl_return) final;
binder::Status makeFileFromRange(int32_t storageId, const std::string& targetPath,
const std::string& sourcePath, int64_t start, int64_t end,
int32_t* _aidl_return) final;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 0a1210907487..5f145f33f628 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -860,7 +860,7 @@ std::string IncrementalService::normalizePathToStorage(const IncFsMount& ifs, St
}
int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id,
- incfs::NewFileParams params) {
+ incfs::NewFileParams params, std::span<const uint8_t> data) {
if (auto ifs = getIfs(storage)) {
std::string normPath = normalizePathToStorage(*ifs, storage, path);
if (normPath.empty()) {
@@ -868,11 +868,15 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m
<< " failed to normalize: " << path;
return -EINVAL;
}
- auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params);
- if (err) {
+ if (auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); err) {
LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err;
return err;
}
+ if (!data.empty()) {
+ if (auto err = setFileContent(ifs, id, path, data); err) {
+ return err;
+ }
+ }
return 0;
}
return -EINVAL;
@@ -1464,7 +1468,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_
// Need a shared pointer: will be passing it into all unpacking jobs.
std::shared_ptr<ZipArchive> zipFile(zipFileHandle, [](ZipArchiveHandle h) { CloseArchive(h); });
void* cookie = nullptr;
- const auto libFilePrefix = path::join(constants().libDir, abi);
+ const auto libFilePrefix = path::join(constants().libDir, abi) + "/";
if (StartIteration(zipFile.get(), &cookie, libFilePrefix, constants().libSuffix)) {
LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath;
return false;
@@ -1585,67 +1589,38 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_
void IncrementalService::extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle zipFile,
ZipEntry& entry, const incfs::FileId& libFileId,
- std::string_view targetLibPath,
+ std::string_view debugLibPath,
Clock::time_point scheduledTs) {
if (!ifs) {
- LOG(INFO) << "Skipping zip file " << targetLibPath << " extraction for an expired mount";
+ LOG(INFO) << "Skipping zip file " << debugLibPath << " extraction for an expired mount";
return;
}
- auto libName = path::basename(targetLibPath);
auto startedTs = Clock::now();
// Write extracted data to new file
// NOTE: don't zero-initialize memory, it may take a while for nothing
auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[entry.uncompressed_length]);
if (ExtractToMemory(zipFile, &entry, libData.get(), entry.uncompressed_length)) {
- LOG(ERROR) << "Failed to extract native lib zip entry: " << libName;
+ LOG(ERROR) << "Failed to extract native lib zip entry: " << path::basename(debugLibPath);
return;
}
auto extractFileTs = Clock::now();
- const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
- if (!writeFd.ok()) {
- LOG(ERROR) << "Failed to open write fd for: " << targetLibPath
- << " errno: " << writeFd.get();
- return;
- }
-
- auto openFileTs = Clock::now();
- const int numBlocks =
- (entry.uncompressed_length + constants().blockSize - 1) / constants().blockSize;
- std::vector<IncFsDataBlock> instructions(numBlocks);
- auto remainingData = std::span(libData.get(), entry.uncompressed_length);
- for (int i = 0; i < numBlocks; i++) {
- const auto blockSize = std::min<long>(constants().blockSize, remainingData.size());
- instructions[i] = IncFsDataBlock{
- .fileFd = writeFd.get(),
- .pageIndex = static_cast<IncFsBlockIndex>(i),
- .compression = INCFS_COMPRESSION_KIND_NONE,
- .kind = INCFS_BLOCK_KIND_DATA,
- .dataSize = static_cast<uint32_t>(blockSize),
- .data = reinterpret_cast<const char*>(remainingData.data()),
- };
- remainingData = remainingData.subspan(blockSize);
- }
- auto prepareInstsTs = Clock::now();
-
- size_t res = mIncFs->writeBlocks(instructions);
- if (res != instructions.size()) {
- LOG(ERROR) << "Failed to write data into: " << targetLibPath;
+ if (setFileContent(ifs, libFileId, debugLibPath,
+ std::span(libData.get(), entry.uncompressed_length))) {
return;
}
if (perfLoggingEnabled()) {
auto endFileTs = Clock::now();
- LOG(INFO) << "incfs: Extracted " << libName << "(" << entry.compressed_length << " -> "
- << entry.uncompressed_length << " bytes): " << elapsedMcs(startedTs, endFileTs)
+ LOG(INFO) << "incfs: Extracted " << path::basename(debugLibPath) << "("
+ << entry.compressed_length << " -> " << entry.uncompressed_length
+ << " bytes): " << elapsedMcs(startedTs, endFileTs)
<< "mcs, scheduling delay: " << elapsedMcs(scheduledTs, startedTs)
<< " extract: " << elapsedMcs(startedTs, extractFileTs)
- << " open: " << elapsedMcs(extractFileTs, openFileTs)
- << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs)
- << " write: " << elapsedMcs(prepareInstsTs, endFileTs);
+ << " open/prepare/write: " << elapsedMcs(extractFileTs, endFileTs);
}
}
@@ -1678,6 +1653,55 @@ bool IncrementalService::waitForNativeBinariesExtraction(StorageId storage) {
return mRunning;
}
+int IncrementalService::setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId,
+ std::string_view debugFilePath,
+ std::span<const uint8_t> data) const {
+ auto startTs = Clock::now();
+
+ const auto writeFd = mIncFs->openForSpecialOps(ifs->control, fileId);
+ if (!writeFd.ok()) {
+ LOG(ERROR) << "Failed to open write fd for: " << debugFilePath
+ << " errno: " << writeFd.get();
+ return writeFd.get();
+ }
+
+ const auto dataLength = data.size();
+
+ auto openFileTs = Clock::now();
+ const int numBlocks = (data.size() + constants().blockSize - 1) / constants().blockSize;
+ std::vector<IncFsDataBlock> instructions(numBlocks);
+ for (int i = 0; i < numBlocks; i++) {
+ const auto blockSize = std::min<long>(constants().blockSize, data.size());
+ instructions[i] = IncFsDataBlock{
+ .fileFd = writeFd.get(),
+ .pageIndex = static_cast<IncFsBlockIndex>(i),
+ .compression = INCFS_COMPRESSION_KIND_NONE,
+ .kind = INCFS_BLOCK_KIND_DATA,
+ .dataSize = static_cast<uint32_t>(blockSize),
+ .data = reinterpret_cast<const char*>(data.data()),
+ };
+ data = data.subspan(blockSize);
+ }
+ auto prepareInstsTs = Clock::now();
+
+ size_t res = mIncFs->writeBlocks(instructions);
+ if (res != instructions.size()) {
+ LOG(ERROR) << "Failed to write data into: " << debugFilePath;
+ return res;
+ }
+
+ if (perfLoggingEnabled()) {
+ auto endTs = Clock::now();
+ LOG(INFO) << "incfs: Set file content " << debugFilePath << "(" << dataLength
+ << " bytes): " << elapsedMcs(startTs, endTs)
+ << "mcs, open: " << elapsedMcs(startTs, openFileTs)
+ << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs)
+ << " write: " << elapsedMcs(prepareInstsTs, endTs);
+ }
+
+ return 0;
+}
+
int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storage);
@@ -2111,6 +2135,11 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount
return binder::Status::ok();
}
+binder::Status IncrementalService::DataLoaderStub::reportStreamHealth(MountId mountId,
+ int newStatus) {
+ return binder::Status::ok();
+}
+
bool IncrementalService::DataLoaderStub::isHealthParamsValid() const {
return mHealthCheckParams.blockedTimeoutMs > 0 &&
mHealthCheckParams.blockedTimeoutMs < mHealthCheckParams.unhealthyTimeoutMs;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a49e0f36c9bc..504c02a57b86 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -127,7 +127,7 @@ public:
int setStorageParams(StorageId storage, bool enableReadLogs);
int makeFile(StorageId storage, std::string_view path, int mode, FileId id,
- incfs::NewFileParams params);
+ incfs::NewFileParams params, std::span<const uint8_t> data);
int makeDir(StorageId storage, std::string_view path, int mode = 0755);
int makeDirs(StorageId storage, std::string_view path, int mode = 0755);
@@ -200,6 +200,7 @@ private:
private:
binder::Status onStatusChanged(MountId mount, int newStatus) final;
+ binder::Status reportStreamHealth(MountId mount, int newStatus) final;
sp<content::pm::IDataLoader> getDataLoader();
@@ -349,13 +350,16 @@ private:
int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
+ int setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId,
+ std::string_view debugFilePath, std::span<const uint8_t> data) const;
+
void registerAppOpsCallback(const std::string& packageName);
bool unregisterAppOpsCallback(const std::string& packageName);
void onAppOpChanged(const std::string& packageName);
void runJobProcessing();
void extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle zipFile, ZipEntry& entry,
- const incfs::FileId& libFileId, std::string_view targetLibPath,
+ const incfs::FileId& libFileId, std::string_view debugLibPath,
Clock::time_point scheduledTs);
void runCmdLooper();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 7492d682a6b5..f81d1ef28a1a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,6 +108,7 @@ import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
+import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
@@ -283,6 +284,8 @@ public final class SystemServer {
"com.android.server.autofill.AutofillManagerService";
private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
"com.android.server.contentcapture.ContentCaptureManagerService";
+ private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
+ "com.android.server.musicrecognition.MusicRecognitionManagerService";
private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
"com.android.server.systemcaptions.SystemCaptionsManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
@@ -1263,6 +1266,10 @@ public final class SystemServer {
mSystemServiceManager.startService(AppIntegrityManagerService.class);
t.traceEnd();
+ t.traceBegin("DeviceStateManagerService");
+ mSystemServiceManager.startService(DeviceStateManagerService.class);
+ t.traceEnd();
+
} catch (Throwable e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service");
@@ -1419,6 +1426,17 @@ public final class SystemServer {
t.traceEnd();
}
+ if (deviceHasConfigString(context,
+ R.string.config_defaultMusicRecognitionService)) {
+ t.traceBegin("StartMusicRecognitionManagerService");
+ mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
+ } else {
+ Slog.d(TAG,
+ "MusicRecognitionManagerService not defined by OEM or disabled by flag");
+ }
+
+
startContentCaptureService(context, t);
startAttentionService(context, t);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 2cfdf3fd4f6f..47505a398a6f 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -667,7 +667,7 @@ public class MidiService extends IMidiManager.Stub {
}
// clear calling identity so bindService does not fail
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
client.addDeviceConnection(device, callback);
} finally {
@@ -692,7 +692,7 @@ public class MidiService extends IMidiManager.Stub {
}
// clear calling identity so bindService does not fail
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
client.addDeviceConnection(device, callback);
} finally {
diff --git a/services/musicrecognition/Android.bp b/services/musicrecognition/Android.bp
new file mode 100644
index 000000000000..39b5bb688767
--- /dev/null
+++ b/services/musicrecognition/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+ name: "services.musicsearch-sources",
+ srcs: ["java/**/*.java"],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.musicsearch",
+ defaults: ["services_defaults"],
+ srcs: [":services.musicsearch-sources"],
+ libs: ["services.core", "app-compat-annotations"],
+} \ No newline at end of file
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
new file mode 100644
index 000000000000..e258ef009fb0
--- /dev/null
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerPerUserService.java
@@ -0,0 +1,300 @@
+/*
+ * 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.musicrecognition;
+
+import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_KILLED;
+import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE;
+import static android.media.musicrecognition.MusicRecognitionManager.RecognitionFailureCode;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.media.AudioRecord;
+import android.media.MediaMetadata;
+import android.media.musicrecognition.IMusicRecognitionManagerCallback;
+import android.media.musicrecognition.IMusicRecognitionServiceCallback;
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Handles per-user requests received by
+ * {@link MusicRecognitionManagerService}. Opens an audio stream from the
+ * dsp and writes it into a pipe to {@link RemoteMusicRecognitionService}.
+ */
+public final class MusicRecognitionManagerPerUserService extends
+ AbstractPerUserSystemService<MusicRecognitionManagerPerUserService,
+ MusicRecognitionManagerService>
+ implements RemoteMusicRecognitionService.Callbacks {
+
+ private static final String TAG = MusicRecognitionManagerPerUserService.class.getSimpleName();
+ // Number of bytes per sample of audio (which is a short).
+ private static final int BYTES_PER_SAMPLE = 2;
+ private static final int MAX_STREAMING_SECONDS = 24;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private RemoteMusicRecognitionService mRemoteService;
+
+ private MusicRecognitionServiceCallback mRemoteServiceCallback =
+ new MusicRecognitionServiceCallback();
+ private IMusicRecognitionManagerCallback mCallback;
+
+ MusicRecognitionManagerPerUserService(
+ @NonNull MusicRecognitionManagerService primary,
+ @NonNull Object lock, int userId) {
+ super(primary, lock, userId);
+ }
+
+ @NonNull
+ @GuardedBy("mLock")
+ @Override
+ protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+ throws PackageManager.NameNotFoundException {
+ ServiceInfo si;
+ try {
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ throw new PackageManager.NameNotFoundException(
+ "Could not get service for " + serviceComponent);
+ }
+ if (!Manifest.permission.BIND_MUSIC_RECOGNITION_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "MusicRecognitionService from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_MUSIC_RECOGNITION_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_MUSIC_RECOGNITION_SERVICE);
+ }
+ // TODO(b/158194857): check process which owns the service has RECORD_AUDIO permission. How?
+ return si;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private RemoteMusicRecognitionService ensureRemoteServiceLocked() {
+ if (mRemoteService == null) {
+ final String serviceName = getComponentNameLocked();
+ if (serviceName == null) {
+ if (mMaster.verbose) {
+ Slog.v(TAG, "ensureRemoteServiceLocked(): not set");
+ }
+ return null;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+ mRemoteService = new RemoteMusicRecognitionService(getContext(),
+ serviceComponent, mUserId, this,
+ mRemoteServiceCallback, mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+ }
+
+ return mRemoteService;
+ }
+
+ /**
+ * Read audio from the given capture session using an AudioRecord and writes it to a
+ * ParcelFileDescriptor.
+ */
+ @GuardedBy("mLock")
+ public void beginRecognitionLocked(
+ @NonNull RecognitionRequest recognitionRequest,
+ @NonNull IBinder callback) {
+ int maxAudioLengthSeconds = Math.min(recognitionRequest.getMaxAudioLengthSeconds(),
+ MAX_STREAMING_SECONDS);
+ mCallback = IMusicRecognitionManagerCallback.Stub.asInterface(callback);
+ AudioRecord audioRecord = createAudioRecord(recognitionRequest, maxAudioLengthSeconds);
+
+ mRemoteService = ensureRemoteServiceLocked();
+ if (mRemoteService == null) {
+ try {
+ mCallback.onRecognitionFailed(
+ RECOGNITION_FAILED_SERVICE_UNAVAILABLE);
+ } catch (RemoteException e) {
+ // Ignored.
+ }
+ return;
+ }
+
+ Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
+ if (clientPipe == null) {
+ try {
+ mCallback.onAudioStreamClosed();
+ } catch (RemoteException ignored) {
+ // Ignored.
+ }
+ return;
+ }
+ ParcelFileDescriptor audioSink = clientPipe.second;
+ ParcelFileDescriptor clientRead = clientPipe.first;
+
+ mMaster.mExecutorService.execute(() -> {
+ try (OutputStream fos =
+ new ParcelFileDescriptor.AutoCloseOutputStream(audioSink)) {
+ int halfSecondBufferSize =
+ audioRecord.getBufferSizeInFrames() / maxAudioLengthSeconds;
+ byte[] byteBuffer = new byte[halfSecondBufferSize];
+ int bytesRead = 0;
+ int totalBytesRead = 0;
+ int ignoreBytes =
+ recognitionRequest.getIgnoreBeginningFrames() * BYTES_PER_SAMPLE;
+ audioRecord.startRecording();
+ while (bytesRead >= 0 && totalBytesRead
+ < audioRecord.getBufferSizeInFrames() * BYTES_PER_SAMPLE) {
+ bytesRead = audioRecord.read(byteBuffer, 0, byteBuffer.length);
+ if (bytesRead > 0) {
+ totalBytesRead += bytesRead;
+ // If we are ignoring the first x bytes, update that counter.
+ if (ignoreBytes > 0) {
+ ignoreBytes -= bytesRead;
+ // If we've dipped negative, we've skipped through all ignored bytes
+ // and then some. Write out the bytes we shouldn't have skipped.
+ if (ignoreBytes < 0) {
+ fos.write(byteBuffer, bytesRead + ignoreBytes, -ignoreBytes);
+ }
+ } else {
+ fos.write(byteBuffer);
+ }
+ }
+ }
+ Slog.i(TAG, String.format("Streamed %s bytes from audio record", totalBytesRead));
+ } catch (IOException e) {
+ Slog.e(TAG, "Audio streaming stopped.", e);
+ } finally {
+ audioRecord.release();
+ try {
+ mCallback.onAudioStreamClosed();
+ } catch (RemoteException ignored) {
+ // Ignored.
+ }
+
+ }
+ });
+ // Send the pipe down to the lookup service while we write to it asynchronously.
+ mRemoteService.writeAudioToPipe(clientRead, recognitionRequest.getAudioFormat());
+ }
+
+ /**
+ * Callback invoked by {@link android.service.musicrecognition.MusicRecognitionService} to pass
+ * back the music search result.
+ */
+ private final class MusicRecognitionServiceCallback extends
+ IMusicRecognitionServiceCallback.Stub {
+ @Override
+ public void onRecognitionSucceeded(MediaMetadata result, Bundle extras) {
+ try {
+ sanitizeBundle(extras);
+ mCallback.onRecognitionSucceeded(result, extras);
+ } catch (RemoteException ignored) {
+ // Ignored.
+ }
+ }
+
+ @Override
+ public void onRecognitionFailed(@RecognitionFailureCode int failureCode) {
+ try {
+ mCallback.onRecognitionFailed(failureCode);
+ } catch (RemoteException ignored) {
+ // Ignored.
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDied(@NonNull RemoteMusicRecognitionService service) {
+ try {
+ mCallback.onRecognitionFailed(RECOGNITION_FAILED_SERVICE_KILLED);
+ } catch (RemoteException e) {
+ // Ignored.
+ }
+ Slog.w(TAG, "remote service died: " + service);
+ }
+
+ /** Establishes an audio stream from the DSP audio source. */
+ private static AudioRecord createAudioRecord(
+ @NonNull RecognitionRequest recognitionRequest,
+ int maxAudioLengthSeconds) {
+ int sampleRate = recognitionRequest.getAudioFormat().getSampleRate();
+ int bufferSize = getBufferSizeInBytes(sampleRate, maxAudioLengthSeconds);
+ return new AudioRecord(recognitionRequest.getAudioAttributes(),
+ recognitionRequest.getAudioFormat(), bufferSize,
+ recognitionRequest.getCaptureSession());
+ }
+
+ /**
+ * Returns the number of bytes required to store {@code bufferLengthSeconds} of audio sampled at
+ * {@code sampleRate} Hz, using the format returned by DSP audio capture.
+ */
+ private static int getBufferSizeInBytes(int sampleRate, int bufferLengthSeconds) {
+ return BYTES_PER_SAMPLE * sampleRate * bufferLengthSeconds;
+ }
+
+ private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
+ ParcelFileDescriptor[] fileDescriptors;
+ try {
+ fileDescriptors = ParcelFileDescriptor.createPipe();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to create audio stream pipe", e);
+ return null;
+ }
+
+ if (fileDescriptors.length != 2) {
+ Slog.e(TAG, "Failed to create audio stream pipe, "
+ + "unexpected number of file descriptors");
+ return null;
+ }
+
+ if (!fileDescriptors[0].getFileDescriptor().valid()
+ || !fileDescriptors[1].getFileDescriptor().valid()) {
+ Slog.e(TAG, "Failed to create audio stream pipe, didn't "
+ + "receive a pair of valid file descriptors.");
+ return null;
+ }
+
+ return Pair.create(fileDescriptors[0], fileDescriptors[1]);
+ }
+
+ /** Removes remote objects from the bundle. */
+ private static void sanitizeBundle(@Nullable Bundle bundle) {
+ if (bundle == null) {
+ return;
+ }
+
+ for (String key : bundle.keySet()) {
+ Object o = bundle.get(key);
+
+ if (o instanceof Bundle) {
+ sanitizeBundle((Bundle) o);
+ } else if (o instanceof IBinder || o instanceof ParcelFileDescriptor) {
+ bundle.remove(key);
+ }
+ }
+ }
+}
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java
new file mode 100644
index 000000000000..b4cb33795878
--- /dev/null
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/MusicRecognitionManagerService.java
@@ -0,0 +1,116 @@
+/*
+ * 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.musicrecognition;
+
+import static android.content.PermissionChecker.PERMISSION_GRANTED;
+import static android.media.musicrecognition.MusicRecognitionManager.RECOGNITION_FAILED_SERVICE_UNAVAILABLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.musicrecognition.IMusicRecognitionManager;
+import android.media.musicrecognition.IMusicRecognitionManagerCallback;
+import android.media.musicrecognition.RecognitionRequest;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Service which allows a DSP audio event to be securely streamed to a designated {@link
+ * MusicRecognitionService}.
+ */
+public class MusicRecognitionManagerService extends
+ AbstractMasterSystemService<MusicRecognitionManagerService,
+ MusicRecognitionManagerPerUserService> {
+
+ private static final String TAG = MusicRecognitionManagerService.class.getSimpleName();
+
+ private MusicRecognitionManagerStub mMusicRecognitionManagerStub;
+ final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ /**
+ * Initializes the system service.
+ *
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ *
+ * @param context The system server context.
+ */
+ public MusicRecognitionManagerService(@NonNull Context context) {
+ super(context, new FrameworkResourcesServiceNameResolver(context,
+ com.android.internal.R.string.config_defaultMusicRecognitionService),
+ /** disallowProperty */null);
+ }
+
+ @Nullable
+ @Override
+ protected MusicRecognitionManagerPerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new MusicRecognitionManagerPerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override
+ public void onStart() {
+ mMusicRecognitionManagerStub = new MusicRecognitionManagerStub();
+ publishBinderService(Context.MUSIC_RECOGNITION_SERVICE, mMusicRecognitionManagerStub);
+ }
+
+ private void enforceCaller(String func) {
+ Context ctx = getContext();
+ if (ctx.checkCallingPermission(android.Manifest.permission.MANAGE_MUSIC_RECOGNITION)
+ == PERMISSION_GRANTED) {
+ return;
+ }
+
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " doesn't hold " + android.Manifest.permission.MANAGE_MUSIC_RECOGNITION;
+ throw new SecurityException(msg);
+ }
+
+ final class MusicRecognitionManagerStub extends IMusicRecognitionManager.Stub {
+ @Override
+ public void beginRecognition(
+ @NonNull RecognitionRequest recognitionRequest,
+ @NonNull IBinder callback) {
+ enforceCaller("beginRecognition");
+
+ synchronized (mLock) {
+ final MusicRecognitionManagerPerUserService service = getServiceForUserLocked(
+ UserHandle.getCallingUserId());
+ if (service != null) {
+ service.beginRecognitionLocked(recognitionRequest, callback);
+ } else {
+ try {
+ IMusicRecognitionManagerCallback.Stub.asInterface(callback)
+ .onRecognitionFailed(RECOGNITION_FAILED_SERVICE_UNAVAILABLE);
+ } catch (RemoteException e) {
+ // ignored.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
new file mode 100644
index 000000000000..4814a821d525
--- /dev/null
+++ b/services/musicrecognition/java/com/android/server/musicrecognition/RemoteMusicRecognitionService.java
@@ -0,0 +1,84 @@
+/*
+ * 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.musicrecognition;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.musicrecognition.IMusicRecognitionService;
+import android.media.musicrecognition.IMusicRecognitionServiceCallback;
+import android.media.musicrecognition.MusicRecognitionService;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+/** Remote connection to an instance of {@link MusicRecognitionService}. */
+public class RemoteMusicRecognitionService extends
+ AbstractMultiplePendingRequestsRemoteService<RemoteMusicRecognitionService,
+ IMusicRecognitionService> {
+
+ // Maximum time allotted for the remote service to return a result. Up to 24s of audio plus
+ // time to fingerprint and make rpcs.
+ private static final long TIMEOUT_IDLE_BIND_MILLIS = 40 * DateUtils.SECOND_IN_MILLIS;
+
+ // Allows the remote service to send back a result.
+ private final IMusicRecognitionServiceCallback mServerCallback;
+
+ public RemoteMusicRecognitionService(Context context, ComponentName serviceName,
+ int userId, MusicRecognitionManagerPerUserService perUserService,
+ IMusicRecognitionServiceCallback callback,
+ boolean bindInstantServiceAllowed, boolean verbose) {
+ super(context, MusicRecognitionService.ACTION_MUSIC_SEARCH_LOOKUP, serviceName, userId,
+ perUserService,
+ context.getMainThreadHandler(),
+ // Prevents the service from having its permissions stripped while in background.
+ Context.BIND_INCLUDE_CAPABILITIES | (bindInstantServiceAllowed
+ ? Context.BIND_ALLOW_INSTANT : 0), verbose,
+ /* initialCapacity= */ 1);
+ mServerCallback = callback;
+ }
+
+ @NonNull
+ @Override
+ protected IMusicRecognitionService getServiceInterface(@NonNull IBinder service) {
+ return IMusicRecognitionService.Stub.asInterface(service);
+ }
+
+ @Override
+ protected long getTimeoutIdleBindMillis() {
+ return TIMEOUT_IDLE_BIND_MILLIS;
+ }
+
+ /**
+ * Required, but empty since we don't need to notify the callback implementation of the request
+ * results.
+ */
+ interface Callbacks extends VultureCallback<RemoteMusicRecognitionService> {}
+
+ /**
+ * Sends the given descriptor to the app's {@link MusicRecognitionService} to read the
+ * audio.
+ */
+ public void writeAudioToPipe(@NonNull ParcelFileDescriptor fd,
+ @NonNull AudioFormat audioFormat) {
+ scheduleAsyncRequest(
+ binder -> binder.onAudioStreamStarted(fd, audioFormat, mServerCallback));
+ }
+}
diff --git a/services/net/java/android/net/TcpKeepalivePacketData.java b/services/net/java/android/net/TcpKeepalivePacketData.java
index c0c386b3046e..4875c7cc4263 100644
--- a/services/net/java/android/net/TcpKeepalivePacketData.java
+++ b/services/net/java/android/net/TcpKeepalivePacketData.java
@@ -19,11 +19,12 @@ import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.system.OsConstants;
+import com.android.net.module.util.IpUtils;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index db464e732e91..94bc1ecdc40c 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -87,6 +87,8 @@ public class IpClientManager {
} catch (RemoteException e) {
log("Error confirming IpClient configuration", e);
return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 33317a38853e..91cb481a3b23 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -30,6 +30,7 @@ import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -40,7 +41,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.android.server.people.data.DataManager;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@@ -74,7 +74,7 @@ public class PeopleService extends SystemService {
@Override
public void onStart() {
- publishBinderService(Context.PEOPLE_SERVICE, new BinderService());
+ publishBinderService(Context.PEOPLE_SERVICE, mService);
publishLocalService(PeopleServiceInternal.class, new LocalService());
}
@@ -95,30 +95,59 @@ public class PeopleService extends SystemService {
* @throws SecurityException if the caller is not system or root
*/
private static void enforceSystemOrRoot(String message) {
- int uid = Binder.getCallingUid();
- if (!UserHandle.isSameApp(uid, Process.SYSTEM_UID) && uid != Process.ROOT_UID) {
+ if (!isSystemOrRoot()) {
throw new SecurityException("Only system may " + message);
}
}
- private final class BinderService extends IPeopleManager.Stub {
+ private static boolean isSystemOrRoot() {
+ final int uid = Binder.getCallingUid();
+ return UserHandle.isSameApp(uid, Process.SYSTEM_UID) || uid == Process.ROOT_UID;
+ }
+
+
+ /**
+ * Enforces that only the system, root UID or SystemUI can make certain calls.
+ *
+ * @param message used as message if SecurityException is thrown
+ * @throws SecurityException if the caller is not system or root
+ */
+ private static void enforceSystemRootOrSystemUI(Context context, String message) {
+ if (isSystemOrRoot()) return;
+ context.enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ message);
+ }
+
+ final IBinder mService = new IPeopleManager.Stub() {
@Override
public ParceledListSlice<ConversationChannel> getRecentConversations() {
enforceSystemOrRoot("get recent conversations");
- return new ParceledListSlice<>(new ArrayList<>());
+ return new ParceledListSlice<>(
+ mDataManager.getRecentConversations(
+ Binder.getCallingUserHandle().getIdentifier()));
}
@Override
public void removeRecentConversation(String packageName, int userId, String shortcutId) {
enforceSystemOrRoot("remove a recent conversation");
+ mDataManager.removeRecentConversation(packageName, userId, shortcutId,
+ Binder.getCallingUserHandle().getIdentifier());
}
@Override
public void removeAllRecentConversations() {
enforceSystemOrRoot("remove all recent conversations");
+ mDataManager.removeAllRecentConversations(
+ Binder.getCallingUserHandle().getIdentifier());
}
- }
+
+ @Override
+ public long getLastInteraction(String packageName, int userId, String shortcutId) {
+ enforceSystemRootOrSystemUI(getContext(), "get last interaction");
+ return mDataManager.getLastInteraction(packageName, userId, shortcutId);
+ }
+ };
@VisibleForTesting
final class LocalService extends PeopleServiceInternal {
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 17378285276f..45f389cbd3ff 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -90,6 +90,11 @@ public class ConversationInfo {
@Nullable
private String mNotificationChannelId;
+ @Nullable
+ private String mParentNotificationChannelId;
+
+ private long mLastEventTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -102,6 +107,8 @@ public class ConversationInfo {
mContactUri = builder.mContactUri;
mContactPhoneNumber = builder.mContactPhoneNumber;
mNotificationChannelId = builder.mNotificationChannelId;
+ mParentNotificationChannelId = builder.mParentNotificationChannelId;
+ mLastEventTimestamp = builder.mLastEventTimestamp;
mShortcutFlags = builder.mShortcutFlags;
mConversationFlags = builder.mConversationFlags;
}
@@ -129,14 +136,32 @@ public class ConversationInfo {
}
/**
- * ID of the {@link android.app.NotificationChannel} where the notifications for this
- * conversation are posted.
+ * ID of the conversation-specific {@link android.app.NotificationChannel} where the
+ * notifications for this conversation are posted.
*/
@Nullable
String getNotificationChannelId() {
return mNotificationChannelId;
}
+ /**
+ * ID of the parent {@link android.app.NotificationChannel} for this conversation. This is the
+ * notification channel where the notifications are posted before this conversation is
+ * customized by the user.
+ */
+ @Nullable
+ String getParentNotificationChannelId() {
+ return mParentNotificationChannelId;
+ }
+
+ /**
+ * Timestamp of the last event, {@code 0L} if there are no events. This timestamp is for
+ * identifying and sorting the recent conversations. It may only count a subset of event types.
+ */
+ long getLastEventTimestamp() {
+ return mLastEventTimestamp;
+ }
+
/** Whether the shortcut for this conversation is set long-lived by the app. */
public boolean isShortcutLongLived() {
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -202,6 +227,8 @@ public class ConversationInfo {
&& Objects.equals(mContactUri, other.mContactUri)
&& Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber)
&& Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
+ && Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
+ && Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
&& mShortcutFlags == other.mShortcutFlags
&& mConversationFlags == other.mConversationFlags;
}
@@ -209,7 +236,8 @@ public class ConversationInfo {
@Override
public int hashCode() {
return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
- mNotificationChannelId, mShortcutFlags, mConversationFlags);
+ mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
+ mShortcutFlags, mConversationFlags);
}
@Override
@@ -221,6 +249,8 @@ public class ConversationInfo {
sb.append(", contactUri=").append(mContactUri);
sb.append(", phoneNumber=").append(mContactPhoneNumber);
sb.append(", notificationChannelId=").append(mNotificationChannelId);
+ sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
+ sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
sb.append(" [");
if (isShortcutLongLived()) {
@@ -280,6 +310,11 @@ public class ConversationInfo {
protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
mNotificationChannelId);
}
+ if (mParentNotificationChannelId != null) {
+ protoOutputStream.write(ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID,
+ mParentNotificationChannelId);
+ }
+ protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
if (mContactPhoneNumber != null) {
@@ -300,6 +335,8 @@ public class ConversationInfo {
out.writeInt(mShortcutFlags);
out.writeInt(mConversationFlags);
out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
+ out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
+ out.writeLong(mLastEventTimestamp);
} catch (IOException e) {
Slog.e(TAG, "Failed to write fields to backup payload.", e);
return null;
@@ -338,6 +375,14 @@ public class ConversationInfo {
builder.setNotificationChannelId(protoInputStream.readString(
ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
break;
+ case (int) ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID:
+ builder.setParentNotificationChannelId(protoInputStream.readString(
+ ConversationInfoProto.PARENT_NOTIFICATION_CHANNEL_ID));
+ break;
+ case (int) ConversationInfoProto.LAST_EVENT_TIMESTAMP:
+ builder.setLastEventTimestamp(protoInputStream.readLong(
+ ConversationInfoProto.LAST_EVENT_TIMESTAMP));
+ break;
case (int) ConversationInfoProto.SHORTCUT_FLAGS:
builder.setShortcutFlags(protoInputStream.readInt(
ConversationInfoProto.SHORTCUT_FLAGS));
@@ -382,6 +427,11 @@ public class ConversationInfo {
if (!TextUtils.isEmpty(contactPhoneNumber)) {
builder.setContactPhoneNumber(contactPhoneNumber);
}
+ String parentNotificationChannelId = in.readUTF();
+ if (!TextUtils.isEmpty(parentNotificationChannelId)) {
+ builder.setParentNotificationChannelId(parentNotificationChannelId);
+ }
+ builder.setLastEventTimestamp(in.readLong());
} catch (IOException e) {
Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
return null;
@@ -408,6 +458,11 @@ public class ConversationInfo {
@Nullable
private String mNotificationChannelId;
+ @Nullable
+ private String mParentNotificationChannelId;
+
+ private long mLastEventTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -427,6 +482,8 @@ public class ConversationInfo {
mContactUri = conversationInfo.mContactUri;
mContactPhoneNumber = conversationInfo.mContactPhoneNumber;
mNotificationChannelId = conversationInfo.mNotificationChannelId;
+ mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
+ mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
mShortcutFlags = conversationInfo.mShortcutFlags;
mConversationFlags = conversationInfo.mConversationFlags;
}
@@ -456,6 +513,16 @@ public class ConversationInfo {
return this;
}
+ Builder setParentNotificationChannelId(String parentNotificationChannelId) {
+ mParentNotificationChannelId = parentNotificationChannelId;
+ return this;
+ }
+
+ Builder setLastEventTimestamp(long lastEventTimestamp) {
+ mLastEventTimestamp = lastEventTimestamp;
+ return this;
+ }
+
Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
mShortcutFlags = shortcutFlags;
return this;
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 4d1129567ca6..87f2c581ef8f 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -24,6 +24,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
+import android.app.people.ConversationChannel;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.usage.UsageEvents;
@@ -74,8 +75,11 @@ import com.android.server.notification.ShortcutHelper;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -96,6 +100,7 @@ public class DataManager {
private static final long QUERY_EVENTS_MAX_AGE_MS = 5L * DateUtils.MINUTE_IN_MILLIS;
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
+ @VisibleForTesting static final int MAX_CACHED_RECENT_SHORTCUTS = 30;
private final Context mContext;
private final Injector mInjector;
@@ -208,6 +213,83 @@ public class DataManager {
mContext.getPackageName(), intentFilter, callingUserId);
}
+ /** Returns the cached non-customized recent conversations. */
+ public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
+ List<ConversationChannel> conversationChannels = new ArrayList<>();
+ forPackagesInProfile(callingUserId, packageData -> {
+ String packageName = packageData.getPackageName();
+ int userId = packageData.getUserId();
+ packageData.forAllConversations(conversationInfo -> {
+ if (!isCachedRecentConversation(conversationInfo)) {
+ return;
+ }
+ String shortcutId = conversationInfo.getShortcutId();
+ ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
+ int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ NotificationChannel parentChannel =
+ mNotificationManagerInternal.getNotificationChannel(packageName, uid,
+ conversationInfo.getParentNotificationChannelId());
+ if (shortcutInfo == null || parentChannel == null) {
+ return;
+ }
+ conversationChannels.add(
+ new ConversationChannel(shortcutInfo, parentChannel,
+ conversationInfo.getLastEventTimestamp(),
+ hasActiveNotifications(packageName, userId, shortcutId)));
+ });
+ });
+ return conversationChannels;
+ }
+
+ /**
+ * Uncaches the shortcut that's associated with the specified conversation so this conversation
+ * will not show up in the recent conversations list.
+ */
+ public void removeRecentConversation(String packageName, int userId, String shortcutId,
+ @UserIdInt int callingUserId) {
+ if (!hasActiveNotifications(packageName, userId, shortcutId)) {
+ mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
+ packageName, Collections.singletonList(shortcutId), userId,
+ ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ }
+ }
+
+ /**
+ * Uncaches the shortcuts for all the recent conversations that they don't have active
+ * notifications.
+ */
+ public void removeAllRecentConversations(@UserIdInt int callingUserId) {
+ forPackagesInProfile(callingUserId, packageData -> {
+ String packageName = packageData.getPackageName();
+ int userId = packageData.getUserId();
+ List<String> idsToUncache = new ArrayList<>();
+ packageData.forAllConversations(conversationInfo -> {
+ String shortcutId = conversationInfo.getShortcutId();
+ if (isCachedRecentConversation(conversationInfo)
+ && !hasActiveNotifications(packageName, userId, shortcutId)) {
+ idsToUncache.add(shortcutId);
+ }
+ });
+ mShortcutServiceInternal.uncacheShortcuts(callingUserId, mContext.getPackageName(),
+ packageName, idsToUncache, userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ });
+ }
+
+ /**
+ * Returns the last notification interaction with the specified conversation. If the
+ * conversation can't be found or no interactions have been recorded, returns 0L.
+ */
+ public long getLastInteraction(String packageName, int userId, String shortcutId) {
+ final PackageData packageData = getPackage(packageName, userId);
+ if (packageData != null) {
+ final ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+ if (conversationInfo != null) {
+ return conversationInfo.getLastEventTimestamp();
+ }
+ }
+ return 0L;
+ }
+
/** Reports the sharing related {@link AppTargetEvent} from App Prediction Manager. */
public void reportShareTargetEvent(@NonNull AppTargetEvent event,
@NonNull IntentFilter intentFilter) {
@@ -277,7 +359,6 @@ public class DataManager {
}
pruneUninstalledPackageData(userData);
- final NotificationListener notificationListener = mNotificationListeners.get(userId);
userData.forAllPackages(packageData -> {
if (signal.isCanceled()) {
return;
@@ -290,20 +371,7 @@ public class DataManager {
packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
}
packageData.pruneOrphanEvents();
- if (notificationListener != null) {
- String packageName = packageData.getPackageName();
- packageData.forAllConversations(conversationInfo -> {
- if (conversationInfo.isShortcutCachedForNotification()
- && conversationInfo.getNotificationChannelId() == null
- && !notificationListener.hasActiveNotifications(
- packageName, conversationInfo.getShortcutId())) {
- mShortcutServiceInternal.uncacheShortcuts(userId,
- mContext.getPackageName(), packageName,
- Collections.singletonList(conversationInfo.getShortcutId()),
- userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- }
- });
- }
+ cleanupCachedShortcuts(userId, MAX_CACHED_RECENT_SHORTCUTS);
});
}
@@ -466,7 +534,8 @@ public class DataManager {
@NonNull String packageName, @UserIdInt int userId,
@Nullable List<String> shortcutIds) {
@ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC
- | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
+ | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
+ | ShortcutQuery.FLAG_MATCH_CACHED;
return mShortcutServiceInternal.getShortcuts(
UserHandle.USER_SYSTEM, mContext.getPackageName(),
/*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
@@ -526,6 +595,68 @@ public class DataManager {
return packageData;
}
+ private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
+ return conversationInfo.isShortcutCachedForNotification()
+ && conversationInfo.getNotificationChannelId() == null
+ && conversationInfo.getParentNotificationChannelId() != null
+ && conversationInfo.getLastEventTimestamp() > 0L;
+ }
+
+ private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
+ String shortcutId) {
+ NotificationListener notificationListener = mNotificationListeners.get(userId);
+ return notificationListener != null
+ && notificationListener.hasActiveNotifications(packageName, shortcutId);
+ }
+
+ /**
+ * Cleans up the oldest cached shortcuts that don't have active notifications for the recent
+ * conversations. After the cleanup, normally, the total number of cached shortcuts will be
+ * less than or equal to the target count. However, there are exception cases: e.g. when all
+ * the existing cached shortcuts have active notifications.
+ */
+ private void cleanupCachedShortcuts(@UserIdInt int userId, int targetCachedCount) {
+ UserData userData = getUnlockedUserData(userId);
+ if (userData == null) {
+ return;
+ }
+ // pair of <package name, conversation info>
+ List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
+ userData.forAllPackages(packageData ->
+ packageData.forAllConversations(conversationInfo -> {
+ if (isCachedRecentConversation(conversationInfo)) {
+ cachedConvos.add(
+ Pair.create(packageData.getPackageName(), conversationInfo));
+ }
+ })
+ );
+ if (cachedConvos.size() <= targetCachedCount) {
+ return;
+ }
+ int numToUncache = cachedConvos.size() - targetCachedCount;
+ // Max heap keeps the oldest cached conversations.
+ PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
+ numToUncache + 1,
+ Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
+ pair.second.getLastEventTimestamp()).reversed());
+ for (Pair<String, ConversationInfo> cached : cachedConvos) {
+ if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
+ continue;
+ }
+ maxHeap.offer(cached);
+ if (maxHeap.size() > numToUncache) {
+ maxHeap.poll();
+ }
+ }
+ while (!maxHeap.isEmpty()) {
+ Pair<String, ConversationInfo> toUncache = maxHeap.poll();
+ mShortcutServiceInternal.uncacheShortcuts(userId,
+ mContext.getPackageName(), toUncache.first,
+ Collections.singletonList(toUncache.second.getShortcutId()),
+ userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ }
+ }
+
@VisibleForTesting
@WorkerThread
void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
@@ -736,9 +867,21 @@ public class DataManager {
public void onShortcutsAddedOrUpdated(@NonNull String packageName,
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
mInjector.getBackgroundExecutor().execute(() -> {
+ PackageData packageData = getPackage(packageName, user.getIdentifier());
for (ShortcutInfo shortcut : shortcuts) {
if (ShortcutHelper.isConversationShortcut(
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
+ if (shortcut.isCached()) {
+ ConversationInfo conversationInfo = packageData != null
+ ? packageData.getConversationInfo(shortcut.getId()) : null;
+ if (conversationInfo == null
+ || !conversationInfo.isShortcutCachedForNotification()) {
+ // This is a newly cached shortcut. Clean up the existing cached
+ // shortcuts to ensure the cache size is under the limit.
+ cleanupCachedShortcuts(user.getIdentifier(),
+ MAX_CACHED_RECENT_SHORTCUTS - 1);
+ }
+ }
addOrUpdateConversationInfo(shortcut);
}
}
@@ -757,14 +900,16 @@ public class DataManager {
Slog.e(TAG, "Package not found: " + packageName, e);
}
PackageData packageData = getPackage(packageName, user.getIdentifier());
+ Set<String> shortcutIds = new HashSet<>();
for (ShortcutInfo shortcutInfo : shortcuts) {
if (packageData != null) {
packageData.deleteDataForConversation(shortcutInfo.getId());
}
- if (uid != Process.INVALID_UID) {
- mNotificationManagerInternal.onConversationRemoved(
- shortcutInfo.getPackage(), uid, shortcutInfo.getId());
- }
+ shortcutIds.add(shortcutInfo.getId());
+ }
+ if (uid != Process.INVALID_UID) {
+ mNotificationManagerInternal.onConversationRemoved(
+ packageName, uid, shortcutIds);
}
});
}
@@ -797,6 +942,16 @@ public class DataManager {
});
if (packageData != null) {
+ ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+ if (conversationInfo == null) {
+ return;
+ }
+ ConversationInfo updated = new ConversationInfo.Builder(conversationInfo)
+ .setLastEventTimestamp(sbn.getPostTime())
+ .setParentNotificationChannelId(sbn.getNotification().getChannelId())
+ .build();
+ packageData.getConversationStore().addOrUpdate(updated);
+
EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory(
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
eventHistory.addEvent(new Event(sbn.getPostTime(), Event.TYPE_NOTIFICATION_POSTED));
@@ -817,16 +972,7 @@ public class DataManager {
int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
if (count <= 0) {
mActiveNotifCounts.remove(conversationKey);
- // The shortcut was cached by Notification Manager synchronously when the
- // associated notification was posted. Uncache it here when all the
- // associated notifications are removed.
- if (conversationInfo.isShortcutCachedForNotification()
- && conversationInfo.getNotificationChannelId() == null) {
- mShortcutServiceInternal.uncacheShortcuts(mUserId,
- mContext.getPackageName(), sbn.getPackageName(),
- Collections.singletonList(conversationInfo.getShortcutId()),
- mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- }
+ cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
} else {
mActiveNotifCounts.put(conversationKey, count);
}
@@ -882,24 +1028,6 @@ public class DataManager {
conversationStore.addOrUpdate(builder.build());
}
- synchronized void cleanupCachedShortcuts() {
- for (Pair<String, String> conversationKey : mActiveNotifCounts.keySet()) {
- String packageName = conversationKey.first;
- String shortcutId = conversationKey.second;
- PackageData packageData = getPackage(packageName, mUserId);
- ConversationInfo conversationInfo =
- packageData != null ? packageData.getConversationInfo(shortcutId) : null;
- if (conversationInfo != null
- && conversationInfo.isShortcutCachedForNotification()
- && conversationInfo.getNotificationChannelId() == null) {
- mShortcutServiceInternal.uncacheShortcuts(mUserId,
- mContext.getPackageName(), packageName,
- Collections.singletonList(shortcutId),
- mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- }
- }
- }
-
synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
}
@@ -972,16 +1100,7 @@ public class DataManager {
@Override
public void onReceive(Context context, Intent intent) {
- forAllUnlockedUsers(userData -> {
- NotificationListener listener = mNotificationListeners.get(userData.getUserId());
- // Clean up the cached shortcuts because all the notifications are cleared after
- // system shutdown. The associated shortcuts need to be uncached to keep in sync
- // unless the settings are changed by the user.
- if (listener != null) {
- listener.cleanupCachedShortcuts();
- }
- userData.forAllPackages(PackageData::saveToDisk);
- });
+ forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk));
}
}
diff --git a/services/profcollect/Android.bp b/services/profcollect/Android.bp
index 68fba5508b58..b7be5d452bbd 100644
--- a/services/profcollect/Android.bp
+++ b/services/profcollect/Android.bp
@@ -30,6 +30,7 @@ filegroup {
java_library_static {
name: "services.profcollect",
+ defaults: ["services_defaults"],
srcs: [":services.profcollect-sources"],
libs: ["services.core"],
}
diff --git a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
index 946d28ee3210..62dbd89c48cb 100644
--- a/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
+++ b/services/restrictions/java/com/android/server/restrictions/RestrictionsManagerService.java
@@ -76,7 +76,7 @@ public final class RestrictionsManagerService extends SystemService {
public boolean hasRestrictionsProvider() throws RemoteException {
int userHandle = UserHandle.getCallingUserId();
if (mDpm != null) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mDpm.getRestrictionsProvider(userHandle) != null;
} finally {
@@ -97,7 +97,7 @@ public final class RestrictionsManagerService extends SystemService {
int callingUid = Binder.getCallingUid();
int userHandle = UserHandle.getUserId(callingUid);
if (mDpm != null) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
ComponentName restrictionsProvider =
mDpm.getRestrictionsProvider(userHandle);
@@ -130,7 +130,7 @@ public final class RestrictionsManagerService extends SystemService {
}
final int userHandle = UserHandle.getCallingUserId();
if (mDpm != null) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
ComponentName restrictionsProvider =
mDpm.getRestrictionsProvider(userHandle);
@@ -163,7 +163,7 @@ public final class RestrictionsManagerService extends SystemService {
int callingUid = Binder.getCallingUid();
int userHandle = UserHandle.getUserId(callingUid);
if (mDpm != null) {
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle);
if (permProvider == null) {
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java b/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java
index 5b226f36d565..f58e295b4957 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupAgentTimeoutParametersTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertEquals;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Handler;
+import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -64,7 +65,7 @@ public class BackupAgentTimeoutParametersTest {
long kvBackupAgentTimeoutMillis = mParameters.getKvBackupAgentTimeoutMillis();
long fullBackupAgentTimeoutMillis = mParameters.getFullBackupAgentTimeoutMillis();
long sharedBackupAgentTimeoutMillis = mParameters.getSharedBackupAgentTimeoutMillis();
- long restoreAgentTimeoutMillis = mParameters.getRestoreAgentTimeoutMillis();
+ long restoreSessionTimeoutMillis = mParameters.getRestoreSessionTimeoutMillis();
long restoreAgentFinishedTimeoutMillis = mParameters.getRestoreAgentFinishedTimeoutMillis();
assertEquals(
@@ -77,14 +78,41 @@ public class BackupAgentTimeoutParametersTest {
BackupAgentTimeoutParameters.DEFAULT_SHARED_BACKUP_AGENT_TIMEOUT_MILLIS,
sharedBackupAgentTimeoutMillis);
assertEquals(
- BackupAgentTimeoutParameters.DEFAULT_RESTORE_AGENT_TIMEOUT_MILLIS,
- restoreAgentTimeoutMillis);
+ BackupAgentTimeoutParameters.DEFAULT_RESTORE_SESSION_TIMEOUT_MILLIS,
+ restoreSessionTimeoutMillis);
assertEquals(
BackupAgentTimeoutParameters.DEFAULT_RESTORE_AGENT_FINISHED_TIMEOUT_MILLIS,
restoreAgentFinishedTimeoutMillis);
}
@Test
+ public void
+ testGetRestoreAgentTimeout_afterConstructorWithStartForSystemAgent_returnsDefaultValue() {
+ mParameters.start();
+
+ // Numbers before FIRST_APPLICATION_UID are reserved as UIDs for system components.
+ long restoreTimeout =
+ mParameters.getRestoreAgentTimeoutMillis(Process.FIRST_APPLICATION_UID - 1);
+
+ assertThat(restoreTimeout)
+ .isEqualTo(
+ BackupAgentTimeoutParameters.DEFAULT_RESTORE_SYSTEM_AGENT_TIMEOUT_MILLIS);
+ }
+
+ @Test
+ public void
+ testGetRestoreAgentTimeout_afterConstructorWithStartForAppAgent_returnsDefaultValue() {
+ mParameters.start();
+
+ // Numbers starting from FIRST_APPLICATION_UID are reserved for app UIDs.
+ long restoreTimeout =
+ mParameters.getRestoreAgentTimeoutMillis(Process.FIRST_APPLICATION_UID);
+
+ assertThat(restoreTimeout)
+ .isEqualTo(BackupAgentTimeoutParameters.DEFAULT_RESTORE_AGENT_TIMEOUT_MILLIS);
+ }
+
+ @Test
public void testGetQuotaExceededTimeoutMillis_returnsDefaultValue() {
mParameters.start();
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 09e3bfe9cf20..7d17109af7d3 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -41,6 +41,7 @@ import static com.android.server.backup.testing.Utils.oneTimeIterable;
import static com.android.server.backup.testing.Utils.transferStreamedData;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -2880,8 +2881,8 @@ public class KeyValueBackupTaskTest {
}
private static IterableSubject assertDirectory(Path directory) throws IOException {
- return assertThat(oneTimeIterable(Files.newDirectoryStream(directory).iterator()))
- .named("directory " + directory);
+ return assertWithMessage("directory " + directory).that(
+ oneTimeIterable(Files.newDirectoryStream(directory).iterator()));
}
private static void assertJournalDoesNotContain(
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
index 3fe1f3f90f2f..3114a751d556 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/TestUtils.java
@@ -17,6 +17,7 @@
package com.android.server.backup.testing;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.robolectric.Shadows.shadowOf;
@@ -95,8 +96,8 @@ public class TestUtils {
* logcat before that.
*/
public static void assertLogcatAtMost(String tag, int level) {
- assertThat(ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
- .named("All logs <= " + level)
+ assertWithMessage("All logs <= " + level).that(
+ ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
.isTrue();
}
@@ -105,8 +106,8 @@ public class TestUtils {
* logcat before that.
*/
public static void assertLogcatAtLeast(String tag, int level) {
- assertThat(ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
- .named("Any log >= " + level)
+ assertWithMessage("Any log >= " + level).that(
+ ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
.isTrue();
}
@@ -121,11 +122,10 @@ public class TestUtils {
* that uses logcat before that.
*/
public static void assertLogcat(String tag, int... logs) {
- assertThat(
+ assertWithMessage("Log items (specified per level)").that(
ShadowLog.getLogsForTag(tag).stream()
.map(logItem -> logItem.type)
.collect(toSet()))
- .named("Log items (specified per level)")
.containsExactly(IntStream.of(logs).boxed().toArray());
}
@@ -135,15 +135,13 @@ public class TestUtils {
/** Declare shadow {@link ShadowEventLog} to use this. */
public static void assertEventLogged(int tag, Object... values) {
- assertThat(ShadowEventLog.getEntries())
- .named("Event logs")
+ assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
.contains(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
}
/** Declare shadow {@link ShadowEventLog} to use this. */
public static void assertEventNotLogged(int tag, Object... values) {
- assertThat(ShadowEventLog.getEntries())
- .named("Event logs")
+ assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
.doesNotContain(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
}
diff --git a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
deleted file mode 100644
index 2d0fe5875301..000000000000
--- a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 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.location;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import com.android.internal.util.IndentingPrintWriter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Unit tests for {@link LocationRequestStatistics}.
- */
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class LocationRequestStatisticsTest {
- private static final String FEATURE_ID = "featureId";
-
- /**
- * Check adding and removing requests & strings
- */
- @Test
- public void testRequestSummary() {
- LocationRequestStatistics.RequestSummary summary =
- new LocationRequestStatistics.RequestSummary(
- "com.example", FEATURE_ID, "gps", 1000);
- StringWriter stringWriter = new StringWriter();
- summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriter), " "), 1234);
- assertThat(stringWriter.toString()).startsWith("At");
-
- StringWriter stringWriterRemove = new StringWriter();
- summary = new LocationRequestStatistics.RequestSummary(
- "com.example", "gps", FEATURE_ID,
- LocationRequestStatistics.RequestSummary.REQUEST_ENDED_INTERVAL);
- summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriterRemove), " "), 2345);
- assertThat(stringWriterRemove.toString()).contains("-");
- assertThat(stringWriterRemove.toString()).contains(FEATURE_ID);
- }
-
- /**
- * Check summary list size capping
- */
- @Test
- public void testSummaryList() {
- LocationRequestStatistics statistics = new LocationRequestStatistics();
- statistics.history.addRequest("com.example", FEATURE_ID, "gps", 1000);
- assertThat(statistics.history.mList.size()).isEqualTo(1);
- // Try (not) to overflow
- for (int i = 0; i < LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE; i++) {
- statistics.history.addRequest("com.example", FEATURE_ID, "gps", 1000);
- }
- assertThat(statistics.history.mList.size()).isEqualTo(
- LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE);
- }
-}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index a398961db4c3..182fe9ae9476 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -17,7 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.mockingservicestests">
- <uses-sdk android:targetSdkVersion="30" />
+ <uses-sdk android:targetSdkVersion="31" />
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 120182f09e8c..a5f083477863 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -25,6 +25,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
@@ -47,13 +48,17 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT
import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
import static com.android.server.alarm.AlarmManagerService.WORKING_INDEX;
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
@@ -67,7 +72,6 @@ import android.app.IAlarmCompleteListener;
import android.app.IAlarmListener;
import android.app.PendingIntent;
import android.app.usage.UsageStatsManagerInternal;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
@@ -79,7 +83,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
@@ -100,19 +106,23 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class AlarmManagerServiceTest {
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
- private static final String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
private static final int SYSTEM_UI_UID = 12345;
- private static final int TEST_CALLING_UID = 67890;
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
private long mAppStandbyWindow;
@@ -120,8 +130,6 @@ public class AlarmManagerServiceTest {
private AppStandbyInternal.AppIdleStateChangeListener mAppStandbyListener;
private AlarmManagerService.ChargingReceiver mChargingReceiver;
@Mock
- private ContentResolver mMockResolver;
- @Mock
private Context mMockContext;
@Mock
private IActivityManager mIActivityManager;
@@ -137,6 +145,9 @@ public class AlarmManagerServiceTest {
private AlarmManagerService.ClockReceiver mClockReceiver;
@Mock
private PowerManager.WakeLock mWakeLock;
+ @Mock
+ DeviceConfig.Properties mDeviceConfigProperties;
+ HashSet<String> mDeviceConfigKeys = new HashSet<>();
private MockitoSession mMockingSession;
private Injector mInjector;
@@ -253,6 +264,14 @@ public class AlarmManagerServiceTest {
PowerManager.WakeLock getAlarmWakeLock() {
return mWakeLock;
}
+
+ @Override
+ void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
+ // Do nothing.
+ // The tests become flaky with an error message of
+ // "IllegalStateException: Querying activity state off main thread is not allowed."
+ // when AlarmManager calls DeviceConfig.addOnPropertiesChangedListener().
+ }
}
@Before
@@ -260,6 +279,7 @@ public class AlarmManagerServiceTest {
mMockingSession = mockitoSession()
.initMocks(this)
.spyStatic(ActivityManager.class)
+ .spyStatic(DeviceConfig.class)
.mockStatic(LocalServices.class)
.spyStatic(Looper.class)
.mockStatic(Settings.Global.class)
@@ -283,9 +303,24 @@ public class AlarmManagerServiceTest {
eq(TEST_CALLING_USER), anyLong())).thenReturn(STANDBY_BUCKET_ACTIVE);
doReturn(Looper.getMainLooper()).when(Looper::myLooper);
- when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
- doReturn("min_futurity=0,min_interval=0").when(() ->
- Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS));
+ doReturn(mDeviceConfigKeys).when(mDeviceConfigProperties).getKeyset();
+ when(mDeviceConfigProperties.getLong(anyString(), anyLong()))
+ .thenAnswer((Answer<Long>) invocationOnMock -> {
+ Object[] args = invocationOnMock.getArguments();
+ return (Long) args[1];
+ });
+ when(mDeviceConfigProperties.getInt(anyString(), anyInt()))
+ .thenAnswer((Answer<Integer>) invocationOnMock -> {
+ Object[] args = invocationOnMock.getArguments();
+ return (Integer) args[1];
+ });
+ doAnswer((Answer<Void>) invocationOnMock -> null)
+ .when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(Executor.class),
+ any(DeviceConfig.OnPropertiesChangedListener.class)));
+ doReturn(mDeviceConfigProperties).when(
+ () -> DeviceConfig.getProperties(
+ eq(DeviceConfig.NAMESPACE_ALARM_MANAGER), ArgumentMatchers.<String>any()));
mInjector = new Injector(mMockContext);
mService = new AlarmManagerService(mMockContext, mInjector);
@@ -302,8 +337,6 @@ public class AlarmManagerServiceTest {
// Other boot phases don't matter
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- assertEquals(0, mService.mConstants.MIN_FUTURITY);
- assertEquals(0, mService.mConstants.MIN_INTERVAL);
mAppStandbyWindow = mService.mConstants.APP_STANDBY_WINDOW;
ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> captor =
ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
@@ -321,19 +354,31 @@ public class AlarmManagerServiceTest {
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
- setTestAlarm(type, triggerTime, operation, 0, TEST_CALLING_UID);
+ setTestAlarm(type, triggerTime, operation, 0, AlarmManager.FLAG_STANDALONE,
+ TEST_CALLING_UID);
}
private void setRepeatingTestAlarm(int type, long firstTrigger, long interval,
PendingIntent pi) {
- setTestAlarm(type, firstTrigger, pi, interval, TEST_CALLING_UID);
+ setTestAlarm(type, firstTrigger, pi, interval, AlarmManager.FLAG_STANDALONE,
+ TEST_CALLING_UID);
+ }
+
+ private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) {
+ setTestAlarm(type, triggerTime, pi, 0, AlarmManager.FLAG_IDLE_UNTIL, TEST_CALLING_UID);
+ }
+
+ private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) {
+ // Note: Only alarm clock alarms are allowed to include this flag in the actual service.
+ // But this is a unit test so we'll only test the flag for granularity and convenience.
+ setTestAlarm(type, triggerTime, pi, 0,
+ AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE, TEST_CALLING_UID);
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation, long interval,
- int callingUid) {
- mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval,
- operation, null, "test", AlarmManager.FLAG_STANDALONE, null, null,
- callingUid, TEST_CALLING_PACKAGE);
+ int flags, int callingUid) {
+ mService.setImpl(type, triggerTime, AlarmManager.WINDOW_EXACT, interval, operation, null,
+ "test", flags, null, null, callingUid, TEST_CALLING_PACKAGE);
}
private void setTestAlarmWithListener(int type, long triggerTime, IAlarmListener listener) {
@@ -354,22 +399,27 @@ public class AlarmManagerServiceTest {
return mockPi;
}
+ private void setDeviceConfigLong(String key, long val) {
+ mDeviceConfigKeys.add(key);
+ doReturn(val).when(mDeviceConfigProperties).getLong(eq(key), anyLong());
+ mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
+ }
+
/**
* Lowers quotas to make testing feasible. Careful while calling as this will replace any
* existing settings for the calling test.
*/
private void setTestableQuotas() {
- final StringBuilder constantsBuilder = new StringBuilder();
- constantsBuilder.append(KEY_MIN_FUTURITY);
- constantsBuilder.append("=0,");
- // Capping active and working quotas to make testing feasible.
- constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
- constantsBuilder.append("=8,");
- constantsBuilder.append(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
- constantsBuilder.append("=5,");
- doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
- Settings.Global.ALARM_MANAGER_CONSTANTS));
- mService.mConstants.onChange(false, null);
+ setDeviceConfigLong(KEY_MIN_FUTURITY, 0);
+ setDeviceConfigLong(KEY_MIN_INTERVAL, 0);
+ mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
+ mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
+ doReturn(8).when(mDeviceConfigProperties)
+ .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt());
+ doReturn(5).when(mDeviceConfigProperties)
+ .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]), anyInt());
+
+ mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
}
@Test
@@ -427,25 +477,13 @@ public class AlarmManagerServiceTest {
@Test
public void testUpdateConstants() {
- final StringBuilder constantsBuilder = new StringBuilder();
- constantsBuilder.append(KEY_MIN_FUTURITY);
- constantsBuilder.append("=5,");
- constantsBuilder.append(KEY_MIN_INTERVAL);
- constantsBuilder.append("=10,");
- constantsBuilder.append(KEY_MAX_INTERVAL);
- constantsBuilder.append("=15,");
- constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_SHORT_TIME);
- constantsBuilder.append("=20,");
- constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_LONG_TIME);
- constantsBuilder.append("=25,");
- constantsBuilder.append(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
- constantsBuilder.append("=30,");
- constantsBuilder.append(KEY_LISTENER_TIMEOUT);
- constantsBuilder.append("=35,");
-
- doReturn(constantsBuilder.toString()).when(() -> Settings.Global.getString(mMockResolver,
- Settings.Global.ALARM_MANAGER_CONSTANTS));
- mService.mConstants.onChange(false, null);
+ setDeviceConfigLong(KEY_MIN_FUTURITY, 5);
+ setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
+ setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
+ setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 20);
+ setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, 25);
+ setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
+ setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
assertEquals(5, mService.mConstants.MIN_FUTURITY);
assertEquals(10, mService.mConstants.MIN_INTERVAL);
assertEquals(15, mService.mConstants.MAX_INTERVAL);
@@ -457,9 +495,7 @@ public class AlarmManagerServiceTest {
@Test
public void testMinFuturity() {
- doReturn("min_futurity=10").when(() ->
- Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS));
- mService.mConstants.onChange(false, null);
+ setDeviceConfigLong(KEY_MIN_FUTURITY, 10L);
assertEquals(10, mService.mConstants.MIN_FUTURITY);
final long triggerTime = mNowElapsedTest + 1;
final long expectedTriggerTime = mNowElapsedTest + mService.mConstants.MIN_FUTURITY;
@@ -469,9 +505,7 @@ public class AlarmManagerServiceTest {
@Test
public void testMinFuturityCoreUid() {
- doReturn("min_futurity=10").when(() ->
- Settings.Global.getString(mMockResolver, Settings.Global.ALARM_MANAGER_CONSTANTS));
- mService.mConstants.onChange(false, null);
+ setDeviceConfigLong(KEY_MIN_FUTURITY, 10L);
assertEquals(10, mService.mConstants.MIN_FUTURITY);
final long triggerTime = mNowElapsedTest + 1;
doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));
@@ -769,7 +803,7 @@ public class AlarmManagerServiceTest {
}
@Test
- public void testAlarmRestrictedInBatterSaver() throws Exception {
+ public void testAlarmRestrictedInBatterySaver() throws Exception {
final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
@@ -984,7 +1018,7 @@ public class AlarmManagerServiceTest {
for (int i = 0; i < numAlarms; i++) {
int mockUid = UserHandle.getUid(mockUserId, 1234 + i);
setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 10,
- getNewMockPendingIntent(mockUid), 0, mockUid);
+ getNewMockPendingIntent(mockUid), 0, AlarmManager.FLAG_STANDALONE, mockUid);
}
assertEquals(numAlarms, mService.mAlarmsPerUid.size());
mService.removeUserLocked(mockUserId);
@@ -1124,6 +1158,116 @@ public class AlarmManagerServiceTest {
}
}
+ @Test
+ public void singleIdleUntil() {
+ doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+ final PendingIntent idleUntilPi6 = getNewMockPendingIntent();
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 6, idleUntilPi6);
+
+ assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi6, null));
+ assertEquals(mNowElapsedTest + 6, mTestTimer.getElapsed());
+ assertEquals(mNowElapsedTest + 6, mService.mPendingIdleUntil.getWhenElapsed());
+
+ final PendingIntent idleUntilPi2 = getNewMockPendingIntent();
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, idleUntilPi2);
+
+ // The same mPendingIdleUntil should get updated, even with a different PendingIntent.
+ assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi2, null));
+ assertEquals(mNowElapsedTest + 2, mTestTimer.getElapsed());
+ assertEquals(1, mService.mAlarmStore.size());
+
+ final PendingIntent idleUntilPi10 = getNewMockPendingIntent();
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 10, idleUntilPi10);
+
+ // The same thing should happen even when the new alarm is in farther in the future.
+ assertTrue(mService.mPendingIdleUntil.matches(idleUntilPi10, null));
+ assertEquals(mNowElapsedTest + 10, mTestTimer.getElapsed());
+ assertEquals(1, mService.mAlarmStore.size());
+ }
+
+ @Test
+ public void nextWakeFromIdle() throws Exception {
+ assertNull(mService.mNextWakeFromIdle);
+
+ final PendingIntent wakeFromIdle6 = getNewMockPendingIntent();
+ final long trigger6 = mNowElapsedTest + 6;
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger6, wakeFromIdle6);
+
+ assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+ assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+ assertEquals(trigger6, mTestTimer.getElapsed());
+
+ final PendingIntent wakeFromIdle10 = getNewMockPendingIntent();
+ final long trigger10 = mNowElapsedTest + 10;
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger10, wakeFromIdle10);
+
+ // mNextWakeFromIdle should not get updated.
+ assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+ assertEquals(trigger6, mTestTimer.getElapsed());
+ assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+
+ final PendingIntent wakeFromIdle3 = getNewMockPendingIntent();
+ final long trigger3 = mNowElapsedTest + 3;
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, trigger3, wakeFromIdle3);
+
+ // mNextWakeFromIdle should always reflect the next earliest wake_from_idle alarm.
+ assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle3, null));
+ assertEquals(trigger3, mTestTimer.getElapsed());
+ assertEquals(trigger3, mService.mNextWakeFromIdle.getWhenElapsed());
+
+ mNowElapsedTest = trigger3;
+ mTestTimer.expire();
+
+ assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle6, null));
+ assertEquals(trigger6, mTestTimer.getElapsed());
+ assertEquals(trigger6, mService.mNextWakeFromIdle.getWhenElapsed());
+
+ mService.removeLocked(wakeFromIdle6, null);
+
+ assertTrue(mService.mNextWakeFromIdle.matches(wakeFromIdle10, null));
+ assertEquals(trigger10, mTestTimer.getElapsed());
+ assertEquals(trigger10, mService.mNextWakeFromIdle.getWhenElapsed());
+
+ mService.removeLocked(wakeFromIdle10, null);
+ assertNull(mService.mNextWakeFromIdle);
+ }
+
+ @Test
+ public void idleUntilBeforeWakeFromIdle() {
+ doReturn(0).when(mService).fuzzForDuration(anyLong());
+
+ final PendingIntent idleUntilPi = getNewMockPendingIntent();
+ final long requestedIdleUntil = mNowElapsedTest + 10;
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, requestedIdleUntil, idleUntilPi);
+
+ assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());
+
+ final PendingIntent wakeFromIdle5 = getNewMockPendingIntent();
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 5, wakeFromIdle5);
+ assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+ final PendingIntent wakeFromIdle8 = getNewMockPendingIntent();
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 8, wakeFromIdle8);
+ assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+ final PendingIntent wakeFromIdle12 = getNewMockPendingIntent();
+ setWakeFromIdle(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 12, wakeFromIdle12);
+ assertEquals(mNowElapsedTest + 5, mService.mPendingIdleUntil.getWhenElapsed());
+
+ mService.removeLocked(wakeFromIdle5, null);
+ assertEquals(mNowElapsedTest + 8, mService.mPendingIdleUntil.getWhenElapsed());
+
+ mService.removeLocked(wakeFromIdle8, null);
+ assertEquals(requestedIdleUntil, mService.mPendingIdleUntil.getWhenElapsed());
+
+ mService.removeLocked(idleUntilPi, null);
+ assertNull(mService.mPendingIdleUntil);
+
+ setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 15, idleUntilPi);
+ assertEquals(mNowElapsedTest + 12, mService.mPendingIdleUntil.getWhenElapsed());
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
@@ -1131,4 +1275,12 @@ public class AlarmManagerServiceTest {
}
LocalServices.removeServiceForTest(AlarmManagerInternal.class);
}
+
+ private void dumpAllAlarms(String tag, ArrayList<Alarm> alarms) {
+ System.out.println(tag + ": ");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(new PrintWriter(System.out));
+ AlarmManagerService.dumpAlarmList(ipw, alarms, mNowElapsedTest,
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
+ ipw.close();
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index 9e43b4ab9b5a..f0490ce469dd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -16,6 +16,9 @@
package com.android.server.alarm;
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -35,9 +38,6 @@ import java.util.ArrayList;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class AlarmStoreTest {
- private static final int TEST_CALLING_UID = 12345;
- private static final String TEST_CALLING_PACKAGE = "android.alarm.unit.test";
-
private AlarmStore mAlarmStore;
@Before
@@ -45,22 +45,22 @@ public class AlarmStoreTest {
mAlarmStore = new BatchingAlarmStore(null);
}
- private static Alarm createAlarm(long whenElapsed, long windowLength, PendingIntent mockPi,
+ private static Alarm createAlarm(long whenElapsed, long windowLength,
AlarmManager.AlarmClockInfo alarmClock) {
- return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength, mockPi,
+ return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength,
alarmClock);
}
private static Alarm createWakeupAlarm(long whenElapsed, long windowLength,
- PendingIntent mockPi, AlarmManager.AlarmClockInfo alarmClock) {
- return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, mockPi,
+ AlarmManager.AlarmClockInfo alarmClock) {
+ return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength,
alarmClock);
}
private static Alarm createAlarm(int type, long whenElapsed, long windowLength,
- PendingIntent mockPi, AlarmManager.AlarmClockInfo alarmClock) {
- return new Alarm(type, whenElapsed, whenElapsed, windowLength, whenElapsed + windowLength,
- 0, mockPi, null, null, null, 0, alarmClock, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
+ AlarmManager.AlarmClockInfo alarmClock) {
+ return new Alarm(type, whenElapsed, whenElapsed, windowLength, 0, mock(PendingIntent.class),
+ null, null, null, 0, alarmClock, TEST_CALLING_UID, TEST_CALLING_PACKAGE);
}
private void addAlarmsToStore(Alarm... alarms) {
@@ -71,11 +71,11 @@ public class AlarmStoreTest {
@Test
public void add() {
- final Alarm a1 = createAlarm(1, 0, mock(PendingIntent.class), null);
+ final Alarm a1 = createAlarm(1, 0, null);
mAlarmStore.add(a1);
assertEquals(1, mAlarmStore.size());
- final Alarm a2 = createAlarm(2, 0, mock(PendingIntent.class), null);
+ final Alarm a2 = createAlarm(2, 0, null);
mAlarmStore.add(a2);
assertEquals(2, mAlarmStore.size());
@@ -86,17 +86,17 @@ public class AlarmStoreTest {
@Test
public void remove() {
- final Alarm a1 = createAlarm(1, 0, mock(PendingIntent.class), null);
- final Alarm a2 = createAlarm(2, 0, mock(PendingIntent.class), null);
- final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
+ final Alarm a1 = createAlarm(1, 0, null);
+ final Alarm a2 = createAlarm(2, 0, null);
+ final Alarm a5 = createAlarm(5, 0, null);
addAlarmsToStore(a1, a2, a5);
- ArrayList<Alarm> removed = mAlarmStore.remove(a -> (a.whenElapsed < 4));
+ ArrayList<Alarm> removed = mAlarmStore.remove(a -> (a.getWhenElapsed() < 4));
assertEquals(2, removed.size());
assertEquals(1, mAlarmStore.size());
assertTrue(removed.contains(a1) && removed.contains(a2));
- final Alarm a8 = createAlarm(8, 0, mock(PendingIntent.class), null);
+ final Alarm a8 = createAlarm(8, 0, null);
addAlarmsToStore(a8, a2, a1);
removed = mAlarmStore.remove(unused -> false);
@@ -110,10 +110,10 @@ public class AlarmStoreTest {
@Test
public void removePendingAlarms() {
- final Alarm a1_11 = createAlarm(1, 10, mock(PendingIntent.class), null);
- final Alarm a2_5 = createAlarm(2, 3, mock(PendingIntent.class), null);
- final Alarm a6_9 = createAlarm(6, 3, mock(PendingIntent.class), null);
- addAlarmsToStore(a2_5, a6_9, a1_11);
+ final Alarm a1to11 = createAlarm(1, 10, null);
+ final Alarm a2to5 = createAlarm(2, 3, null);
+ final Alarm a6to9 = createAlarm(6, 3, null);
+ addAlarmsToStore(a2to5, a6to9, a1to11);
final ArrayList<Alarm> pendingAt0 = mAlarmStore.removePendingAlarms(0);
assertEquals(0, pendingAt0.size());
@@ -121,24 +121,24 @@ public class AlarmStoreTest {
final ArrayList<Alarm> pendingAt3 = mAlarmStore.removePendingAlarms(3);
assertEquals(2, pendingAt3.size());
- assertTrue(pendingAt3.contains(a1_11) && pendingAt3.contains(a2_5));
+ assertTrue(pendingAt3.contains(a1to11) && pendingAt3.contains(a2to5));
assertEquals(1, mAlarmStore.size());
- addAlarmsToStore(a2_5, a1_11);
+ addAlarmsToStore(a2to5, a1to11);
final ArrayList<Alarm> pendingAt7 = mAlarmStore.removePendingAlarms(7);
assertEquals(3, pendingAt7.size());
- assertTrue(pendingAt7.contains(a1_11) && pendingAt7.contains(a2_5) && pendingAt7.contains(
- a6_9));
+ assertTrue(pendingAt7.contains(a1to11) && pendingAt7.contains(a2to5) && pendingAt7.contains(
+ a6to9));
assertEquals(0, mAlarmStore.size());
}
@Test
public void getNextWakeupDeliveryTime() {
- final Alarm a1_10 = createAlarm(1, 9, mock(PendingIntent.class), null);
- final Alarm a3_8_wakeup = createWakeupAlarm(3, 5, mock(PendingIntent.class), null);
- final Alarm a6_wakeup = createWakeupAlarm(6, 0, mock(PendingIntent.class), null);
- final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
- addAlarmsToStore(a5, a6_wakeup, a3_8_wakeup, a1_10);
+ final Alarm a1to10 = createAlarm(1, 9, null);
+ final Alarm a3to8wakeup = createWakeupAlarm(3, 5, null);
+ final Alarm a6wakeup = createWakeupAlarm(6, 0, null);
+ final Alarm a5 = createAlarm(5, 0, null);
+ addAlarmsToStore(a5, a6wakeup, a3to8wakeup, a1to10);
// The wakeup alarms are [6] and [3, 8], hence 6 is the latest time till when we can
// defer delivering any wakeup alarm.
@@ -155,11 +155,11 @@ public class AlarmStoreTest {
@Test
public void getNextDeliveryTime() {
- final Alarm a1_10 = createAlarm(1, 9, mock(PendingIntent.class), null);
- final Alarm a3_8_wakeup = createWakeupAlarm(3, 5, mock(PendingIntent.class), null);
- final Alarm a6_wakeup = createWakeupAlarm(6, 0, mock(PendingIntent.class), null);
- final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
- addAlarmsToStore(a5, a6_wakeup, a3_8_wakeup, a1_10);
+ final Alarm a1to10 = createAlarm(1, 9, null);
+ final Alarm a3to8wakeup = createWakeupAlarm(3, 5, null);
+ final Alarm a6wakeup = createWakeupAlarm(6, 0, null);
+ final Alarm a5 = createAlarm(5, 0, null);
+ addAlarmsToStore(a5, a6wakeup, a3to8wakeup, a1to10);
assertTrue(mAlarmStore.getNextDeliveryTime() <= 5);
@@ -168,24 +168,22 @@ public class AlarmStoreTest {
}
@Test
- public void recalculateAlarmDeliveries() {
- final Alarm a5 = createAlarm(5, 0, mock(PendingIntent.class), null);
- final Alarm a8 = createAlarm(8, 0, mock(PendingIntent.class), null);
- final Alarm a10 = createAlarm(10, 0, mock(PendingIntent.class), null);
+ public void updateAlarmDeliveries() {
+ final Alarm a5 = createAlarm(5, 0, null);
+ final Alarm a8 = createAlarm(8, 0, null);
+ final Alarm a10 = createAlarm(10, 0, null);
addAlarmsToStore(a8, a10, a5);
assertEquals(5, mAlarmStore.getNextDeliveryTime());
- mAlarmStore.recalculateAlarmDeliveries(a -> {
- a.whenElapsed += 3;
- a.maxWhenElapsed = a.whenElapsed;
+ mAlarmStore.updateAlarmDeliveries(a -> {
+ a.setPolicyElapsed(Alarm.REQUESTER_POLICY_INDEX, a.getWhenElapsed() + 3);
return true;
});
assertEquals(8, mAlarmStore.getNextDeliveryTime());
- mAlarmStore.recalculateAlarmDeliveries(a -> {
- a.whenElapsed = 20 - a.whenElapsed;
- a.maxWhenElapsed = a.whenElapsed;
+ mAlarmStore.updateAlarmDeliveries(a -> {
+ a.setPolicyElapsed(Alarm.REQUESTER_POLICY_INDEX, 20 - a.getWhenElapsed());
return true;
});
assertEquals(7, mAlarmStore.getNextDeliveryTime());
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
new file mode 100644
index 000000000000..efcfae38c3d3
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.alarm;
+
+import static android.app.AlarmManager.ELAPSED_REALTIME;
+
+import static com.android.server.alarm.Alarm.APP_STANDBY_POLICY_INDEX;
+import static com.android.server.alarm.Alarm.REQUESTER_POLICY_INDEX;
+import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
+import static com.android.server.alarm.Constants.TEST_CALLING_UID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.app.PendingIntent;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AlarmTest {
+
+ private Alarm createDefaultAlarm(long requestedElapsed, long windowLength) {
+ return new Alarm(ELAPSED_REALTIME, 0, requestedElapsed, windowLength, 0,
+ mock(PendingIntent.class), null, null, null, 0, null, TEST_CALLING_UID,
+ TEST_CALLING_PACKAGE);
+ }
+
+ @Test
+ public void initSetsOnlyRequesterPolicy() {
+ final Alarm a = createDefaultAlarm(4567, 2);
+ assertEquals(4567, a.getPolicyElapsed(REQUESTER_POLICY_INDEX));
+ assertEquals(0, a.getPolicyElapsed(APP_STANDBY_POLICY_INDEX));
+ }
+
+ @Test
+ public void whenElapsed() {
+ final Alarm a = createDefaultAlarm(0, 0);
+
+ a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4);
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10);
+ assertEquals(10, a.getWhenElapsed());
+
+ a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 12);
+ assertEquals(12, a.getWhenElapsed());
+
+ a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 7);
+ assertEquals(10, a.getWhenElapsed());
+
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 2);
+ assertEquals(7, a.getWhenElapsed());
+
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 7);
+ assertEquals(7, a.getWhenElapsed());
+ }
+
+ @Test
+ public void maxWhenElapsed() {
+ final Alarm a = createDefaultAlarm(10, 12);
+ assertEquals(22, a.getMaxWhenElapsed());
+
+ a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 15);
+ assertEquals(27, a.getMaxWhenElapsed());
+
+ a.setPolicyElapsed(REQUESTER_POLICY_INDEX, 2);
+ assertEquals(14, a.getMaxWhenElapsed());
+
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 5);
+ assertEquals(14, a.getMaxWhenElapsed());
+
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 16);
+ assertEquals(16, a.getMaxWhenElapsed());
+
+ a.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 12);
+ assertEquals(14, a.getMaxWhenElapsed());
+ }
+
+ @Test
+ public void setPolicyElapsed() {
+ final Alarm exactAlarm = createDefaultAlarm(10, 0);
+
+ assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
+ assertTrue(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
+
+ assertFalse(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 8));
+ assertFalse(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 10));
+ assertFalse(exactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
+
+ assertTrue(exactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 7));
+
+ final Alarm inexactAlarm = createDefaultAlarm(10, 5);
+
+ assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 4));
+ assertTrue(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 10));
+
+ // whenElapsed won't change, but maxWhenElapsed will.
+ assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 8));
+ assertTrue(inexactAlarm.setPolicyElapsed(REQUESTER_POLICY_INDEX, 10));
+
+ assertFalse(inexactAlarm.setPolicyElapsed(APP_STANDBY_POLICY_INDEX, 8));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
index 6465739f6822..5bb6a42b2604 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/BackgroundRestrictedAlarmsTest.java
@@ -45,7 +45,7 @@ public class BackgroundRestrictedAlarmsTest {
}
uidAlarms.add(new Alarm(
removeIt ? RTC : RTC_WAKEUP,
- 0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+ 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
return all;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java b/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java
new file mode 100644
index 000000000000..2552db8a310a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/Constants.java
@@ -0,0 +1,22 @@
+/*
+ * 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.alarm;
+
+public interface Constants {
+ String TEST_CALLING_PACKAGE = "com.android.framework.test-package";
+ int TEST_CALLING_UID = 67890;
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
index 04e8b634e72e..b85da9460476 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
@@ -34,12 +34,12 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
-import com.android.server.am.ActivityManagerService.Injector;
import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -55,7 +55,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
+import java.io.ByteArrayInputStream;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
@Presubmit
public class AppChildProcessTest {
@@ -68,6 +72,7 @@ public class AppChildProcessTest {
private Context mContext = getInstrumentation().getTargetContext();
private TestInjector mInjector;
+ private PhantomTestInjector mPhantomInjector;
private ActivityManagerService mAms;
private ProcessList mProcessList;
private PhantomProcessList mPhantomProcessList;
@@ -94,6 +99,7 @@ public class AppChildProcessTest {
mProcessList = spy(pList);
mInjector = new TestInjector(mContext);
+ mPhantomInjector = new PhantomTestInjector();
mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
@@ -101,6 +107,7 @@ public class AppChildProcessTest {
mAms.mPackageManagerInt = mPackageManagerInt;
pList.mService = mAms;
mPhantomProcessList = mAms.mPhantomProcessList;
+ mPhantomProcessList.mInjector = mPhantomInjector;
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
doReturn(false).when(() -> Process.supportsPidFd());
// Remove stale instance of PackageManagerInternal if there is any
@@ -136,47 +143,60 @@ public class AppChildProcessTest {
final String child2ProcessName = "test1_child1_child2";
final String nativeProcessName = "test_native";
- makeParent(zygote64Pid, initPid);
- makeParent(zygote32Pid, initPid);
+ makeProcess(rootUid, zygote64Pid, zygote64ProcessName);
+ makeParent(rootUid, zygote64Pid, initPid);
+ makeProcess(rootUid, zygote32Pid, zygote32ProcessName);
+ makeParent(rootUid, zygote32Pid, initPid);
makeAppProcess(app1Pid, app1Uid, app1ProcessName, app1ProcessName);
- makeParent(app1Pid, zygote64Pid);
makeAppProcess(app2Pid, app2Uid, app2ProcessName, app2ProcessName);
- makeParent(app2Pid, zygote64Pid);
+
+ mPhantomProcessList.lookForPhantomProcessesLocked();
assertEquals(0, mPhantomProcessList.mPhantomProcesses.size());
// Verify zygote itself isn't a phantom process
assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
- zygote64ProcessName, rootUid, zygote64Pid));
+ zygote64ProcessName, rootUid, zygote64Pid, false));
assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
- zygote32ProcessName, rootUid, zygote32Pid));
+ zygote32ProcessName, rootUid, zygote32Pid, false));
// Verify none of the app isn't a phantom process
assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
- app1ProcessName, app1Uid, app1Pid));
+ app1ProcessName, app1Uid, app1Pid, false));
assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
- app2ProcessName, app2Uid, app2Pid));
+ app2ProcessName, app2Uid, app2Pid, false));
// "Fork" an app child process
- makeParent(child1Pid, app1Pid);
+ makeProcess(app1Uid, child1Pid, child1ProcessName);
+ makeParent(app1Uid, child1Pid, app1Pid);
+ mPhantomProcessList.lookForPhantomProcessesLocked();
+
PhantomProcessRecord pr = mPhantomProcessList
- .getOrCreatePhantomProcessIfNeededLocked(child1ProcessName, app1Uid, child1Pid);
+ .getOrCreatePhantomProcessIfNeededLocked(
+ child1ProcessName, app1Uid, child1Pid, true);
assertTrue(pr != null);
assertEquals(1, mPhantomProcessList.mPhantomProcesses.size());
assertEquals(pr, mPhantomProcessList.mPhantomProcesses.valueAt(0));
verifyPhantomProcessRecord(pr, child1ProcessName, app1Uid, child1Pid);
// Create another native process from init
- makeParent(nativePid, initPid);
+ makeProcess(rootUid, nativePid, nativeProcessName);
+ makeParent(rootUid, nativePid, initPid);
+ mPhantomProcessList.lookForPhantomProcessesLocked();
+
assertEquals(null, mPhantomProcessList.getOrCreatePhantomProcessIfNeededLocked(
- nativeProcessName, rootUid, nativePid));
+ nativeProcessName, rootUid, nativePid, false));
assertEquals(1, mPhantomProcessList.mPhantomProcesses.size());
assertEquals(pr, mPhantomProcessList.mPhantomProcesses.valueAt(0));
// "Fork" another app child process
- makeParent(child2Pid, child1Pid);
+ makeProcess(app1Uid, child2Pid, child2ProcessName);
+ makeParent(app1Uid, child2Pid, app1Pid);
+ mPhantomProcessList.lookForPhantomProcessesLocked();
+
PhantomProcessRecord pr2 = mPhantomProcessList
- .getOrCreatePhantomProcessIfNeededLocked(child2ProcessName, app1Uid, child2Pid);
+ .getOrCreatePhantomProcessIfNeededLocked(
+ child2ProcessName, app1Uid, child2Pid, false);
assertTrue(pr2 != null);
assertEquals(2, mPhantomProcessList.mPhantomProcesses.size());
verifyPhantomProcessRecord(pr2, child2ProcessName, app1Uid, child2Pid);
@@ -197,19 +217,27 @@ public class AppChildProcessTest {
assertEquals(pid, pr.mPid);
}
+ private void makeProcess(int uid, int pid, String processName) {
+ doReturn(uid).when(() -> Process.getUidForPid(eq(pid)));
+ mPhantomInjector.mPidToName.put(pid, processName);
+ }
+
private void makeAppProcess(int pid, int uid, String packageName, String processName) {
+ makeProcess(uid, pid, processName);
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
+ ai.uid = uid;
ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
app.pid = pid;
mAms.mPidsSelfLocked.doAddInternal(app);
+ mPhantomInjector.addToProcess(uid, pid, pid);
}
- private void makeParent(int pid, int ppid) {
- doReturn(ppid).when(() -> Process.getParentPid(eq(pid)));
+ private void makeParent(int uid, int pid, int ppid) {
+ mPhantomInjector.addToProcess(uid, ppid, pid);
}
- private class TestInjector extends Injector {
+ private class TestInjector extends ActivityManagerService.Injector {
TestInjector(Context context) {
super(context);
}
@@ -230,6 +258,56 @@ public class AppChildProcessTest {
}
}
+ private class PhantomTestInjector extends PhantomProcessList.Injector {
+ ArrayMap<String, InputStream> mPathToInput = new ArrayMap<>();
+ ArrayMap<String, StringBuffer> mPathToData = new ArrayMap<>();
+ ArrayMap<InputStream, StringBuffer> mInputToData = new ArrayMap<>();
+ ArrayMap<Integer, String> mPidToName = new ArrayMap<>();
+
+ @Override
+ InputStream openCgroupProcs(String path) throws FileNotFoundException, SecurityException {
+ InputStream input = mPathToInput.get(path);
+ if (input != null) {
+ return input;
+ }
+ input = new ByteArrayInputStream(new byte[8]); // buf size doesn't matter here
+ mPathToInput.put(path, input);
+ StringBuffer sb = mPathToData.get(path);
+ if (sb == null) {
+ sb = new StringBuffer();
+ mPathToData.put(path, sb);
+ }
+ mInputToData.put(input, sb);
+ return input;
+ }
+
+ @Override
+ int readCgroupProcs(InputStream input, byte[] buf, int offset, int len) throws IOException {
+ StringBuffer sb = mInputToData.get(input);
+ if (sb == null) {
+ return -1;
+ }
+ byte[] avail = sb.toString().getBytes();
+ System.arraycopy(avail, 0, buf, offset, Math.min(len, avail.length));
+ return Math.min(len, avail.length);
+ }
+
+ @Override
+ String getProcessName(final int pid) {
+ return mPidToName.get(pid);
+ }
+
+ void addToProcess(int uid, int pid, int newPid) {
+ final String path = PhantomProcessList.getCgroupFilePath(uid, pid);
+ StringBuffer sb = mPathToData.get(path);
+ if (sb == null) {
+ sb = new StringBuffer();
+ mPathToData.put(path, sb);
+ }
+ sb.append(newPid).append('\n');
+ }
+ }
+
static class ServiceThreadRule implements TestRule {
private ServiceThread mThread;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 5d8f662301c1..d032dbd5f47f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -351,7 +351,7 @@ public class MockingOomAdjusterTests {
doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
WindowProcessController wpc = app.getWindowProcessController();
doReturn(true).when(wpc).hasActivities();
- doAnswer(answer((minTaskLayer, callback) -> {
+ doAnswer(answer(callback -> {
Field field = callback.getClass().getDeclaredField("adj");
field.set(callback, VISIBLE_APP_ADJ);
field = callback.getClass().getDeclaredField("foregroundActivities");
@@ -361,7 +361,7 @@ public class MockingOomAdjusterTests {
field = callback.getClass().getDeclaredField("schedGroup");
field.set(callback, SCHED_GROUP_TOP_APP);
return 0;
- })).when(wpc).computeOomAdjFromActivities(anyInt(),
+ })).when(wpc).computeOomAdjFromActivities(
any(WindowProcessController.ComputeOomAdjCallback.class));
sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
@@ -457,12 +457,12 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_HeavyWeight() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
- doReturn(true).when(sService.mAtmInternal).isHeavyWeightProcess(any(
- WindowProcessController.class));
+ doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
+ WindowProcessController wpc = app.getWindowProcessController();
+ doReturn(true).when(wpc).isHeavyWeightProcess();
sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
- doReturn(false).when(sService.mAtmInternal).isHeavyWeightProcess(any(
- WindowProcessController.class));
+ doReturn(false).when(wpc).isHeavyWeightProcess();
assertProcStates(app, PROCESS_STATE_HEAVY_WEIGHT, HEAVY_WEIGHT_APP_ADJ,
SCHED_GROUP_BACKGROUND);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
index a0f48c674316..d67edddb30e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationFudgerTest.java
@@ -79,7 +79,7 @@ public class LocationFudgerTest {
Location coarse = mFudger.createCoarse(fine);
assertThat(coarse).isNotNull();
- assertThat(coarse).isNotSameAs(fine);
+ assertThat(coarse).isNotSameInstanceAs(fine);
assertThat(coarse.hasBearing()).isFalse();
assertThat(coarse.hasSpeed()).isFalse();
assertThat(coarse.hasAltitude()).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
index 149148649cf6..31ec4a53908c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationProviderManagerTest.java
@@ -16,8 +16,6 @@
package com.android.server.location;
-import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
-import static android.app.AlarmManager.WINDOW_EXACT;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
@@ -41,7 +39,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.after;
@@ -55,8 +52,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.MockitoAnnotations.initMocks;
-import android.app.AlarmManager;
-import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
import android.location.ILocationCallback;
import android.location.ILocationListener;
@@ -66,14 +61,11 @@ import android.location.LocationManagerInternal.ProviderEnabledListener;
import android.location.LocationRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.IRemoteCallback;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.WorkSource;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
@@ -120,6 +112,7 @@ public class LocationProviderManagerTest {
private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1,
"mypackage",
"attribution");
+ private static final WorkSource WORK_SOURCE = new WorkSource(IDENTITY.getUid());
private Random mRandom;
@@ -128,8 +121,6 @@ public class LocationProviderManagerTest {
@Mock
private Context mContext;
@Mock
- private AlarmManager mAlarmManager;
- @Mock
private PowerManager mPowerManager;
@Mock
private PowerManager.WakeLock mWakeLock;
@@ -152,7 +143,6 @@ public class LocationProviderManagerTest {
LocalServices.addService(LocationManagerInternal.class, mInternal);
doReturn("android").when(mContext).getPackageName();
- doReturn(mAlarmManager).when(mContext).getSystemService(AlarmManager.class);
doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
@@ -214,22 +204,20 @@ public class LocationProviderManagerTest {
@Test
public void testIsEnabled() {
assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
+ assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+ assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
mProvider.setAllowed(false);
assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
+ assertThat(mManager.isEnabled(OTHER_USER)).isFalse();
mProvider.setAllowed(true);
- mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
- assertThat(mManager.isEnabled(CURRENT_USER)).isFalse();
- assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
-
- mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
assertThat(mManager.isEnabled(CURRENT_USER)).isTrue();
- assertThat(mManager.isEnabled(OTHER_USER)).isFalse();
+ assertThat(mManager.isEnabled(OTHER_USER)).isTrue();
}
@Test
@@ -249,23 +237,15 @@ public class LocationProviderManagerTest {
mProvider.setAllowed(false);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
false);
+ verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
+ false);
mProvider.setAllowed(true);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, CURRENT_USER,
true);
-
- mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
- verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
- false);
verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
true);
- mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
- verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, CURRENT_USER,
- true);
- verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, OTHER_USER,
- false);
-
mManager.removeEnabledListener(listener);
mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
verifyNoMoreInteractions(listener);
@@ -355,7 +335,7 @@ public class LocationProviderManagerTest {
@Test
public void testPassive_Listener() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).build();
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
mPassive.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
@@ -380,8 +360,11 @@ public class LocationProviderManagerTest {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
ILocationListener listener = createMockLocationListener();
- mManager.registerLocationRequest(new LocationRequest.Builder(0).build(), IDENTITY,
- PERMISSION_FINE, listener);
+ mManager.registerLocationRequest(
+ new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(),
+ IDENTITY,
+ PERMISSION_FINE,
+ listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
@@ -409,16 +392,6 @@ public class LocationProviderManagerTest {
mProvider.setAllowed(true);
verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, true);
- mInjector.getUserInfoHelper().setCurrentUserId(OTHER_USER);
- verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, false);
- loc = createLocation(NAME, mRandom);
- mProvider.setProviderLocation(loc);
- verify(listener, times(1)).onLocationChanged(any(Location.class),
- nullable(IRemoteCallback.class));
-
- mInjector.getUserInfoHelper().setCurrentUserId(CURRENT_USER);
- verify(listener, timeout(TIMEOUT_MS).times(3)).onProviderEnabledChanged(NAME, true);
-
loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
verify(listener, times(2)).onLocationChanged(locationCaptor.capture(),
@@ -434,8 +407,11 @@ public class LocationProviderManagerTest {
"attribution");
ILocationListener listener = createMockLocationListener();
- mManager.registerLocationRequest(new LocationRequest.Builder(0).build(), identity,
- PERMISSION_FINE, listener);
+ mManager.registerLocationRequest(
+ new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(),
+ identity,
+ PERMISSION_FINE,
+ listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
@@ -447,8 +423,11 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_Unregister() throws Exception {
ILocationListener listener = createMockLocationListener();
- mManager.registerLocationRequest(new LocationRequest.Builder(0).build(), IDENTITY,
- PERMISSION_FINE, listener);
+ mManager.registerLocationRequest(
+ new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(),
+ IDENTITY,
+ PERMISSION_FINE,
+ listener);
mManager.unregisterLocationRequest(listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -465,8 +444,11 @@ public class LocationProviderManagerTest {
"attribution");
ILocationListener listener = createMockLocationListener();
- mManager.registerLocationRequest(new LocationRequest.Builder(0).build(), identity,
- PERMISSION_FINE, listener);
+ mManager.registerLocationRequest(
+ new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(),
+ identity,
+ PERMISSION_FINE,
+ listener);
CountDownLatch blocker = new CountDownLatch(1);
IN_PROCESS_EXECUTOR.execute(() -> {
@@ -487,7 +469,10 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_NumUpdates() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).setMaxUpdates(5).build();
+ LocationRequest request = new LocationRequest.Builder(0)
+ .setMaxUpdates(5)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -504,21 +489,13 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_ExpiringAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(5000).build();
+ LocationRequest request = new LocationRequest.Builder(0)
+ .setDurationMillis(5000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
- long baseTimeMs = SystemClock.elapsedRealtime();
-
- ArgumentCaptor<Long> timeoutCapture = ArgumentCaptor.forClass(Long.class);
- ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
- OnAlarmListener.class);
- verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), timeoutCapture.capture(),
- eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
- any(WorkSource.class));
-
- assertThat(timeoutCapture.getValue()).isAtLeast(baseTimeMs + 4000);
- assertThat(timeoutCapture.getValue()).isAtMost(baseTimeMs + 5000);
- listenerCapture.getValue().onAlarm();
+ mInjector.getAlarmHelper().incrementAlarmTime(5000);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
verify(listener, never()).onLocationChanged(any(Location.class),
nullable(IRemoteCallback.class));
@@ -527,7 +504,10 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_ExpiringNoAlarm() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).setDurationMillis(25).build();
+ LocationRequest request = new LocationRequest.Builder(0)
+ .setDurationMillis(25)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Thread.sleep(25);
@@ -540,8 +520,10 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_FastestInterval() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(5000).setMinUpdateIntervalMillis(
- 5000).build();
+ LocationRequest request = new LocationRequest.Builder(5000)
+ .setMinUpdateIntervalMillis(5000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -554,8 +536,10 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_SmallestDisplacement() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(5000).setMinUpdateDistanceMeters(
- 1f).build();
+ LocationRequest request = new LocationRequest.Builder(5000)
+ .setMinUpdateDistanceMeters(1f)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
@@ -569,7 +553,7 @@ public class LocationProviderManagerTest {
@Test
public void testRegisterListener_NoteOpFailure() throws Exception {
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).build();
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getAppOpsHelper().setAppOpAllowed(OP_FINE_LOCATION, IDENTITY.getPackageName(),
@@ -587,8 +571,11 @@ public class LocationProviderManagerTest {
"attribution");
ILocationListener listener = createMockLocationListener();
- mManager.registerLocationRequest(new LocationRequest.Builder(0).build(), identity,
- PERMISSION_FINE, listener);
+ mManager.registerLocationRequest(
+ new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build(),
+ identity,
+ PERMISSION_FINE,
+ listener);
CountDownLatch blocker = new CountDownLatch(1);
IN_PROCESS_EXECUTOR.execute(() -> {
@@ -615,10 +602,8 @@ public class LocationProviderManagerTest {
ArgumentCaptor<Location> locationCaptor = ArgumentCaptor.forClass(Location.class);
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
Location loc = createLocation(NAME, mRandom);
mProvider.setProviderLocation(loc);
@@ -631,10 +616,9 @@ public class LocationProviderManagerTest {
@Test
public void testGetCurrentLocation_Cancel() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ ICancellationSignal cancellationSignal = mManager.getCurrentLocation(request,
+ IDENTITY, PERMISSION_FINE, listener);
cancellationSignal.cancel();
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -645,10 +629,8 @@ public class LocationProviderManagerTest {
@Test
public void testGetCurrentLocation_ProviderDisabled() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderAllowed(false);
mProvider.setProviderAllowed(true);
@@ -661,10 +643,8 @@ public class LocationProviderManagerTest {
mProvider.setProviderAllowed(false);
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
mProvider.setProviderAllowed(true);
mProvider.setProviderLocation(createLocation(NAME, mRandom));
@@ -679,10 +659,8 @@ public class LocationProviderManagerTest {
mProvider.setProviderLocation(loc);
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
verify(listener, times(1)).onLocation(locationCaptor.capture());
assertThat(locationCaptor.getValue()).isEqualTo(loc);
@@ -691,18 +669,10 @@ public class LocationProviderManagerTest {
@Test
public void testGetCurrentLocation_Timeout() throws Exception {
ILocationCallback listener = createMockGetCurrentLocationListener();
- LocationRequest locationRequest = new LocationRequest.Builder(0).build();
- ICancellationSignal cancellationSignal = CancellationSignal.createTransport();
- mManager.getCurrentLocation(locationRequest, IDENTITY,
- PERMISSION_FINE, cancellationSignal, listener);
-
- ArgumentCaptor<OnAlarmListener> listenerCapture = ArgumentCaptor.forClass(
- OnAlarmListener.class);
- verify(mAlarmManager).set(eq(ELAPSED_REALTIME_WAKEUP), anyLong(),
- eq(WINDOW_EXACT), eq(0L), listenerCapture.capture(), any(Handler.class),
- any(WorkSource.class));
- listenerCapture.getValue().onAlarm();
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
+ mManager.getCurrentLocation(request, IDENTITY, PERMISSION_FINE, listener);
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
verify(listener, times(1)).onLocation(isNull());
}
@@ -714,7 +684,7 @@ public class LocationProviderManagerTest {
IDENTITY.getPackageName())).isFalse();
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(0).build();
+ LocationRequest request = new LocationRequest.Builder(0).setWorkSource(WORK_SOURCE).build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
@@ -743,7 +713,8 @@ public class LocationProviderManagerTest {
assertThat(mProvider.getRequest().getLocationRequests()).isEmpty();
ILocationListener listener1 = createMockLocationListener();
- LocationRequest request1 = new LocationRequest.Builder(5).build();
+ LocationRequest request1 = new LocationRequest.Builder(5).setWorkSource(
+ WORK_SOURCE).build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().isActive()).isTrue();
@@ -754,7 +725,10 @@ public class LocationProviderManagerTest {
assertThat(mProvider.getRequest().getWorkSource()).isNotNull();
ILocationListener listener2 = createMockLocationListener();
- LocationRequest request2 = new LocationRequest.Builder(1).setLowPower(true).build();
+ LocationRequest request2 = new LocationRequest.Builder(1)
+ .setLowPower(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
assertThat(mProvider.getRequest().isActive()).isTrue();
@@ -781,9 +755,49 @@ public class LocationProviderManagerTest {
}
@Test
+ public void testProviderRequest_DelayedRequest() throws Exception {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+
+ verify(listener1).onLocationChanged(any(Location.class), nullable(IRemoteCallback.class));
+
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
+ assertThat(mProvider.getRequest().isActive()).isTrue();
+ assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(60000);
+ }
+
+ @Test
+ public void testProviderRequest_SpamRequesting() {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.unregisterLocationRequest(listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ mManager.unregisterLocationRequest(listener1);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
public void testProviderRequest_BackgroundThrottle() {
ILocationListener listener1 = createMockLocationListener();
- LocationRequest request1 = new LocationRequest.Builder(5).build();
+ LocationRequest request1 = new LocationRequest.Builder(5)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
@@ -799,7 +813,9 @@ public class LocationProviderManagerTest {
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
- LocationRequest request1 = new LocationRequest.Builder(5).build();
+ LocationRequest request1 = new LocationRequest.Builder(5)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().isActive()).isTrue();
@@ -807,8 +823,10 @@ public class LocationProviderManagerTest {
assertThat(mProvider.getRequest().isLocationSettingsIgnored()).isFalse();
ILocationListener listener2 = createMockLocationListener();
- LocationRequest request2 = new LocationRequest.Builder(1).setLocationSettingsIgnored(
- true).build();
+ LocationRequest request2 = new LocationRequest.Builder(1)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
assertThat(mProvider.getRequest().isActive()).isTrue();
@@ -822,12 +840,16 @@ public class LocationProviderManagerTest {
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
- LocationRequest request1 = new LocationRequest.Builder(1).build();
+ LocationRequest request1 = new LocationRequest.Builder(1)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
ILocationListener listener2 = createMockLocationListener();
- LocationRequest request2 = new LocationRequest.Builder(5).setLocationSettingsIgnored(
- true).build();
+ LocationRequest request2 = new LocationRequest.Builder(5)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request2, IDENTITY, PERMISSION_FINE, listener2);
mInjector.getSettingsHelper().setLocationEnabled(false, IDENTITY.getUserId());
@@ -844,8 +866,10 @@ public class LocationProviderManagerTest {
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(1).setLocationSettingsIgnored(
- true).build();
+ LocationRequest request = new LocationRequest.Builder(1)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
mInjector.getSettingsHelper().setIgnoreSettingsPackageWhitelist(Collections.emptySet());
@@ -861,8 +885,10 @@ public class LocationProviderManagerTest {
Collections.singleton(IDENTITY.getPackageName()));
ILocationListener listener1 = createMockLocationListener();
- LocationRequest request1 = new LocationRequest.Builder(5).setLocationSettingsIgnored(
- true).build();
+ LocationRequest request1 = new LocationRequest.Builder(5)
+ .setLocationSettingsIgnored(true)
+ .setWorkSource(WORK_SOURCE)
+ .build();
mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
assertThat(mProvider.getRequest().getIntervalMillis()).isEqualTo(5);
@@ -877,7 +903,7 @@ public class LocationProviderManagerTest {
LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF);
ILocationListener listener = createMockLocationListener();
- LocationRequest request = new LocationRequest.Builder(5).build();
+ LocationRequest request = new LocationRequest.Builder(5).setWorkSource(WORK_SOURCE).build();
mManager.registerLocationRequest(request, IDENTITY, PERMISSION_FINE, listener);
assertThat(mProvider.getRequest().isActive()).isTrue();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
new file mode 100644
index 000000000000..0e3e6ef02a18
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAlarmHelper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.location.util;
+
+import android.app.AlarmManager.OnAlarmListener;
+import android.os.WorkSource;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class FakeAlarmHelper extends AlarmHelper {
+
+ private static class Alarm {
+ public long delayMs;
+ public final OnAlarmListener listener;
+
+ Alarm(long delayMs, OnAlarmListener listener) {
+ this.delayMs = delayMs;
+ this.listener = listener;
+ }
+ }
+
+ private final ArrayList<Alarm> mAlarms = new ArrayList<>();
+
+ @Override
+ public void setDelayedAlarmInternal(long delayMs, OnAlarmListener listener,
+ WorkSource workSource) {
+ mAlarms.add(new Alarm(delayMs, listener));
+ }
+
+ @Override
+ public void cancel(OnAlarmListener listener) {
+ mAlarms.removeIf(alarm -> alarm.listener == listener);
+ }
+
+ public void incrementAlarmTime(long incrementMs) {
+ Iterator<Alarm> it = mAlarms.iterator();
+ while (it.hasNext()) {
+ Alarm alarm = it.next();
+ alarm.delayMs -= incrementMs;
+ if (alarm.delayMs <= 0) {
+ it.remove();
+ alarm.listener.onAlarm();
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java
index 3ead5d4f214d..aea0170fcea6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java
@@ -28,7 +28,8 @@ public class FakeLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper
@LocationPowerSaveMode
private int mLocationPowerSaveMode;
- public FakeLocationPowerSaveModeHelper() {
+ public FakeLocationPowerSaveModeHelper(LocationEventLog locationEventLog) {
+ super(locationEventLog);
mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java
index 2acb70c4b83d..7890454c8d2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java
@@ -84,7 +84,7 @@ public class SystemLocationPowerSaveModeHelperTest {
Context context = mock(Context.class);
doReturn(powerManager).when(context).getSystemService(PowerManager.class);
- mHelper = new SystemLocationPowerSaveModeHelper(context);
+ mHelper = new SystemLocationPowerSaveModeHelper(context, new LocationEventLog());
mHelper.onSystemReady();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
index 1867be0b9f3b..94651e66a4d7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java
@@ -16,11 +16,11 @@
package com.android.server.location.util;
-import com.android.server.location.LocationRequestStatistics;
-
public class TestInjector implements Injector {
+ private final LocationEventLog mLocationEventLog;
private final FakeUserInfoHelper mUserInfoHelper;
+ private final FakeAlarmHelper mAlarmHelper;
private final FakeAppOpsHelper mAppOpsHelper;
private final FakeLocationPermissionsHelper mLocationPermissionsHelper;
private final FakeSettingsHelper mSettingsHelper;
@@ -29,19 +29,19 @@ public class TestInjector implements Injector {
private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
private final LocationAttributionHelper mLocationAttributionHelper;
private final LocationUsageLogger mLocationUsageLogger;
- private final LocationRequestStatistics mLocationRequestStatistics;
public TestInjector() {
+ mLocationEventLog = new LocationEventLog();
mUserInfoHelper = new FakeUserInfoHelper();
+ mAlarmHelper = new FakeAlarmHelper();
mAppOpsHelper = new FakeAppOpsHelper();
mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper);
mSettingsHelper = new FakeSettingsHelper();
mAppForegroundHelper = new FakeAppForegroundHelper();
- mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper();
+ mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(mLocationEventLog);
mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
mLocationUsageLogger = new LocationUsageLogger();
- mLocationRequestStatistics = new LocationRequestStatistics();
}
@Override
@@ -50,6 +50,11 @@ public class TestInjector implements Injector {
}
@Override
+ public FakeAlarmHelper getAlarmHelper() {
+ return mAlarmHelper;
+ }
+
+ @Override
public FakeAppOpsHelper getAppOpsHelper() {
return mAppOpsHelper;
}
@@ -90,7 +95,7 @@ public class TestInjector implements Injector {
}
@Override
- public LocationRequestStatistics getLocationRequestStatistics() {
- return mLocationRequestStatistics;
+ public LocationEventLog getLocationEventLog() {
+ return mLocationEventLog;
}
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 7fc6bbd70000..1f723749856f 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -78,6 +78,7 @@ android_test {
"libbinder",
"libc++",
"libcutils",
+ "libicing",
"liblog",
"liblzma",
"libnativehelper",
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 90e1cfcd305a..79936ce6d623 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -77,6 +77,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
<uses-permission android:name="android.permission.DUMP"/>
<uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+ <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
<uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
index 1fa1b8fc0619..75696dac3b44 100644
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
+++ b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
@@ -20,16 +20,20 @@ import static android.location.timezone.ParcelableTestSupport.assertRoundTripPar
import org.junit.Test;
+import java.time.Duration;
+
public class LocationTimeZoneProviderRequestTest {
@Test
public void testParcelable() {
LocationTimeZoneProviderRequest.Builder builder =
new LocationTimeZoneProviderRequest.Builder()
- .setReportLocationTimeZone(true);
+ .setReportLocationTimeZone(false);
assertRoundTripParcelable(builder.build());
- builder.setReportLocationTimeZone(false);
+ builder.setReportLocationTimeZone(true)
+ .setInitializationTimeoutMillis(Duration.ofMinutes(5).toMillis());
+
assertRoundTripParcelable(builder.build());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index c91bb93fc559..9a2ce3c598a4 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -223,6 +223,25 @@ public class GestureLauncherServiceTest {
}
@Test
+ public void testInterceptPowerKeyDown_firstPowerDown_panicGestureNotLaunched() {
+ withPanicGestureEnabledSettingValue(true);
+ mGestureLauncherService.updatePanicButtonGestureEnabled();
+
+ long eventTime = INITIAL_EVENT_TIME_MILLIS
+ + GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS - 1;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ verify(mMetricsLogger).histogram("power_double_tap_interval", (int) eventTime);
+ }
+
+ @Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
withCameraDoubleTapPowerEnableConfigValue(false);
withCameraDoubleTapPowerDisableSettingValue(1);
@@ -405,6 +424,146 @@ public class GestureLauncherServiceTest {
}
@Test
+ public void
+ testInterceptPowerKeyDown_fiveInboundPresses_cameraAndPanicEnabled_bothLaunch() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ withPanicGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ mGestureLauncherService.updatePanicButtonGestureEnabled();
+ withUserSetupCompleteValue(true);
+
+ // First button press does nothing
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+
+ // 2nd button triggers camera
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertTrue(intercepted);
+ assertTrue(outLaunched.value);
+
+ // Camera checks
+ verify(mStatusBarManagerInternal).onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
+ verify(mMetricsLogger)
+ .action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) interval);
+
+ final ArgumentCaptor<Integer> cameraIntervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_double_tap_interval"), cameraIntervalCaptor.capture());
+ List<Integer> cameraIntervals = cameraIntervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, cameraIntervals.get(0).intValue());
+ assertEquals((int) interval, cameraIntervals.get(1).intValue());
+
+ final ArgumentCaptor<Integer> tapCountCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(2)).histogram(
+ eq("power_consecutive_short_tap_count"), tapCountCaptor.capture());
+ List<Integer> tapCounts = tapCountCaptor.getAllValues();
+ assertEquals(1, tapCounts.get(0).intValue());
+ assertEquals(2, tapCounts.get(1).intValue());
+
+ // Continue the button presses for the panic gesture.
+
+ // Presses 3 and 4 should not trigger any gesture
+ for (int i = 0; i < 2; i++) {
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ }
+
+ // Fifth button press should trigger the panic flow
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertTrue(intercepted);
+ assertTrue(outLaunched.value);
+
+ // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+ verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(5)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+ }
+
+ @Test
+ public void
+ testInterceptPowerKeyDown_fiveInboundPresses_panicGestureEnabled_launchesPanicFlow() {
+ withPanicGestureEnabledSettingValue(true);
+ mGestureLauncherService.updatePanicButtonGestureEnabled();
+ withUserSetupCompleteValue(true);
+
+ // First button press does nothing
+ long eventTime = INITIAL_EVENT_TIME_MILLIS;
+ KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ boolean interactive = true;
+ MutableBoolean outLaunched = new MutableBoolean(true);
+ boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+
+ final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ // 3 more button presses which should not trigger any gesture (camera gesture disabled)
+ for (int i = 0; i < 3; i++) {
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertFalse(intercepted);
+ assertFalse(outLaunched.value);
+ }
+
+ // Fifth button press should trigger the panic flow
+ eventTime += interval;
+ keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+ IGNORED_REPEAT);
+ outLaunched.value = false;
+ intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+ outLaunched);
+ assertTrue(outLaunched.value);
+ assertTrue(intercepted);
+
+ // TODO (b/169960245) Verify metric event equiv. to ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE
+ verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+
+ final ArgumentCaptor<Integer> intervalCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMetricsLogger, times(5)).histogram(
+ eq("power_double_tap_interval"), intervalCaptor.capture());
+ List<Integer> intervals = intervalCaptor.getAllValues();
+ assertEquals((int) INITIAL_EVENT_TIME_MILLIS, intervals.get(0).intValue());
+ assertEquals((int) interval, intervals.get(1).intValue());
+ }
+
+ @Test
public void testInterceptPowerKeyDown_longpress() {
withCameraDoubleTapPowerEnableConfigValue(true);
withCameraDoubleTapPowerDisableSettingValue(0);
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
new file mode 100644
index 000000000000..8a22a2f5d92f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorManagerService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorManagerServiceTest
+ */
+@Presubmit
+public class VibratorManagerServiceTest {
+
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ private VibratorManagerService createService() {
+ return new VibratorManagerService(InstrumentationRegistry.getContext(),
+ new VibratorManagerService.Injector() {
+ @Override
+ VibratorManagerService.NativeWrapper getNativeWrapper() {
+ return mNativeWrapperMock;
+ }
+ });
+ }
+
+ @Test
+ public void createService_initializesNativeService() {
+ createService();
+ verify(mNativeWrapperMock).init();
+ }
+
+ @Test
+ public void getVibratorIds_withNullResultFromNative_returnsEmptyArray() {
+ when(mNativeWrapperMock.getVibratorIds()).thenReturn(null);
+ assertArrayEquals(new int[0], createService().getVibratorIds());
+ }
+
+ @Test
+ public void getVibratorIds_withNonEmptyResultFromNative_returnsSameArray() {
+ when(mNativeWrapperMock.getVibratorIds()).thenReturn(new int[]{ 1, 2 });
+ assertArrayEquals(new int[]{ 1, 2 }, createService().getVibratorIds());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 8d4f2aa69d68..8d60de9abe99 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -24,6 +24,7 @@ import static org.mockito.AdditionalMatchers.gt;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.ArgumentMatchers.notNull;
@@ -36,11 +37,13 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManagerInternal;
import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
import android.os.Handler;
import android.os.IBinder;
import android.os.IVibratorStateListener;
@@ -104,6 +107,7 @@ public class VibratorServiceTest {
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PowerSaveState mPowerSaveStateMock;
@Mock private Vibrator mVibratorMock;
+ @Mock private AppOpsManager mAppOpsManagerMock;
@Mock private VibratorService.NativeWrapper mNativeWrapperMock;
@Mock private IVibratorStateListener mVibratorStateListenerMock;
@Mock private IBinder mVibratorStateListenerBinderMock;
@@ -121,6 +125,7 @@ public class VibratorServiceTest {
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibratorMock);
+ when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
when(mVibratorMock.getDefaultHapticFeedbackIntensity())
.thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
when(mVibratorMock.getDefaultNotificationVibrationIntensity())
@@ -133,6 +138,14 @@ public class VibratorServiceTest {
when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
.thenReturn(mPowerSaveStateMock);
+ setVibrationIntensityUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setVibrationIntensityUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setVibrationIntensityUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ setVibrationIntensityUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_MEDIUM);
+
addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
FakeSettingsProvider.clearSettingsProvider();
@@ -286,6 +299,55 @@ public class VibratorServiceTest {
}
@Test
+ public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+ VibratorService service = createService();
+ service.systemReady();
+
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+ VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
+ audioAttributes, effect).build();
+
+ vibrate(service, effect, vibrationAttributes);
+
+ verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+ }
+
+ @Test
+ public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+ VibratorService service = createService();
+ service.systemReady();
+
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+ new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_UNKNOWN).build());
+
+ InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+ anyInt(), anyString());
+ inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+ eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+ }
+
+ @Test
public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude() {
mockVibratorCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorService service = createService();
@@ -367,7 +429,7 @@ public class VibratorServiceTest {
// Wait for VibrateThread to turn vibrator ON with total timing and no callback.
Thread.sleep(5);
- verify(mNativeWrapperMock).vibratorOn(eq(30L), eq(0L));
+ verify(mNativeWrapperMock).vibratorOn(eq(30L), anyLong());
// First amplitude set right away.
verify(mNativeWrapperMock).vibratorSetAmplitude(eq(100));
@@ -434,8 +496,8 @@ public class VibratorServiceTest {
Thread.sleep(15);
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
- inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), eq(0L));
- inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), eq(0L));
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(3L), anyLong());
+ inOrderVerifier.verify(mNativeWrapperMock).vibratorOn(eq(2L), anyLong());
inOrderVerifier.verify(mNativeWrapperMock).vibratorOff();
}
@@ -637,6 +699,11 @@ public class VibratorServiceTest {
vibrate(service, effect, ALARM_ATTRS);
}
+ private void vibrate(VibratorService service, VibrationEffect effect, AudioAttributes attrs) {
+ VibrationAttributes attributes = new VibrationAttributes.Builder(attrs, effect).build();
+ vibrate(service, effect, attributes);
+ }
+
private void vibrate(VibratorService service, VibrationEffect effect,
VibrationAttributes attributes) {
service.vibrate(UID, PACKAGE_NAME, effect, attributes, "some reason", service);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b7355ce92c28..b9c2e56a4d90 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -49,6 +49,7 @@ import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
+import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.WindowMagnificationManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -94,6 +95,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
@Mock private IBinder mMockBinder;
@Mock private IAccessibilityServiceClient mMockServiceClient;
@Mock private WindowMagnificationManager mMockWindowMagnificationMgr;
+ @Mock private MagnificationController mMockMagnificationController;
private AccessibilityUserState mUserState;
private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
@@ -110,6 +112,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+ when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
+ mMockWindowMagnificationMgr);
mA11yms = new AccessibilityManagerService(
InstrumentationRegistry.getContext(),
mMockPackageManager,
@@ -117,7 +121,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
mMockSystemActionPerformer,
mMockA11yWindowManager,
mMockA11yDisplayListener,
- mMockWindowMagnificationMgr);
+ mMockMagnificationController);
final AccessibilityUserState userState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 6acd9b6b3803..9ba9188dc580 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationRequestObserver;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -93,6 +95,10 @@ public class FullScreenMagnificationControllerTest {
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+ private final MagnificationAnimationCallback mAnimationCallback = mock(
+ MagnificationAnimationCallback.class);
+ private final MagnificationRequestObserver mRequestObserver = mock(
+ MagnificationRequestObserver.class);
final MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
ValueAnimator mMockValueAnimator;
@@ -100,12 +106,10 @@ public class FullScreenMagnificationControllerTest {
ValueAnimator.AnimatorListener mStateListener;
FullScreenMagnificationController mFullScreenMagnificationController;
- MagnificationAnimationCallback mAnimationCallback;
@Before
public void setUp() {
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
- mAnimationCallback = Mockito.mock(MagnificationAnimationCallback.class);
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -116,7 +120,7 @@ public class FullScreenMagnificationControllerTest {
initMockWindowManager();
mFullScreenMagnificationController = new FullScreenMagnificationController(
- mMockControllerCtx, new Object());
+ mMockControllerCtx, new Object(), mRequestObserver);
}
@After
@@ -345,6 +349,7 @@ public class FullScreenMagnificationControllerTest {
verify(mMockAms).notifyMagnificationChanged(displayId,
INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
verify(mMockValueAnimator).start();
+ verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1);
// Initial value
when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 008cbed10d18..1cdd873860b8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -53,6 +53,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationRequestObserver;
import com.android.server.accessibility.magnification.MagnificationGestureHandler.ScaleChangedListener;
import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
@@ -126,6 +127,8 @@ public class FullScreenMagnificationGestureHandlerTest {
FullScreenMagnificationController mFullScreenMagnificationController;
@Mock
ScaleChangedListener mMockScaleChangedListener;
+ @Mock
+ MagnificationRequestObserver mMagnificationRequestObserver;
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
@@ -148,7 +151,7 @@ public class FullScreenMagnificationGestureHandlerTest {
when(mockController.getAnimationDuration()).thenReturn(1000L);
when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
mFullScreenMagnificationController = new FullScreenMagnificationController(mockController,
- new Object()) {
+ new Object(), mMagnificationRequestObserver) {
@Override
public boolean magnificationRegionContains(int displayId, float x, float y) {
return true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index cd8e39cfd2e7..e82ff344d4cc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationTransitionControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -62,7 +62,7 @@ import org.mockito.MockitoAnnotations;
* Tests for MagnificationController.
*/
@RunWith(AndroidJUnit4.class)
-public class MagnificationTransitionControllerTest {
+public class MagnificationControllerTest {
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600);
@@ -75,7 +75,7 @@ public class MagnificationTransitionControllerTest {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@Mock private AccessibilityManagerService mService;
- @Mock private MagnificationTransitionController.TransitionCallBack mTransitionCallBack;
+ @Mock private MagnificationController.TransitionCallBack mTransitionCallBack;
@Mock private Context mContext;
@Mock private FullScreenMagnificationController mScreenMagnificationController;
@Captor private ArgumentCaptor<MagnificationAnimationCallback> mCallbackArgumentCaptor;
@@ -83,7 +83,7 @@ public class MagnificationTransitionControllerTest {
private MockWindowMagnificationConnection mMockConnection;
private WindowMagnificationManager mWindowMagnificationManager;
private MockContentResolver mMockResolver;
- private MagnificationTransitionController mMagnificationTransitionController;
+ private MagnificationController mMagnificationController;
@Before
public void setUp() throws Exception {
@@ -95,14 +95,12 @@ public class MagnificationTransitionControllerTest {
Settings.Secure.putFloatForUser(mMockResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, DEFAULT_SCALE,
CURRENT_USER_ID);
- mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID);
- when(mService.getFullScreenMagnificationController()).thenReturn(
- mScreenMagnificationController);
- when(mService.getWindowMagnificationMgr()).thenReturn(mWindowMagnificationManager);
+ mWindowMagnificationManager = Mockito.spy(
+ new WindowMagnificationManager(mContext, CURRENT_USER_ID));
mMockConnection = new MockWindowMagnificationConnection(true);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
- mMagnificationTransitionController = new MagnificationTransitionController(mService,
- new Object());
+ mMagnificationController = new MagnificationController(mService, new Object(), mContext,
+ mScreenMagnificationController, mWindowMagnificationManager);
}
@After
@@ -113,7 +111,7 @@ public class MagnificationTransitionControllerTest {
@Test
public void transitionToWindowMode_notMagnifying_doNothing() throws RemoteException {
setMagnificationModeSettings(MODE_FULLSCREEN);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_WINDOW,
mTransitionCallBack);
@@ -130,7 +128,7 @@ public class MagnificationTransitionControllerTest {
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_WINDOW,
mTransitionCallBack);
@@ -147,11 +145,11 @@ public class MagnificationTransitionControllerTest {
public void transitionToWindowMode_disablingWindowMode_enablingWindowWithFormerCenter()
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_FULLSCREEN,
mTransitionCallBack);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_WINDOW,
mTransitionCallBack);
@@ -166,7 +164,7 @@ public class MagnificationTransitionControllerTest {
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_FULLSCREEN,
mTransitionCallBack);
mMockConnection.invokeCallbacks();
@@ -186,7 +184,7 @@ public class MagnificationTransitionControllerTest {
magnificationBounds.bottom + 100);
setMagnificationEnabled(MODE_WINDOW, magnifiedCenter.x, magnifiedCenter.y);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_FULLSCREEN,
mTransitionCallBack);
mMockConnection.invokeCallbacks();
@@ -202,11 +200,11 @@ public class MagnificationTransitionControllerTest {
public void transitionToFullScreenMode_disablingFullScreen_enableFullScreenWithFormerCenter()
throws RemoteException {
setMagnificationEnabled(MODE_FULLSCREEN);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_WINDOW,
mTransitionCallBack);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_FULLSCREEN,
mTransitionCallBack);
@@ -221,7 +219,7 @@ public class MagnificationTransitionControllerTest {
public void interruptDuringTransitionToFullScreenMode_windowMagnifying_notifyTransitionFailed()
throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
- mMagnificationTransitionController.transitionMagnificationModeLocked(TEST_DISPLAY,
+ mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY,
MODE_FULLSCREEN,
mTransitionCallBack);
@@ -237,7 +235,34 @@ public class MagnificationTransitionControllerTest {
verify(mTransitionCallBack).onResult(false);
}
+ @Test
+ public void onDisplayRemoved_notifyAllModules() {
+ mMagnificationController.onDisplayRemoved(TEST_DISPLAY);
+
+ verify(mScreenMagnificationController).onDisplayRemoved(TEST_DISPLAY);
+ verify(mWindowMagnificationManager).onDisplayRemoved(TEST_DISPLAY);
+ }
+
+ @Test
+ public void updateUserIdIfNeeded_AllModulesAvailable_setUserId() {
+ mMagnificationController.updateUserIdIfNeeded(CURRENT_USER_ID);
+
+ verify(mScreenMagnificationController).setUserId(CURRENT_USER_ID);
+ verify(mWindowMagnificationManager).setUserId(CURRENT_USER_ID);
+ }
+
+ @Test
+ public void onMagnificationRequest_windowMagnifying_disableWindow() throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+
+ mMagnificationController.onRequestMagnificationSpec(TEST_DISPLAY, 1);
+ mMockConnection.invokeCallbacks();
+
+ assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+ }
+
private void setMagnificationEnabled(int mode) throws RemoteException {
+
setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 89b0a03a25bb..d5be3ede40d2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -369,6 +369,16 @@ public class WindowMagnificationManagerTest {
assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f);
}
+ @Test
+ public void onDisplayRemoved_enabledOnTestDisplay_disabled() {
+ mWindowMagnificationManager.requestConnection(true);
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f);
+
+ mWindowMagnificationManager.onDisplayRemoved(TEST_DISPLAY);
+
+ assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+ }
+
private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
final int len = pointersLocation.length;
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
new file mode 100644
index 000000000000..488e5cdf33b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.am;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BatteryStatsImpl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(AndroidJUnit4.class)
+public final class BatteryStatsServiceTest {
+
+ private BatteryStatsService mBatteryStatsService;
+ private HandlerThread mBgThread;
+
+ @Before
+ public void setUp() {
+ final Context context = InstrumentationRegistry.getContext();
+ mBgThread = new HandlerThread("bg thread");
+ mBgThread.start();
+ mBatteryStatsService = new BatteryStatsService(context,
+ context.getCacheDir(), new Handler(mBgThread.getLooper()));
+ }
+
+ @After
+ public void tearDown() {
+ mBatteryStatsService.shutdown();
+ mBgThread.quitSafely();
+ }
+
+ @Test
+ public void testAwaitCompletion() throws Exception {
+ final CountDownLatch readyLatch = new CountDownLatch(2);
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch testLatch = new CountDownLatch(1);
+ final AtomicBoolean quiting = new AtomicBoolean(false);
+ final AtomicBoolean finished = new AtomicBoolean(false);
+ final int uid = Process.myUid();
+ final Thread noteThread = new Thread(() -> {
+ final int maxIterations = 1000;
+ final int eventCode = 12345;
+ final String eventName = "placeholder";
+ final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+
+ readyLatch.countDown();
+ try {
+ startLatch.await();
+ } catch (InterruptedException e) {
+ }
+
+ for (int i = 0; i < maxIterations && !quiting.get(); i++) {
+ synchronized (stats) {
+ mBatteryStatsService.noteEvent(eventCode, eventName, uid);
+ }
+ }
+ finished.set(true);
+ });
+ final Thread waitThread = new Thread(() -> {
+ readyLatch.countDown();
+ try {
+ startLatch.await();
+ } catch (InterruptedException e) {
+ }
+
+ do {
+ mBatteryStatsService.takeUidSnapshot(uid);
+ } while (!finished.get() && !quiting.get());
+
+ if (!quiting.get()) {
+ // do one more to ensure we've cleared the queue
+ mBatteryStatsService.takeUidSnapshot(uid);
+ }
+
+ testLatch.countDown();
+ });
+ noteThread.start();
+ waitThread.start();
+ readyLatch.await();
+ startLatch.countDown();
+
+ try {
+ assertTrue("Timed out in waiting for the completion of battery event handling",
+ testLatch.await(10 * 1000, TimeUnit.MILLISECONDS));
+ } finally {
+ quiting.set(true);
+ noteThread.interrupt();
+ noteThread.join(1000);
+ waitThread.interrupt();
+ waitThread.join(1000);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java
new file mode 100644
index 000000000000..24f78308c600
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/AppSearchImplTest.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 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.appsearch.external.localbackend;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.exceptions.AppSearchException;
+
+import com.android.server.appsearch.proto.DocumentProto;
+import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
+import com.android.server.appsearch.proto.IndexingConfig;
+import com.android.server.appsearch.proto.PropertyConfigProto;
+import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.ResultSpecProto;
+import com.android.server.appsearch.proto.SchemaProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.ScoringSpecProto;
+import com.android.server.appsearch.proto.SearchResultProto;
+import com.android.server.appsearch.proto.SearchSpecProto;
+import com.android.server.appsearch.proto.StatusProto;
+import com.android.server.appsearch.proto.TermMatchType;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.Set;
+
+public class AppSearchImplTest {
+ @Rule
+ public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private AppSearchImpl mAppSearchImpl;
+
+ @Before
+ public void setUp() throws Exception {
+ mAppSearchImpl = new AppSearchImpl(mTemporaryFolder.newFolder());
+ mAppSearchImpl.initialize();
+ }
+
+ /**
+ * Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While
+ * also keeping any other existing schema types that may already be part of Icing's persisted
+ * schema.
+ */
+ @Test
+ public void testRewriteSchema() throws Exception {
+ SchemaProto.Builder existingSchemaBuilder = mAppSearchImpl.getSchemaProto().toBuilder();
+
+ SchemaProto newSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Foo").build())
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("RefType")
+ .build()
+ ).build()
+ ).build();
+
+ Set<String> newTypes = mAppSearchImpl.rewriteSchema("databaseName", existingSchemaBuilder,
+ newSchema);
+ assertThat(newTypes).containsExactly("databaseName/Foo", "databaseName/TestType");
+
+ SchemaProto expectedSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("databaseName/Foo").build())
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("databaseName/TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("databaseName/RefType")
+ .build()
+ ).build())
+ .build();
+ assertThat(existingSchemaBuilder.getTypesList())
+ .containsExactlyElementsIn(expectedSchema.getTypesList());
+ }
+
+ @Test
+ public void testRewriteDocumentProto() {
+ DocumentProto insideDocument = DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .build();
+ DocumentProto documentProto = DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument))
+ .build();
+
+ DocumentProto expectedInsideDocument = DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("databaseName/type")
+ .setNamespace("databaseName/namespace")
+ .build();
+ DocumentProto expectedDocumentProto = DocumentProto.newBuilder()
+ .setUri("uri")
+ .setSchema("databaseName/type")
+ .setNamespace("databaseName/namespace")
+ .addProperties(PropertyProto.newBuilder().addDocumentValues(expectedInsideDocument))
+ .build();
+
+ DocumentProto.Builder actualDocument = documentProto.toBuilder();
+ mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/true);
+ assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
+ mAppSearchImpl.rewriteDocumentTypes("databaseName/", actualDocument, /*add=*/false);
+ assertThat(actualDocument.build()).isEqualTo(documentProto);
+ }
+
+ @Test
+ public void testOptimize() throws Exception {
+ // Insert schema
+ SchemaProto schema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("type").build())
+ .build();
+ mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
+
+ // Insert enough documents.
+ for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+ + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+ DocumentProto insideDocument = DocumentProto.newBuilder()
+ .setUri("inside-uri" + i)
+ .setSchema("type")
+ .setNamespace("namespace")
+ .build();
+ mAppSearchImpl.putDocument("database", insideDocument);
+ }
+
+ // Check optimize() will release 0 docs since there is no deletion.
+ GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
+
+ // delete 999 documents , we will reach the threshold to trigger optimize() in next
+ // deletion.
+ for (int i = 0; i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1; i++) {
+ mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
+ }
+
+ // optimize() still not be triggered since we are in the interval to call getOptimizeInfo()
+ optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ assertThat(optimizeInfo.getOptimizableDocs())
+ .isEqualTo(AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT - 1);
+
+ // Keep delete docs, will reach the interval this time and trigger optimize().
+ for (int i = AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT;
+ i < AppSearchImpl.OPTIMIZE_THRESHOLD_DOC_COUNT
+ + AppSearchImpl.CHECK_OPTIMIZE_INTERVAL; i++) {
+ mAppSearchImpl.remove("database", "namespace", "inside-uri" + i);
+ }
+
+ // Verify optimize() is triggered
+ optimizeInfo = mAppSearchImpl.getOptimizeInfoResult();
+ assertThat(optimizeInfo.getOptimizableDocs())
+ .isLessThan((long) AppSearchImpl.CHECK_OPTIMIZE_INTERVAL);
+ }
+
+ @Test
+ public void testRewriteSearchSpec() throws Exception {
+ SearchSpecProto.Builder searchSpecProto =
+ SearchSpecProto.newBuilder().setQuery("");
+
+ // Insert schema
+ SchemaProto schema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("type").build())
+ .build();
+ mAppSearchImpl.setSchema("database", schema, /*forceOverride=*/false);
+ // Insert document
+ DocumentProto insideDocument = DocumentProto.newBuilder()
+ .setUri("inside-uri")
+ .setSchema("type")
+ .setNamespace("namespace")
+ .build();
+ mAppSearchImpl.putDocument("database", insideDocument);
+
+ // Rewrite SearchSpec
+ mAppSearchImpl.rewriteSearchSpecForNonEmptyDatabase(
+ "database", searchSpecProto);
+ assertThat(searchSpecProto.getSchemaTypeFiltersList()).containsExactly("database/type");
+ assertThat(searchSpecProto.getNamespaceFiltersList()).containsExactly("database/namespace");
+ }
+
+ @Test
+ public void testQueryEmptyDatabase() throws Exception {
+ SearchResultProto searchResultProto = mAppSearchImpl.query("EmptyDatabase",
+ SearchSpecProto.getDefaultInstance(),
+ ResultSpecProto.getDefaultInstance(), ScoringSpecProto.getDefaultInstance());
+ assertThat(searchResultProto.getResultsCount()).isEqualTo(0);
+ assertThat(searchResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
+ }
+
+ @Test
+ public void testRemoveEmptyDatabase_NoExceptionThrown() throws Exception {
+ mAppSearchImpl.removeByType("EmptyDatabase", "FakeType");
+ mAppSearchImpl.removeByNamespace("EmptyDatabase", "FakeNamespace");
+ mAppSearchImpl.removeAll("EmptyDatabase");
+ }
+
+ @Test
+ public void testSetSchema() throws Exception {
+ // Create schemas
+ SchemaProto schemaProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
+
+ // Set schema Email to AppSearch database1
+ mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
+
+ // Create excepted schemaType proto.
+ SchemaProto exceptedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .build();
+ assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+ .containsExactlyElementsIn(exceptedProto.getTypesList());
+ }
+
+ @Test
+ public void testRemoveSchema() throws Exception {
+ // Create schemas
+ SchemaProto schemaProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
+
+ // Set schema Email and Document to AppSearch database1
+ mAppSearchImpl.setSchema("database1", schemaProto, /*forceOverride=*/false);
+
+ // Create excepted schemaType proto.
+ SchemaProto exceptedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
+ .build();
+
+ // Check both schema Email and Document saved correctly.
+ assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+ .containsExactlyElementsIn(exceptedProto.getTypesList());
+
+ // Save only Email this time.
+ schemaProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email")).build();
+
+ // Check the incompatible error has been thrown.
+ SchemaProto finalSchemaProto = schemaProto;
+ AppSearchException e = expectThrows(AppSearchException.class, () ->
+ mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/false));
+ assertThat(e).hasMessageThat().isEqualTo("Schema is incompatible.");
+
+ // ForceOverride to delete.
+ mAppSearchImpl.setSchema("database1", finalSchemaProto, /*forceOverride=*/true);
+
+ // Check Document schema is removed.
+ exceptedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .build();
+ assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+ .containsExactlyElementsIn(exceptedProto.getTypesList());
+ }
+
+ @Test
+ public void testRemoveSchema_differentDataBase() throws Exception {
+ // Create schemas
+ SchemaProto emailAndDocSchemaProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Document")).build();
+
+ // Set schema Email and Document to AppSearch database1 and 2
+ mAppSearchImpl.setSchema("database1", emailAndDocSchemaProto, /*forceOverride=*/false);
+ mAppSearchImpl.setSchema("database2", emailAndDocSchemaProto, /*forceOverride=*/false);
+
+ // Create excepted schemaType proto.
+ SchemaProto exceptedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Document"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+ .build();
+
+ // Check Email and Document is saved in database 1 and 2 correctly.
+ assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+ .containsExactlyElementsIn(exceptedProto.getTypesList());
+
+ // Save only Email to database1 this time.
+ SchemaProto emailSchemaProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Email"))
+ .build();
+ mAppSearchImpl.setSchema("database1", emailSchemaProto, /*forceOverride=*/true);
+
+ // Create excepted schemaType list, database 1 should only contain Email but database 2
+ // remains in same.
+ exceptedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database1/Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Email"))
+ .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("database2/Document"))
+ .build();
+
+ // Check nothing changed in database2.
+ assertThat(mAppSearchImpl.getSchemaProto().getTypesList())
+ .containsExactlyElementsIn(exceptedProto.getTypesList());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverterTest.java
new file mode 100644
index 000000000000..8b2fd1ce9fef
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localbackend/converter/SchemaToProtoConverterTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 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.appsearch.external.localbackend.converter;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearchSchema;
+
+import com.android.server.appsearch.proto.IndexingConfig;
+import com.android.server.appsearch.proto.PropertyConfigProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
+import com.android.server.appsearch.proto.TermMatchType;
+
+import org.junit.Test;
+
+public class SchemaToProtoConverterTest {
+ @Test
+ public void testGetProto_Email() {
+ AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email")
+ .addProperty(new AppSearchSchema.PropertyConfig.Builder("subject")
+ .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()
+ ).addProperty(new AppSearchSchema.PropertyConfig.Builder("body")
+ .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()
+ ).build();
+
+ SchemaTypeConfigProto expectedEmailProto = SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Email")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setSchemaType("")
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.android.server.appsearch.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("body")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setSchemaType("")
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.android.server.appsearch.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).build();
+
+ assertThat(SchemaToProtoConverter.convert(emailSchema)).isEqualTo(expectedEmailProto);
+ }
+
+ @Test
+ public void testGetProto_MusicRecording() {
+ AppSearchSchema musicRecordingSchema = new AppSearchSchema.Builder("MusicRecording")
+ .addProperty(new AppSearchSchema.PropertyConfig.Builder("artist")
+ .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
+ .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_PLAIN)
+ .build()
+ ).addProperty(new AppSearchSchema.PropertyConfig.Builder("pubDate")
+ .setDataType(AppSearchSchema.PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingType(AppSearchSchema.PropertyConfig.INDEXING_TYPE_NONE)
+ .setTokenizerType(AppSearchSchema.PropertyConfig.TOKENIZER_TYPE_NONE)
+ .build()
+ ).build();
+
+ SchemaTypeConfigProto expectedMusicRecordingProto = SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("MusicRecording")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("artist")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setSchemaType("")
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setIndexingConfig(
+ com.android.server.appsearch.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("pubDate")
+ .setDataType(PropertyConfigProto.DataType.Code.INT64)
+ .setSchemaType("")
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.android.server.appsearch.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(IndexingConfig.TokenizerType.Code.NONE)
+ .setTermMatchType(TermMatchType.Code.UNKNOWN)
+ )
+ ).build();
+
+ assertThat(SchemaToProtoConverter.convert(musicRecordingSchema))
+ .isEqualTo(expectedMusicRecordingProto);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
deleted file mode 100644
index 8986cbae262d..000000000000
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 com.android.server.appsearch.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.appsearch.proto.IndexingConfig;
-import com.android.server.appsearch.proto.PropertyConfigProto;
-import com.android.server.appsearch.proto.SchemaProto;
-import com.android.server.appsearch.proto.SchemaTypeConfigProto;
-import com.android.server.appsearch.proto.TermMatchType;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class AppSearchImplTest {
- private final Context mContext = InstrumentationRegistry.getContext();
- private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
-
- @Test
- public void testRewriteSchemaTypes() {
- SchemaProto inSchema = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder()
- .setSchemaType("TestType")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("subject")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- IndexingConfig.newBuilder()
- .setTokenizerType(
- IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- .build()
- ).build()
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("link")
- .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setSchemaType("RefType")
- .build()
- ).build()
- ).build();
-
- SchemaProto expectedSchema = SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder()
- .setSchemaType("com.android.server.appsearch.impl@42:TestType")
- .addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("subject")
- .setDataType(PropertyConfigProto.DataType.Code.STRING)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setIndexingConfig(
- IndexingConfig.newBuilder()
- .setTokenizerType(
- IndexingConfig.TokenizerType.Code.PLAIN)
- .setTermMatchType(TermMatchType.Code.PREFIX)
- .build()
- ).build()
- ).addProperties(PropertyConfigProto.newBuilder()
- .setPropertyName("link")
- .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
- .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
- .setSchemaType("com.android.server.appsearch.impl@42:RefType")
- .build()
- ).build()
- ).build();
-
- AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
- SchemaProto.Builder actualSchema = inSchema.toBuilder();
- impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
-
- assertThat(actualSchema.build()).isEqualTo(expectedSchema);
- }
-
- @Test
- public void testPackageNotFound() {
- AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
- IllegalStateException e = expectThrows(
- IllegalStateException.class,
- () -> impl.setSchema(
- /*callingUid=*/Integer.MAX_VALUE,
- SchemaProto.getDefaultInstance(),
- /*forceOverride=*/false));
- assertThat(e).hasMessageThat().contains("Failed to look up package name");
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
deleted file mode 100644
index 3196fbee8a02..000000000000
--- a/services/tests/servicestests/src/com/android/server/appsearch/impl/FakeIcingTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 com.android.server.appsearch.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.appsearch.proto.DocumentProto;
-import com.android.server.appsearch.proto.PropertyProto;
-import com.android.server.appsearch.proto.SearchResultProto;
-import com.android.server.appsearch.proto.StatusProto;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class FakeIcingTest {
- @Test
- public void query() {
- FakeIcing icing = new FakeIcing();
- icing.put(createDoc("uri:cat", "The cat said meow"));
- icing.put(createDoc("uri:dog", "The dog said woof"));
-
- assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
- assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
- assertThat(queryGetUris(icing, "fred")).isEmpty();
- }
-
- @Test
- public void queryNorm() {
- FakeIcing icing = new FakeIcing();
- icing.put(createDoc("uri:cat", "The cat said meow"));
- icing.put(createDoc("uri:dog", "The dog said woof"));
-
- assertThat(queryGetUris(icing, "the")).containsExactly("uri:cat", "uri:dog");
- assertThat(queryGetUris(icing, "The")).containsExactly("uri:cat", "uri:dog");
- assertThat(queryGetUris(icing, "tHe")).containsExactly("uri:cat", "uri:dog");
- }
-
- @Test
- public void get() {
- DocumentProto cat = createDoc("uri:cat", "The cat said meow");
- FakeIcing icing = new FakeIcing();
- icing.put(cat);
- assertThat(icing.get("uri:cat")).isEqualTo(cat);
- }
-
- @Test
- public void replace() {
- DocumentProto cat = createDoc("uri:cat", "The cat said meow");
- DocumentProto dog = createDoc("uri:dog", "The dog said woof");
-
- FakeIcing icing = new FakeIcing();
- icing.put(cat);
- icing.put(dog);
-
- assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
- assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
- assertThat(icing.get("uri:cat")).isEqualTo(cat);
-
- // Replace
- DocumentProto cat2 = createDoc("uri:cat", "The cat said purr");
- DocumentProto bird = createDoc("uri:bird", "The cat said tweet");
- icing.put(cat2);
- icing.put(bird);
-
- assertThat(queryGetUris(icing, "meow")).isEmpty();
- assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog", "uri:bird");
- assertThat(icing.get("uri:cat")).isEqualTo(cat2);
- }
-
- @Test
- public void delete() {
- DocumentProto cat = createDoc("uri:cat", "The cat said meow");
- DocumentProto dog = createDoc("uri:dog", "The dog said woof");
-
- FakeIcing icing = new FakeIcing();
- icing.put(cat);
- icing.put(dog);
-
- assertThat(queryGetUris(icing, "meow")).containsExactly("uri:cat");
- assertThat(queryGetUris(icing, "said")).containsExactly("uri:cat", "uri:dog");
- assertThat(icing.get("uri:cat")).isEqualTo(cat);
-
- // Delete
- icing.delete("uri:cat");
- icing.delete("uri:notreal");
-
- assertThat(queryGetUris(icing, "meow")).isEmpty();
- assertThat(queryGetUris(icing, "said")).containsExactly("uri:dog");
- assertThat(icing.get("uri:cat")).isNull();
- }
-
- private static DocumentProto createDoc(String uri, String body) {
- return DocumentProto.newBuilder()
- .setUri(uri)
- .addProperties(PropertyProto.newBuilder().addStringValues(body))
- .build();
- }
-
- private static List<String> queryGetUris(FakeIcing icing, String term) {
- List<String> uris = new ArrayList<>();
- SearchResultProto results = icing.query(term);
- assertThat(results.getStatus().getCode()).isEqualTo(StatusProto.Code.OK);
- for (SearchResultProto.ResultProto result : results.getResultsList()) {
- uris.add(result.getDocument().getUri());
- }
- return uris;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
index 922d71554426..9ee1205b4325 100644
--- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java
@@ -116,7 +116,7 @@ public class AttentionManagerServiceTest {
@Test
public void testCheckAttention_returnFalseWhenPowerManagerNotInteract() throws RemoteException {
- doReturn(true).when(mSpyAttentionManager).isAttentionServiceSupported();
+ mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(false).when(mMockIPowerManager).isInteractive();
AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class);
assertThat(mSpyAttentionManager.checkAttention(mTimeout, callback)).isFalse();
@@ -124,7 +124,7 @@ public class AttentionManagerServiceTest {
@Test
public void testCheckAttention_callOnSuccess() throws RemoteException {
- doReturn(true).when(mSpyAttentionManager).isAttentionServiceSupported();
+ mSpyAttentionManager.mIsServiceEnabled = true;
doReturn(true).when(mSpyAttentionManager).isServiceAvailable();
doReturn(true).when(mMockIPowerManager).isInteractive();
doNothing().when(mSpyAttentionManager).freeIfInactiveLocked();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index cc1fdab3d22a..e011c797777e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -123,8 +123,7 @@ public class AuthServiceTest {
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
- "1:4:255", // ID1:Iris:Weak
- "2:8:4095", // ID2:Face:Convenience
+ "1:8:4095", // ID2:Face:Convenience
};
when(mInjector.getConfiguration(any())).thenReturn(config);
@@ -133,12 +132,14 @@ public class AuthServiceTest {
mAuthService.onStart();
final int fingerprintId = 0;
- final int irisId = 1;
- final int faceId = 2;
+ final int faceId = 1;
- verify(mFingerprintService).initializeConfiguration(eq(fingerprintId));
- verify(mIrisService).initializeConfiguration(eq(irisId));
- verify(mFaceService).initializeConfiguration(eq(faceId));
+ final int fingerprintStrength = 15;
+ final int faceStrength = 4095;
+
+ verify(mFingerprintService).initializeConfiguration(eq(fingerprintId),
+ eq(fingerprintStrength));
+ verify(mFaceService).initializeConfiguration(eq(faceId), eq(faceStrength));
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/HardwareAuthTokenUtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/HardwareAuthTokenUtilsTest.java
new file mode 100644
index 000000000000..84987e641544
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/HardwareAuthTokenUtilsTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.biometrics;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.hardware.keymaster.HardwareAuthToken;
+import android.hardware.keymaster.Timestamp;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class HardwareAuthTokenUtilsTest {
+
+ @Test
+ public void testByteArrayLoopBack() {
+ final byte[] hat = new byte[69];
+ for (int i = 0; i < 69; i++) {
+ hat[i] = (byte) i;
+ }
+
+ final HardwareAuthToken hardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hat);
+ final byte[] hat2 = HardwareAuthTokenUtils.toByteArray(hardwareAuthToken);
+
+ for (int i = 0; i < hat.length; i++) {
+ assertEquals(hat[i], hat2[i]);
+ }
+ }
+
+ @Test
+ public void testHardwareAuthTokenLoopBack() {
+ final long testChallenge = 1000L;
+ final long testUserId = 2000L;
+ final long testAuthenticatorId = 3000L;
+ final int testAuthenticatorType = 4000;
+ final long testTimestamp = 5000L;
+
+ final HardwareAuthToken hardwareAuthToken = new HardwareAuthToken();
+ hardwareAuthToken.challenge = testChallenge;
+ hardwareAuthToken.userId = testUserId;
+ hardwareAuthToken.authenticatorId = testAuthenticatorId;
+ hardwareAuthToken.authenticatorType = testAuthenticatorType;
+ hardwareAuthToken.timestamp = new Timestamp();
+ hardwareAuthToken.timestamp.milliSeconds = testTimestamp;
+ hardwareAuthToken.mac = new byte[32];
+
+ for (int i = 0; i < hardwareAuthToken.mac.length; i++) {
+ hardwareAuthToken.mac[i] = (byte) i;
+ }
+
+ final byte[] hat = HardwareAuthTokenUtils.toByteArray(hardwareAuthToken);
+ final HardwareAuthToken hardwareAuthToken2 =
+ HardwareAuthTokenUtils.toHardwareAuthToken(hat);
+
+ assertEquals(testChallenge, hardwareAuthToken2.challenge);
+ assertEquals(testUserId, hardwareAuthToken2.userId);
+ assertEquals(testAuthenticatorId, hardwareAuthToken2.authenticatorId);
+ assertEquals(testAuthenticatorType, hardwareAuthToken2.authenticatorType);
+ assertEquals(testTimestamp, hardwareAuthToken2.timestamp.milliSeconds);
+
+ for (int i = 0; i < hardwareAuthToken.mac.length; i++) {
+ assertEquals(hardwareAuthToken.mac[i], hardwareAuthToken2.mac[i]);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 36c55cce076b..c890c52f2181 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -16,11 +16,21 @@
package com.android.server.biometrics.sensors;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.content.Context;
+import android.hardware.biometrics.IBiometricService;
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -40,18 +50,21 @@ public class BiometricSchedulerTest {
@Mock
private Context mContext;
@Mock
- private ClientMonitor.LazyDaemon<Object> mLazyDaemon;
+ private IBiometricService mBiometricService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */);
+ mScheduler = new BiometricScheduler(TAG, null /* gestureAvailabilityTracker */,
+ mBiometricService);
}
@Test
public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
- final ClientMonitor<Object> client1 = new TestClientMonitor(mContext, mLazyDaemon);
- final ClientMonitor<Object> client2 = new TestClientMonitor(mContext, mLazyDaemon);
+ final ClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+
+ final ClientMonitor<Object> client1 = new TestClientMonitor(mContext, nonNullDaemon);
+ final ClientMonitor<Object> client2 = new TestClientMonitor(mContext, nonNullDaemon);
mScheduler.scheduleClientMonitor(client1);
mScheduler.scheduleClientMonitor(client2);
@@ -59,7 +72,93 @@ public class BiometricSchedulerTest {
client1.mCallback.onClientFinished(client1, true /* success */);
}
+ @Test
+ public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
+ // Even if second client has a non-null daemon, it needs to be canceled.
+ Object daemon2 = mock(Object.class);
+
+ final ClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
+ final ClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
+
+ final TestClientMonitor client1 = new TestClientMonitor(mContext, lazyDaemon1);
+ final TestClientMonitor client2 = new TestClientMonitor(mContext, lazyDaemon2);
+
+ final ClientMonitor.Callback callback1 = mock(ClientMonitor.Callback.class);
+ final ClientMonitor.Callback callback2 = mock(ClientMonitor.Callback.class);
+
+ // Pretend the scheduler is busy so the first operation doesn't start right away. We want
+ // to pretend like there are two operations in the queue before kicking things off
+ mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+ mock(ClientMonitor.class), mock(ClientMonitor.Callback.class));
+
+ mScheduler.scheduleClientMonitor(client1, callback1);
+ assertEquals(1, mScheduler.mPendingOperations.size());
+ // client1 is pending. Allow the scheduler to start once second client is added.
+ mScheduler.mCurrentOperation = null;
+ mScheduler.scheduleClientMonitor(client2, callback2);
+ waitForIdle();
+
+ assertTrue(client1.wasUnableToStart());
+ verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
+ verify(callback1, never()).onClientStarted(any());
+
+ assertTrue(client2.wasUnableToStart());
+ verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
+ verify(callback2, never()).onClientStarted(any());
+
+ assertTrue(mScheduler.mPendingOperations.isEmpty());
+ }
+
+ @Test
+ public void testRemovesOnlyBiometricPromptOperation_whenNullHal() {
+ // Second non-BiometricPrompt client has a valid daemon
+ final Object daemon2 = mock(Object.class);
+
+ final ClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> null;
+ final ClientMonitor.LazyDaemon<Object> lazyDaemon2 = () -> daemon2;
+
+ final TestClientMonitor client1 =
+ new TestBiometricPromptClientMonitor(mContext, lazyDaemon1);
+ final TestClientMonitor client2 = new TestClientMonitor(mContext, lazyDaemon2);
+
+ final ClientMonitor.Callback callback1 = mock(ClientMonitor.Callback.class);
+ final ClientMonitor.Callback callback2 = mock(ClientMonitor.Callback.class);
+
+ // Pretend the scheduler is busy so the first operation doesn't start right away. We want
+ // to pretend like there are two operations in the queue before kicking things off
+ mScheduler.mCurrentOperation = new BiometricScheduler.Operation(
+ mock(ClientMonitor.class), mock(ClientMonitor.Callback.class));
+
+ mScheduler.scheduleClientMonitor(client1, callback1);
+ assertEquals(1, mScheduler.mPendingOperations.size());
+ // client1 is pending. Allow the scheduler to start once second client is added.
+ mScheduler.mCurrentOperation = null;
+ mScheduler.scheduleClientMonitor(client2, callback2);
+ waitForIdle();
+
+ // Simulate that the BiometricPrompt client's sensor is ready
+ mScheduler.startPreparedClient(client1.getCookie());
+
+ assertTrue(client1.wasUnableToStart());
+ verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
+ verify(callback1, never()).onClientStarted(any());
+
+ // Client 2 was able to start
+ assertFalse(client2.wasUnableToStart());
+ assertTrue(client2.hasStarted());
+ verify(callback2).onClientStarted(eq(client2));
+ }
+
+ private static class TestBiometricPromptClientMonitor extends TestClientMonitor {
+ public TestBiometricPromptClientMonitor(@NonNull Context context,
+ @NonNull LazyDaemon<Object> lazyDaemon) {
+ super(context, lazyDaemon, 1 /* cookie */);
+ }
+ }
+
private static class TestClientMonitor extends ClientMonitor<Object> {
+ private boolean mUnableToStart;
+ private boolean mStarted;
public TestClientMonitor(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon) {
super(context, lazyDaemon, null /* token */, null /* listener */, 0 /* userId */,
@@ -67,14 +166,42 @@ public class BiometricSchedulerTest {
0 /* statsAction */, 0 /* statsClient */);
}
+ public TestClientMonitor(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon,
+ int cookie) {
+ super(context, lazyDaemon, null /* token */, null /* listener */, 0 /* userId */,
+ TAG, cookie, 0 /* sensorId */, 0 /* statsModality */,
+ 0 /* statsAction */, 0 /* statsClient */);
+ }
+
+
@Override
public void unableToStart() {
+ assertFalse(mUnableToStart);
+ mUnableToStart = true;
+ }
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ assertFalse(mStarted);
+ mStarted = true;
}
@Override
protected void startHalOperation() {
}
+
+ public boolean wasUnableToStart() {
+ return mUnableToStart;
+ }
+
+ public boolean hasStarted() {
+ return mStarted;
+ }
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
new file mode 100644
index 000000000000..7a0d8946d179
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/Face10Test.java
@@ -0,0 +1,69 @@
+/*
+ * 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.biometrics.sensors.face;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class Face10Test {
+
+ private static final String TAG = "Face10Test";
+ private static final int SENSOR_ID = 1;
+
+ @Mock
+ private Context mContext;
+
+ private LockoutResetDispatcher mLockoutResetDispatcher;
+ private Face10 mFace10;
+ private IBinder mBinder;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
+ mFace10 = new Face10(mContext, SENSOR_ID, BiometricManager.Authenticators.BIOMETRIC_STRONG,
+ mLockoutResetDispatcher, false /* supportsSelfIllumination */,
+ 1 /* maxTemplatesAllowed */);
+ mBinder = new Binder();
+ }
+
+ @Test
+ public void scheduleRevokeChallenge_doesNotCrash() {
+ mFace10.scheduleRevokeChallenge(mBinder, TAG);
+ waitForIdle();
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 4ce6411dc306..631b4d48e9f2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4407,7 +4407,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
// Caller is Profile Owner, but no supervision app is configured.
setAsProfileOwner(admin1);
- assertExpectException(SecurityException.class, "no default supervision component defined",
+ assertExpectException(SecurityException.class, "is not the default supervision component",
() -> dpm.setSecondaryLockscreenEnabled(admin1, true));
assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(CALLER_USER_HANDLE)));
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 09b6d7b0cd7e..cb49a519d10a 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -113,7 +113,7 @@ public class DpmMockContext extends MockContext {
}
public void withCleanCallingIdentity(@NonNull FunctionalUtils.ThrowingRunnable action) {
- long callingIdentity = clearCallingIdentity();
+ final long callingIdentity = clearCallingIdentity();
Throwable throwableToPropagate = null;
try {
action.runOrThrow();
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
new file mode 100644
index 000000000000..058794a3b9e9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.devicestate;
+
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link DeviceStateManagerService}.
+ * <p/>
+ * Run with <code>atest DeviceStateManagerServiceTest</code>.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class DeviceStateManagerServiceTest {
+ private static final int DEFAULT_DEVICE_STATE = 0;
+ private static final int OTHER_DEVICE_STATE = 1;
+ private static final int UNSUPPORTED_DEVICE_STATE = 999;
+
+ private TestDeviceStatePolicy mPolicy;
+ private TestDeviceStateProvider mProvider;
+ private DeviceStateManagerService mService;
+
+ @Before
+ public void setup() {
+ mProvider = new TestDeviceStateProvider();
+ mPolicy = new TestDeviceStatePolicy(mProvider);
+ mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
+ mService.onStart();
+ }
+
+ @Test
+ public void requestStateChange() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_pendingState() {
+ mPolicy.blockConfigure();
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+ mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+
+ mPolicy.resumeConfigure();
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_unsupportedState() {
+ mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE);
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void requestStateChange_invalidState() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mProvider.notifyRequestState(INVALID_DEVICE_STATE);
+ });
+ }
+
+ @Test
+ public void supportedStatesChanged() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+
+ // The current committed and requests states do not change because the current state remains
+ // supported.
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+ }
+
+ @Test
+ public void supportedStatesChanged_invalidState() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ mProvider.notifySupportedDeviceStates(new int []{ INVALID_DEVICE_STATE });
+ });
+ }
+
+ @Test
+ public void supportedStatesChanged_unsupportedRequestedState() {
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+
+ mProvider.notifySupportedDeviceStates(new int []{ OTHER_DEVICE_STATE });
+
+ // The current requested state is cleared because it is no longer supported.
+ assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), INVALID_DEVICE_STATE);
+
+ mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+
+ assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+ assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
+ assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
+ }
+
+ private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
+ private final DeviceStateProvider mProvider;
+ private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
+ private boolean mConfigureBlocked = false;
+ private Runnable mPendingConfigureCompleteRunnable;
+
+ TestDeviceStatePolicy(DeviceStateProvider provider) {
+ mProvider = provider;
+ }
+
+ @Override
+ public DeviceStateProvider getDeviceStateProvider() {
+ return mProvider;
+ }
+
+ public void blockConfigure() {
+ mConfigureBlocked = true;
+ }
+
+ public void resumeConfigure() {
+ mConfigureBlocked = false;
+ if (mPendingConfigureCompleteRunnable != null) {
+ Runnable onComplete = mPendingConfigureCompleteRunnable;
+ mPendingConfigureCompleteRunnable = null;
+ onComplete.run();
+ }
+ }
+
+ public int getMostRecentRequestedStateToConfigure() {
+ return mLastDeviceStateRequestedToConfigure;
+ }
+
+ @Override
+ public void configureDeviceForState(int state, Runnable onComplete) {
+ if (mPendingConfigureCompleteRunnable != null) {
+ throw new IllegalStateException("configureDeviceForState() called while configure"
+ + " is pending");
+ }
+
+ mLastDeviceStateRequestedToConfigure = state;
+ if (mConfigureBlocked) {
+ mPendingConfigureCompleteRunnable = onComplete;
+ return;
+ }
+ onComplete.run();
+ }
+ }
+
+ private static final class TestDeviceStateProvider implements DeviceStateProvider {
+ private int[] mSupportedDeviceStates = new int[]{ DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE };
+ private int mCurrentDeviceState = DEFAULT_DEVICE_STATE;
+ private Listener mListener;
+
+ @Override
+ public void setListener(Listener listener) {
+ if (mListener != null) {
+ throw new IllegalArgumentException("Provider already has listener set.");
+ }
+
+ mListener = listener;
+ mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
+ mListener.onStateChanged(mCurrentDeviceState);
+ }
+
+ public void notifySupportedDeviceStates(int[] supportedDeviceStates) {
+ mSupportedDeviceStates = supportedDeviceStates;
+ mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
+ }
+
+ public void notifyRequestState(int state) {
+ mCurrentDeviceState = state;
+ mListener.onStateChanged(state);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index b69cc47ba738..ec747acd376d 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -83,8 +83,8 @@ public class AutomaticBrightnessControllerTest {
BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
- mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext,
- mDisplayDeviceConfig);
+ mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mContext
+ );
controller.setLoggingEnabled(true);
// Configure the brightness controller and grab an instance of the sensor listener,
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 73dda0736d2f..13b019897600 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -309,7 +309,8 @@ public class DisplayManagerServiceTest {
zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY;
displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
- displayManager.handleDisplayDeviceAdded(displayDevice);
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
// Find the display id of the added FakeDisplayDevice
DisplayManagerService.BinderService bs = displayManager.new BinderService();
@@ -342,7 +343,8 @@ public class DisplayManagerServiceTest {
displayDeviceInfo2.copyFrom(displayDeviceInfo);
displayDeviceInfo2.displayCutout = null;
displayDevice.setDisplayDeviceInfo(displayDeviceInfo2);
- displayManager.handleDisplayDeviceChanged(displayDevice);
+ displayManager.getDisplayDeviceRepository()
+ .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED);
handler.runWithScissors(() -> {
}, 0 /* now */);
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 43a396d8e5d7..b8dbd6267bc5 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -302,4 +302,42 @@ public class DisplayModeDirectorTest {
verifyBrightnessObserverCall(director, 90, 90, 0, 90, 90);
verifyBrightnessObserverCall(director, 120, 90, 0, 120, 90);
}
+
+ @Test
+ public void testVotingWithAlwaysRespectAppRequest() {
+ final int displayId = 0;
+ DisplayModeDirector director = createDirectorFromFpsRange(50, 90);
+ SparseArray<Vote> votes = new SparseArray<>();
+ SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>();
+ votesByDisplay.put(displayId, votes);
+ votes.put(Vote.PRIORITY_LOW_BRIGHTNESS, Vote.forRefreshRates(0, 60));
+ votes.put(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE, Vote.forRefreshRates(60, 90));
+ votes.put(Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, Vote.forRefreshRates(90, 90));
+ votes.put(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE, Vote.forRefreshRates(60, 60));
+ votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRefreshRates(0, 60));
+
+
+ director.injectVotesByDisplay(votesByDisplay);
+ Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+ DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+
+ director.setShouldAlwaysRespectAppRequestedMode(true);
+ Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isTrue();
+ desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+ Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(90);
+
+ director.setShouldAlwaysRespectAppRequestedMode(false);
+ Truth.assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse();
+
+ desiredSpecs = director.getDesiredDisplayModeSpecs(displayId);
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.primaryRefreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(60);
+ Truth.assertThat(desiredSpecs.baseModeId).isEqualTo(60);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index 301a9fe64d5e..ac4501723c90 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -28,7 +28,8 @@ import android.view.SurfaceControl;
import org.junit.Before;
import org.junit.Test;
-import java.util.ArrayList;
+import java.io.InputStream;
+import java.io.OutputStream;
public class LogicalDisplayTest {
private static final int DISPLAY_ID = 0;
@@ -51,9 +52,24 @@ public class LogicalDisplayTest {
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
- ArrayList<DisplayDevice> displayDevices = new ArrayList<>();
- displayDevices.add(mDisplayDevice);
- mLogicalDisplay.updateLocked(displayDevices);
+ DisplayDeviceRepository repo = new DisplayDeviceRepository(
+ new DisplayManagerService.SyncRoot(),
+ new PersistentDataStore(new PersistentDataStore.Injector() {
+ @Override
+ public InputStream openRead() {
+ return null;
+ }
+
+ @Override
+ public OutputStream startWrite() {
+ return null;
+ }
+
+ @Override
+ public void finishWrite(OutputStream os, boolean success) {}
+ }));
+ repo.onDisplayDeviceEvent(mDisplayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
+ mLogicalDisplay.updateLocked(repo);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
index 7b88a0e012de..4f0cb324f17f 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java
@@ -32,9 +32,11 @@ public class GlobalSaturationTintControllerTest {
public void setAndGetMatrix() {
final GlobalSaturationTintController tintController = new GlobalSaturationTintController();
tintController.setMatrix(50);
- assertThat(tintController.getMatrix()).hasValuesWithin(0.00001f)
- .of(new float[]{0.6155f, 0.1155f, 0.1155f, 0.0f, 0.3575f, 0.85749996f, 0.3575f,
- 0.0f, 0.036f, 0.036f, 0.536f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(
+ 0.6155f, 0.1155f, 0.1155f, 0.0f, 0.3575f, 0.85749996f, 0.3575f,
+ 0.0f, 0.036f, 0.036f, 0.536f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
+ .inOrder();
}
@Test
@@ -43,6 +45,7 @@ public class GlobalSaturationTintControllerTest {
tintController.setMatrix(100);
final float[] matrix = new float[16];
Matrix.setIdentityM(matrix, 0);
- assertThat(tintController.getMatrix()).hasValuesWithin(0.00001f).of(matrix);
+ assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+ .containsExactly(matrix).inOrder();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 870a27417cd2..2a9c3942211c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -110,7 +110,7 @@ public class ActiveSourceActionTest {
mHdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
- this.mHdmiControlService, mNativeWrapper);
+ this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(hdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -160,7 +160,7 @@ public class ActiveSourceActionTest {
assertThat(playbackDevice.getActiveSource().logicalAddress).isEqualTo(
playbackDevice.mAddress);
assertThat(playbackDevice.getActiveSource().physicalAddress).isEqualTo(mPhysicalAddress);
- assertThat(playbackDevice.mIsActiveSource).isTrue();
+ assertThat(playbackDevice.isActiveSource()).isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index c0d5f7b5aa9e..1385376b740d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -54,8 +54,6 @@ public class ArcInitiationActionFromAvrTest {
private Context mContextSpy;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
- private HdmiCecController mHdmiCecController;
- private HdmiControlService mHdmiControlService;
private FakeNativeWrapper mNativeWrapper;
private ArcInitiationActionFromAvr mAction;
@@ -78,7 +76,7 @@ public class ArcInitiationActionFromAvrTest {
when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
- mHdmiControlService =
+ HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy) {
@Override
boolean isPowerStandby() {
@@ -110,7 +108,7 @@ public class ArcInitiationActionFromAvrTest {
}
};
- mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
protected void setPreferredAddress(int addr) {
}
@@ -118,18 +116,18 @@ public class ArcInitiationActionFromAvrTest {
mHdmiCecLocalDeviceAudioSystem.init();
Looper looper = mTestLooper.getLooper();
- mHdmiControlService.setIoLooper(looper);
+ hdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper);
- mHdmiControlService.setCecController(mHdmiCecController);
- mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
- mHdmiControlService.initPortInfo();
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
+ hdmiControlService.setCecController(hdmiCecController);
+ hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
+ hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
+ hdmiControlService.initPortInfo();
mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
}
@@ -142,7 +140,7 @@ public class ArcInitiationActionFromAvrTest {
assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
- mHdmiControlService.sendCecCommand(
+ mNativeWrapper.onCecMessage(
HdmiCecMessageBuilder.buildReportArcInitiated(
Constants.ADDR_TV,
Constants.ADDR_AUDIO_SYSTEM));
@@ -174,7 +172,7 @@ public class ArcInitiationActionFromAvrTest {
assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
- mHdmiControlService.handleCecCommand(HdmiCecMessageBuilder.buildReportArcTerminated(
+ mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportArcTerminated(
Constants.ADDR_TV,
Constants.ADDR_AUDIO_SYSTEM));
mTestLooper.dispatchAll();
@@ -192,7 +190,7 @@ public class ArcInitiationActionFromAvrTest {
assertThat(mNativeWrapper.getResultMessages()).contains(initiateArc);
- mHdmiControlService.handleCecCommand(
+ mNativeWrapper.onCecMessage(
HdmiCecMessageBuilder.buildFeatureAbortCommand(
Constants.ADDR_TV,
Constants.ADDR_AUDIO_SYSTEM, Constants.MESSAGE_INITIATE_ARC,
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index f986a70804f6..169f885a7253 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -56,8 +56,6 @@ public class ArcTerminationActionFromAvrTest {
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private ArcTerminationActionFromAvr mAction;
- private HdmiCecController mHdmiCecController;
- private HdmiControlService mHdmiControlService;
private FakeNativeWrapper mNativeWrapper;
private TestLooper mTestLooper = new TestLooper();
@@ -79,7 +77,7 @@ public class ArcTerminationActionFromAvrTest {
when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager);
when(mIPowerManagerMock.isInteractive()).thenReturn(true);
- mHdmiControlService =
+ HdmiControlService hdmiControlService =
new HdmiControlService(mContextSpy) {
@Override
void wakeUp() {
@@ -112,16 +110,16 @@ public class ArcTerminationActionFromAvrTest {
};
Looper looper = mTestLooper.getLooper();
- mHdmiControlService.setIoLooper(looper);
+ hdmiControlService.setIoLooper(looper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(this.mHdmiControlService, mNativeWrapper);
- mHdmiControlService.setCecController(mHdmiCecController);
- mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
- mHdmiControlService.initPortInfo();
-
- mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService) {
+ HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
+ hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
+ hdmiControlService.setCecController(hdmiCecController);
+ hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService));
+ hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService));
+ hdmiControlService.initPortInfo();
+
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) {
@Override
protected void setPreferredAddress(int addr) {
}
@@ -130,7 +128,7 @@ public class ArcTerminationActionFromAvrTest {
mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
mTestLooper.dispatchAll();
}
@@ -173,7 +171,7 @@ public class ArcTerminationActionFromAvrTest {
HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildReportArcTerminated(
Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM);
- mHdmiControlService.handleCecCommand(arcTerminatedResponse);
+ mNativeWrapper.onCecMessage(arcTerminatedResponse);
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 01f0a3d398df..2c42791fabce 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -16,7 +16,11 @@
package com.android.server.hdmi;
import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.CecMessage;
+import android.hardware.tv.cec.V1_0.HotplugEvent;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiCecController.NativeWrapper;
@@ -29,6 +33,8 @@ import java.util.List;
/** Fake {@link NativeWrapper} useful for testing. */
final class FakeNativeWrapper implements NativeWrapper {
+ private static final String TAG = "FakeNativeWrapper";
+
private final int[] mPollAddressResponse =
new int[] {
SendMessageResult.NACK,
@@ -52,6 +58,7 @@ final class FakeNativeWrapper implements NativeWrapper {
private final HashMap<Integer, Integer> mMessageSendResult = new HashMap<>();
private int mMyPhysicalAddress = 0;
private HdmiPortInfo[] mHdmiPortInfo = null;
+ private HdmiCecController.HdmiCecCallback mCallback = null;
@Override
public String nativeInit() {
@@ -59,7 +66,9 @@ final class FakeNativeWrapper implements NativeWrapper {
}
@Override
- public void setCallback(HdmiCecController.HdmiCecCallback callback) {}
+ public void setCallback(HdmiCecController.HdmiCecCallback callback) {
+ this.mCallback = callback;
+ }
@Override
public int nativeSendCecCommand(
@@ -119,6 +128,42 @@ final class FakeNativeWrapper implements NativeWrapper {
return false;
}
+ public void onCecMessage(HdmiCecMessage hdmiCecMessage) {
+ if (mCallback == null) {
+ return;
+ }
+ CecMessage message = new CecMessage();
+ message.initiator = hdmiCecMessage.getSource();
+ message.destination = hdmiCecMessage.getDestination();
+ ArrayList<Byte> body = new ArrayList<>();
+ body.add((byte) hdmiCecMessage.getOpcode());
+ for (byte param : hdmiCecMessage.getParams()) {
+ body.add(param);
+ }
+ message.body = body;
+ try {
+ mCallback.onCecMessage(message);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending CEC message", e);
+ }
+ }
+
+ public void onHotplugEvent(int port, boolean connected) {
+ if (mCallback == null) {
+ return;
+ }
+
+ HotplugEvent hotplugEvent = new HotplugEvent();
+ hotplugEvent.portId = port;
+ hotplugEvent.connected = connected;
+
+ try {
+ mCallback.onHotplugEvent(hotplugEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending hotplug event", e);
+ }
+ }
+
public List<HdmiCecMessage> getResultMessages() {
return new ArrayList<>(mResultMessages);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 55c925f7f7aa..6e7ec2a88140 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -70,7 +70,6 @@ public class HdmiCecControllerTest {
}
}
- private HdmiControlService mHdmiControlService;
private HdmiCecController mHdmiCecController;
private int mLogicalAddress = 16;
private AllocateAddressCallback mCallback =
@@ -87,22 +86,23 @@ public class HdmiCecControllerTest {
public void SetUp() {
mMyLooper = mTestLooper.getLooper();
mMyLooper = mTestLooper.getLooper();
- mHdmiControlService = new MyHdmiControlService(InstrumentationRegistry.getTargetContext());
+ HdmiControlService hdmiControlService = new MyHdmiControlService(
+ InstrumentationRegistry.getTargetContext());
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
}
/** Tests for {@link HdmiCecController#allocateLogicalAddress} */
@Test
- public void testAllocatLogicalAddress_TvDevicePreferredNotOcupied() {
+ public void testAllocateLogicalAddress_TvDevicePreferredNotOccupied() {
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_TV, mCallback);
mTestLooper.dispatchAll();
assertEquals(ADDR_TV, mLogicalAddress);
}
@Test
- public void testAllocatLogicalAddress_TvDeviceNonPreferredNotOcupied() {
+ public void testAllocateLogicalAddress_TvDeviceNonPreferredNotOccupied() {
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
@@ -110,7 +110,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_TvDeviceNonPreferredFirstOcupied() {
+ public void testAllocateLogicalAddress_TvDeviceNonPreferredFirstOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
@@ -118,7 +118,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_TvDeviceNonPreferredAllOcupied() {
+ public void testAllocateLogicalAddress_TvDeviceNonPreferredAllOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
mNativeWrapper.setPollAddressResponse(ADDR_SPECIFIC_USE, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_UNREGISTERED, mCallback);
@@ -127,7 +127,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_AudioSystemNonPreferredNotOcupied() {
+ public void testAllocateLogicalAddress_AudioSystemNonPreferredNotOccupied() {
mHdmiCecController.allocateLogicalAddress(
DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
@@ -135,7 +135,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_AudioSystemNonPreferredAllOcupied() {
+ public void testAllocateLogicalAddress_AudioSystemNonPreferredAllOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(
DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback);
@@ -144,14 +144,14 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_PlaybackPreferredNotOccupied() {
+ public void testAllocateLogicalAddress_PlaybackPreferredNotOccupied() {
mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback);
mTestLooper.dispatchAll();
assertEquals(ADDR_PLAYBACK_1, mLogicalAddress);
}
@Test
- public void testAllocatLogicalAddress_PlaybackPreferredOcuppied() {
+ public void testAllocateLogicalAddress_PlaybackPreferredOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback);
mTestLooper.dispatchAll();
@@ -159,14 +159,14 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_PlaybackNoPreferredNotOcuppied() {
+ public void testAllocateLogicalAddress_PlaybackNoPreferredNotOccupied() {
mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
assertEquals(ADDR_PLAYBACK_1, mLogicalAddress);
}
@Test
- public void testAllocatLogicalAddress_PlaybackNoPreferredFirstOcuppied() {
+ public void testAllocateLogicalAddress_PlaybackNoPreferredFirstOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback);
mTestLooper.dispatchAll();
@@ -174,7 +174,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_PlaybackNonPreferredFirstTwoOcuppied() {
+ public void testAllocateLogicalAddress_PlaybackNonPreferredFirstTwoOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_UNREGISTERED, mCallback);
@@ -183,7 +183,7 @@ public class HdmiCecControllerTest {
}
@Test
- public void testAllocatLogicalAddress_PlaybackNonPreferredAllOcupied() {
+ public void testAllocateLogicalAddress_PlaybackNonPreferredAllOccupied() {
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_3, SendMessageResult.SUCCESS);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 953a03c2d9e3..74fd6830de61 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -30,10 +30,15 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.google.common.truth.Truth.assertThat;
+import android.content.Context;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.media.AudioManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -47,6 +52,8 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -81,8 +88,17 @@ public class HdmiCecLocalDeviceAudioSystemTest {
private HdmiPortInfo[] mHdmiPortInfo;
private boolean mWokenUp;
+ @Mock private IPowerManager mIPowerManagerMock;
+ @Mock private IThermalService mIThermalServiceMock;
+
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ Context context = InstrumentationRegistry.getTargetContext();
+ mMyLooper = mTestLooper.getLooper();
+ PowerManager powerManager = new PowerManager(context, mIPowerManagerMock,
+ mIThermalServiceMock, new Handler(mMyLooper));
mHdmiControlService =
new HdmiControlService(InstrumentationRegistry.getTargetContext()) {
@Override
@@ -166,6 +182,11 @@ public class HdmiCecLocalDeviceAudioSystemTest {
return defVal;
}
}
+
+ @Override
+ PowerManager getPowerManager() {
+ return powerManager;
+ }
};
mHdmiControlService.setHdmiCecVolumeControlEnabled(true);
@@ -174,11 +195,6 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@Override
- void setIsActiveSource(boolean on) {
- mIsActiveSource = on;
- }
-
- @Override
protected int getPreferredAddress() {
return ADDR_PLAYBACK_1;
}
@@ -188,8 +204,8 @@ public class HdmiCecLocalDeviceAudioSystemTest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
mNativeWrapper.setPhysicalAddress(SELF_PHYSICAL_ADDRESS);
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -827,4 +843,68 @@ public class HdmiCecLocalDeviceAudioSystemTest {
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(unexpected);
}
+
+ @Test
+ public void setActiveSource_localDevice_playback() {
+ mHdmiControlService.setActiveSource(mHdmiCecLocalDevicePlayback.mAddress,
+ SELF_PHYSICAL_ADDRESS,
+ "HdmiControlServiceTest");
+
+ assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
+ mHdmiCecLocalDevicePlayback.mAddress);
+ assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
+ SELF_PHYSICAL_ADDRESS);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
+ }
+
+ @Test
+ public void setActiveSource_localDevice_audio() {
+ mHdmiControlService.setActiveSource(mHdmiCecLocalDeviceAudioSystem.mAddress,
+ SELF_PHYSICAL_ADDRESS,
+ "HdmiControlServiceTest");
+
+ assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
+ mHdmiCecLocalDeviceAudioSystem.mAddress);
+ assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
+ SELF_PHYSICAL_ADDRESS);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isTrue();
+ }
+
+ @Test
+ public void setActiveSource_remoteDevice() {
+ mHdmiControlService.setActiveSource(Constants.ADDR_TV, 0x0000, "HdmiControlServiceTest");
+
+ assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
+ Constants.ADDR_TV);
+ assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x000);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
+ }
+
+ @Test
+ public void setActiveSource_nonCecDevice() {
+ mHdmiControlService.setActiveSource(Constants.ADDR_INVALID, 0x1234,
+ "HdmiControlServiceTest");
+
+ assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
+ Constants.ADDR_INVALID);
+ assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x1234);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
+ }
+
+ @Test
+ public void setActiveSource_unknown() {
+ mHdmiControlService.setActiveSource(Constants.ADDR_INVALID,
+ Constants.INVALID_PHYSICAL_ADDRESS, "HdmiControlServiceTest");
+
+ assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
+ Constants.ADDR_INVALID);
+ assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
+ Constants.INVALID_PHYSICAL_ADDRESS);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isActiveSource()).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 0c35797b38ea..ef98b98ba7e1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -17,6 +17,7 @@ package com.android.server.hdmi;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
+import static com.android.server.hdmi.Constants.ADDR_INVALID;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -39,7 +40,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -119,8 +119,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -267,119 +267,205 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void handleRoutingChange_otherDevice_None() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ 0x5000);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_INVALID);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void handleRoutingChange_sameDevice_None_ActiveSource() {
+ mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mStandby = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ mPlaybackLogicalAddress);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void handleRoutingChange_sameDevice_None_InactiveSource() {
+ mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mStandby = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_INVALID);
assertThat(mStandby).isFalse();
}
@Test
public void handleRoutingChange_otherDevice_StandbyNow() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@Test
public void handleRoutingChange_otherDevice_StandbyNow_InactiveSource() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
@Test
public void handleRoutingChange_sameDevice_StandbyNow_ActiveSource() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingChange(ADDR_TV, 0x0000,
mPlaybackPhysicalAddress);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingChange(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mStandby).isFalse();
}
@Test
public void handleRoutingInformation_otherDevice_None() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
+ mStandby = false;
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ 0x5000);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_INVALID);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void handleRoutingInformation_sameDevice_None_ActiveSource() {
+ mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
- HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ mPlaybackLogicalAddress);
+ assertThat(mStandby).isFalse();
+ }
+
+ @Test
+ public void handleRoutingInformation_sameDevice_None_InactiveSource() {
+ mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
+ mStandby = false;
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
+ mPlaybackPhysicalAddress);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_INVALID);
assertThat(mStandby).isFalse();
}
@Test
public void handleRoutingInformation_otherDevice_StandbyNow() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@Test
public void handleRoutingInformation_otherDevice_StandbyNow_InactiveSource() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
@Test
public void handleRoutingInformation_sameDevice_StandbyNow_ActiveSource() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRoutingInformation(ADDR_TV,
mPlaybackPhysicalAddress);
assertThat(mHdmiCecLocalDevicePlayback.handleRoutingInformation(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
assertThat(mStandby).isFalse();
}
- // Playback device does not handle routing control related feature right now
- @Ignore("b/120845532")
@Test
- public void handleSetStreamPath_underCurrentDevice() {
- assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(0);
+ public void handleSetStreamPath() {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2100);
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
- // TODO(amyjojo): Move set and get LocalActivePath to Control Service.
- assertThat(mHdmiCecLocalDevicePlayback.getLocalActivePath()).isEqualTo(1);
}
@Test
@@ -436,7 +522,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -455,7 +542,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -474,7 +562,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_NONE);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -493,7 +582,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -512,7 +602,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -531,7 +622,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mHdmiCecLocalDevicePlayback.mService.writeStringSetting(
Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
HdmiControlManager.SEND_STANDBY_ON_SLEEP_NONE);
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
mTestLooper.dispatchAll();
@@ -555,6 +647,11 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ mPlaybackLogicalAddress);
}
@Test
@@ -566,6 +663,11 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ 0x0000);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_TV);
}
@Test
@@ -578,6 +680,7 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mStandby).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
}
@Test
@@ -589,6 +692,7 @@ public class HdmiCecLocalDevicePlaybackTest {
assertThat(mHdmiCecLocalDevicePlayback.handleActiveSource(message)).isTrue();
mTestLooper.dispatchAll();
assertThat(mStandby).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
}
@Test
@@ -709,8 +813,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mPlaybackLogicalAddress, ADDR_TV);
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
- assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
}
@Test
@@ -729,8 +833,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
- assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
+ assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
}
@Test
@@ -748,8 +852,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mPlaybackLogicalAddress, ADDR_TV);
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
- assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
}
@Test
@@ -767,8 +871,8 @@ public class HdmiCecLocalDevicePlaybackTest {
mPlaybackLogicalAddress, ADDR_AUDIO_SYSTEM);
mTestLooper.dispatchAll();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
- assertThat(mNativeWrapper.getResultMessages()).containsAllOf(pressed, released);
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mNativeWrapper.getResultMessages()).containsAtLeast(pressed, released);
}
@Test
@@ -786,8 +890,8 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void handleSetStreamPath_afterHotplug_broadcastsActiveSource() {
- mHdmiControlService.onHotplug(1, false);
- mHdmiControlService.onHotplug(1, true);
+ mNativeWrapper.onHotplugEvent(1, false);
+ mNativeWrapper.onHotplugEvent(1, true);
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
@@ -803,29 +907,36 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void handleSetStreamPath_afterHotplug_hasCorrectActiveSource() {
- mHdmiControlService.onHotplug(1, false);
- mHdmiControlService.onHotplug(1, true);
+ mNativeWrapper.onHotplugEvent(1, false);
+ mNativeWrapper.onHotplugEvent(1, true);
HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV,
mPlaybackPhysicalAddress);
mHdmiCecLocalDevicePlayback.dispatchMessage(setStreamPath);
mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ mPlaybackPhysicalAddress);
assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress());
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isTrue();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isTrue();
}
@Test
public void handleSetStreamPath_otherDevice_None() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
- HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ HdmiProperties.power_state_change_on_active_source_lost_values.NONE;
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().physicalAddress).isEqualTo(
+ 0x5000);
+ assertThat(mHdmiCecLocalDevicePlayback.getActiveSource().logicalAddress).isEqualTo(
+ ADDR_INVALID);
assertThat(mStandby).isFalse();
}
@@ -833,12 +944,13 @@ public class HdmiCecLocalDevicePlaybackTest {
public void handleSetStreamPath_otherDevice_StandbyNow() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(true);
+ mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
+ mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isTrue();
}
@@ -846,12 +958,13 @@ public class HdmiCecLocalDevicePlaybackTest {
public void handleSetStreamPath_otherDevice_StandbyNow_InactiveSource() {
mHdmiCecLocalDevicePlayback.mPowerStateChangeOnActiveSourceLost =
HdmiProperties.power_state_change_on_active_source_lost_values.STANDBY_NOW;
- mHdmiCecLocalDevicePlayback.setIsActiveSource(false);
+ mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
+ "HdmiCecLocalDevicePlaybackTest");
mStandby = false;
HdmiCecMessage message =
HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x5000);
assertThat(mHdmiCecLocalDevicePlayback.handleSetStreamPath(message)).isTrue();
- assertThat(mHdmiCecLocalDevicePlayback.mIsActiveSource).isFalse();
+ assertThat(mHdmiCecLocalDevicePlayback.isActiveSource()).isFalse();
assertThat(mStandby).isFalse();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 526c1d07296f..e54f2c9e6b2e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -117,9 +117,8 @@ public class HdmiCecLocalDeviceTest {
}
};
mHdmiControlService.setIoLooper(mTestLooper.getLooper());
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(
- mHdmiControlService, new FakeNativeWrapper());
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, new FakeNativeWrapper(), mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV);
mMessageValidator =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index b88573ae168a..ce1cdf369076 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -97,8 +97,8 @@ public class HdmiCecLocalDeviceTvTest {
mHdmiCecLocalDeviceTv.init();
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
index c6cf9b116a1d..c4068d34c00d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
@@ -15,28 +15,28 @@
*/
package com.android.server.hdmi;
-import static android.os.SystemClock.sleep;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.Looper;
-import android.os.SystemProperties;
import android.os.test.TestLooper;
-import android.util.Slog;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
+
/**
* Tests for {@link HdmiControlServiceBinderAPITest} class.
*/
@@ -138,11 +138,6 @@ public class HdmiControlServiceBinderAPITest {
mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
@Override
- void setIsActiveSource(boolean on) {
- mIsActiveSource = on;
- }
-
- @Override
protected void wakeUpIfActiveSource() {}
@Override
@@ -158,8 +153,8 @@ public class HdmiControlServiceBinderAPITest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -186,13 +181,13 @@ public class HdmiControlServiceBinderAPITest {
}
});
assertEquals(mResult, -1);
- assertThat(mPlaybackDevice.mIsActiveSource).isFalse();
+ assertThat(mPlaybackDevice.isActiveSource()).isFalse();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mTestLooper.dispatchAll();
assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.mIsActiveSource).isTrue();
+ assertThat(mPlaybackDevice.isActiveSource()).isTrue();
}
@Test
@@ -207,6 +202,6 @@ public class HdmiControlServiceBinderAPITest {
}
});
assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.mIsActiveSource).isTrue();
+ assertThat(mPlaybackDevice.isActiveSource()).isTrue();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 31cf59ee7bde..2f48b5ee4c70 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -158,8 +158,8 @@ public class HdmiControlServiceTest {
mHdmiControlService.setIoLooper(mMyLooper);
mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController =
- HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper);
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
mHdmiControlService.setCecController(mHdmiCecController);
mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
@@ -405,82 +405,6 @@ public class HdmiControlServiceTest {
assertThat(callback2.mVolumeControlEnabled).isTrue();
}
- @Test
- public void setActiveSource_localDevice_playback() {
- int physicalAddress = 0x1000;
- mNativeWrapper.setPhysicalAddress(physicalAddress);
-
- mHdmiControlService.setActiveSource(mMyPlaybackDevice.mAddress, physicalAddress,
- "HdmiControlServiceTest");
-
- assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
- mMyPlaybackDevice.mAddress);
- assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
- physicalAddress);
- assertThat(mMyPlaybackDevice.mIsActiveSource).isTrue();
- assertThat(mMyAudioSystemDevice.mIsActiveSource).isFalse();
- }
-
- @Test
- public void setActiveSource_localDevice_audio() {
- int physicalAddress = 0x1000;
- mNativeWrapper.setPhysicalAddress(physicalAddress);
-
- mHdmiControlService.setActiveSource(mMyAudioSystemDevice.mAddress, physicalAddress,
- "HdmiControlServiceTest");
-
- assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
- mMyAudioSystemDevice.mAddress);
- assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
- physicalAddress);
- assertThat(mMyPlaybackDevice.mIsActiveSource).isFalse();
- assertThat(mMyAudioSystemDevice.mIsActiveSource).isTrue();
- }
-
- @Test
- public void setActiveSource_remoteDevice() {
- int physicalAddress = 0x1000;
- mNativeWrapper.setPhysicalAddress(physicalAddress);
-
- mHdmiControlService.setActiveSource(Constants.ADDR_TV, 0x0000, "HdmiControlServiceTest");
-
- assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
- Constants.ADDR_TV);
- assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x000);
- assertThat(mMyPlaybackDevice.mIsActiveSource).isFalse();
- assertThat(mMyAudioSystemDevice.mIsActiveSource).isFalse();
- }
-
- @Test
- public void setActiveSource_nonCecDevice() {
- int physicalAddress = 0x1000;
- mNativeWrapper.setPhysicalAddress(physicalAddress);
-
- mHdmiControlService.setActiveSource(Constants.ADDR_INVALID, 0x1234,
- "HdmiControlServiceTest");
-
- assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
- Constants.ADDR_INVALID);
- assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(0x1234);
- assertThat(mMyPlaybackDevice.mIsActiveSource).isFalse();
- assertThat(mMyAudioSystemDevice.mIsActiveSource).isFalse();
- }
-
- @Test
- public void setActiveSource_unknown() {
- int physicalAddress = 0x1000;
- mNativeWrapper.setPhysicalAddress(physicalAddress);
-
- mHdmiControlService.setActiveSource(Constants.ADDR_INVALID,
- Constants.INVALID_PHYSICAL_ADDRESS, "HdmiControlServiceTest");
-
- assertThat(mHdmiControlService.getLocalActiveSource().logicalAddress).isEqualTo(
- Constants.ADDR_INVALID);
- assertThat(mHdmiControlService.getLocalActiveSource().physicalAddress).isEqualTo(
- Constants.INVALID_PHYSICAL_ADDRESS);
- assertThat(mMyPlaybackDevice.mIsActiveSource).isFalse();
- assertThat(mMyAudioSystemDevice.mIsActiveSource).isFalse();
- }
private static class VolumeControlFeatureCallback extends
IHdmiCecVolumeControlFeatureListener.Stub {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index c7342426c80c..6be28d9a13be 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -150,7 +150,7 @@ public class SystemAudioInitiationActionFromAvrTest {
@Override
public void setAndBroadcastActiveSourceFromOneDeviceType(
- int sourceAddress, int physicalAddress) {
+ int sourceAddress, int physicalAddress, String caller) {
mBroadcastActiveSource = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
index cdff97b1376d..9ab762aaa8fa 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/MultiClientInputMethodManagerServiceTest.java
@@ -76,7 +76,7 @@ public class MultiClientInputMethodManagerServiceTest {
// Act and assert
assertThat(MultiClientInputMethodManagerService.resolveMultiClientImeService(
- asList(imeService))).isSameAs(imeService);
+ asList(imeService))).isSameInstanceAs(imeService);
}
private ResolveInfo buildResolveInfo(String permission, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 41be54ab5b7a..f26e0941e008 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -194,7 +194,7 @@ public class IntegrityFileManagerTest {
assertThat(rulesFetched.size())
.isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
assertThat(rulesFetched)
- .containsAllOf(
+ .containsAtLeast(
getPackageNameIndexedRule(installedPackageName),
getAppCertificateIndexedRule(installedAppCertificate));
}
diff --git a/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java
deleted file mode 100644
index b6b8b82f191a..000000000000
--- a/services/tests/servicestests/src/com/android/server/location/LocationRequestStatisticsTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package com.android.server.location;
-
-import android.os.SystemClock;
-import android.test.AndroidTestCase;
-
-import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
-import com.android.server.location.LocationRequestStatistics.PackageStatistics;
-
-/**
- * Unit tests for {@link LocationRequestStatistics}.
- */
-public class LocationRequestStatisticsTest extends AndroidTestCase {
- private static final String PACKAGE1 = "package1";
- private static final String PACKAGE2 = "package2";
- private static final String FEATURE_ID = "featureId";
- private static final String PROVIDER1 = "provider1";
- private static final String PROVIDER2 = "provider2";
- private static final long INTERVAL1 = 5000;
- private static final long INTERVAL2 = 100000;
-
- private LocationRequestStatistics mStatistics;
- private long mStartElapsedRealtimeMs;
-
- @Override
- public void setUp() {
- mStatistics = new LocationRequestStatistics();
- mStartElapsedRealtimeMs = SystemClock.elapsedRealtime();
- }
-
- /**
- * Tests that adding a single package works correctly.
- */
- public void testSinglePackage() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
-
- assertEquals(1, mStatistics.statistics.size());
- PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
- assertEquals(PACKAGE1, key.mPackageName);
- assertEquals(PROVIDER1, key.mProviderName);
- assertEquals(FEATURE_ID, key.mFeatureId);
- PackageStatistics stats = mStatistics.statistics.values().iterator().next();
- verifyStatisticsTimes(stats);
- assertEquals(INTERVAL1, stats.getFastestIntervalMs());
- assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
- assertTrue(stats.isActive());
- }
-
- /**
- * Tests that adding a single package works correctly when it is stopped and restarted.
- */
- public void testSinglePackage_stopAndRestart() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
-
- assertEquals(1, mStatistics.statistics.size());
- PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
- assertEquals(PACKAGE1, key.mPackageName);
- assertEquals(FEATURE_ID, key.mFeatureId);
- assertEquals(PROVIDER1, key.mProviderName);
- PackageStatistics stats = mStatistics.statistics.values().iterator().next();
- verifyStatisticsTimes(stats);
- assertEquals(INTERVAL1, stats.getFastestIntervalMs());
- assertEquals(INTERVAL1, stats.getSlowestIntervalMs());
- assertTrue(stats.isActive());
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- assertFalse(stats.isActive());
- }
-
- /**
- * Tests that adding a single package works correctly when multiple intervals are used.
- */
- public void testSinglePackage_multipleIntervals() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL2, true);
-
- assertEquals(1, mStatistics.statistics.size());
- PackageProviderKey key = mStatistics.statistics.keySet().iterator().next();
- assertEquals(PACKAGE1, key.mPackageName);
- assertEquals(PROVIDER1, key.mProviderName);
- assertEquals(FEATURE_ID, key.mFeatureId);
- PackageStatistics stats = mStatistics.statistics.values().iterator().next();
- verifyStatisticsTimes(stats);
- assertEquals(INTERVAL1, stats.getFastestIntervalMs());
- assertTrue(stats.isActive());
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- assertTrue(stats.isActive());
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- assertFalse(stats.isActive());
- }
-
- /**
- * Tests that adding a single package works correctly when multiple providers are used.
- */
- public void testSinglePackage_multipleProviders() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER2, INTERVAL2, true);
-
- assertEquals(2, mStatistics.statistics.size());
- PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, FEATURE_ID, PROVIDER1);
- PackageStatistics stats1 = mStatistics.statistics.get(key1);
- verifyStatisticsTimes(stats1);
- assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
- assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
- assertTrue(stats1.isActive());
- PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, FEATURE_ID, PROVIDER2);
- PackageStatistics stats2 = mStatistics.statistics.get(key2);
- verifyStatisticsTimes(stats2);
- assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
- assertEquals(INTERVAL2, stats2.getFastestIntervalMs());
- assertTrue(stats2.isActive());
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- assertFalse(stats1.isActive());
- assertTrue(stats2.isActive());
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER2);
- assertFalse(stats1.isActive());
- assertFalse(stats2.isActive());
- }
-
- /**
- * Tests that adding multiple packages works correctly.
- */
- public void testMultiplePackages() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER2, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER2, INTERVAL2, true);
- mStatistics.startRequesting(PACKAGE2, FEATURE_ID, PROVIDER1, INTERVAL1, true);
-
- assertEquals(3, mStatistics.statistics.size());
- PackageProviderKey key1 = new PackageProviderKey(PACKAGE1, FEATURE_ID, PROVIDER1);
- PackageStatistics stats1 = mStatistics.statistics.get(key1);
- verifyStatisticsTimes(stats1);
- assertEquals(INTERVAL1, stats1.getSlowestIntervalMs());
- assertEquals(INTERVAL1, stats1.getFastestIntervalMs());
- assertTrue(stats1.isActive());
-
- PackageProviderKey key2 = new PackageProviderKey(PACKAGE1, FEATURE_ID, PROVIDER2);
- PackageStatistics stats2 = mStatistics.statistics.get(key2);
- verifyStatisticsTimes(stats2);
- assertEquals(INTERVAL2, stats2.getSlowestIntervalMs());
- assertEquals(INTERVAL1, stats2.getFastestIntervalMs());
- assertTrue(stats2.isActive());
-
- PackageProviderKey key3 = new PackageProviderKey(PACKAGE2, FEATURE_ID, PROVIDER1);
- PackageStatistics stats3 = mStatistics.statistics.get(key3);
- verifyStatisticsTimes(stats3);
- assertEquals(INTERVAL1, stats3.getSlowestIntervalMs());
- assertEquals(INTERVAL1, stats3.getFastestIntervalMs());
- assertTrue(stats3.isActive());
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
- assertFalse(stats1.isActive());
- assertTrue(stats2.isActive());
- assertTrue(stats3.isActive());
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER2);
- assertFalse(stats1.isActive());
- assertTrue(stats2.isActive());
- assertTrue(stats3.isActive());
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER2);
- assertFalse(stats2.isActive());
-
- mStatistics.stopRequesting(PACKAGE2, FEATURE_ID, PROVIDER1);
- assertFalse(stats1.isActive());
- assertFalse(stats2.isActive());
- assertFalse(stats3.isActive());
- }
-
- /**
- * Tests that switching foreground & background states accmulates time reasonably.
- */
- public void testForegroundBackground() {
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER1, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE1, FEATURE_ID, PROVIDER2, INTERVAL1, true);
- mStatistics.startRequesting(PACKAGE2, FEATURE_ID, PROVIDER1, INTERVAL1, false);
-
- mStatistics.updateForeground(PACKAGE1, FEATURE_ID, PROVIDER2, false);
- mStatistics.updateForeground(PACKAGE2, FEATURE_ID, PROVIDER1, true);
-
- mStatistics.stopRequesting(PACKAGE1, FEATURE_ID, PROVIDER1);
-
- for (PackageStatistics stats : mStatistics.statistics.values()) {
- verifyStatisticsTimes(stats);
- }
- }
-
- private void verifyStatisticsTimes(PackageStatistics stats) {
- long durationMs = stats.getDurationMs();
- long foregroundDurationMs = stats.getForegroundDurationMs();
- long timeSinceFirstRequestMs = stats.getTimeSinceFirstRequestMs();
- long maxDeltaMs = SystemClock.elapsedRealtime() - mStartElapsedRealtimeMs;
- assertTrue("Duration is too small", durationMs >= 0);
- assertTrue("Duration is too large", durationMs <= maxDeltaMs);
- assertTrue("Foreground Duration is too small", foregroundDurationMs >= 0);
- assertTrue("Foreground Duration is too large", foregroundDurationMs <= maxDeltaMs);
- assertTrue("Time since first request is too large", timeSinceFirstRequestMs <= maxDeltaMs);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index dbaad6633e98..292b7c6bf5ad 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -15,18 +15,22 @@
*/
package com.android.server.location.timezone;
+import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
import static android.location.timezone.LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
-import static com.android.server.location.timezone.ControllerImpl.UNCERTAINTY_DELAY;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_CERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_UNCERTAIN;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
import static com.android.server.location.timezone.TestSupport.USER1_ID;
import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -40,6 +44,7 @@ import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.IndentingPrintWriter;
+import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
import com.android.server.timezonedetector.TestState;
@@ -47,6 +52,7 @@ import com.android.server.timezonedetector.TestState;
import org.junit.Before;
import org.junit.Test;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -65,10 +71,13 @@ public class ControllerImplTest {
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_SUCCESS, asList("Europe/Paris"));
private static final LocationTimeZoneEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_UNCERTAIN, null);
+ private static final LocationTimeZoneEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
+ createLocationTimeZoneEvent(USER1_ID, EVENT_TYPE_PERMANENT_FAILURE, null);
private TestThreadingDomain mTestThreadingDomain;
private TestCallback mTestCallback;
- private TestLocationTimeZoneProvider mTestLocationTimeZoneProvider;
+ private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider;
+ private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider;
@Before
public void setUp() {
@@ -77,266 +86,854 @@ public class ControllerImplTest {
// will never get a chance to execute.
mTestThreadingDomain = new TestThreadingDomain();
mTestCallback = new TestCallback(mTestThreadingDomain);
- mTestLocationTimeZoneProvider =
+ mTestPrimaryLocationTimeZoneProvider =
new TestLocationTimeZoneProvider(mTestThreadingDomain, "primary");
+ mTestSecondaryLocationTimeZoneProvider =
+ new TestLocationTimeZoneProvider(mTestThreadingDomain, "secondary");
}
@Test
public void initialState_enabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout()
+ .plus(testEnvironment.getProviderInitializationTimeoutFuzz());
+
+ // Initialize. After initialization the providers must be initialized and one should be
+ // enabled.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertInitialized();
+ mTestPrimaryLocationTimeZoneProvider.assertInitialized();
+ mTestSecondaryLocationTimeZoneProvider.assertInitialized();
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void initialState_disabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize. After initialization the providers must be initialized but neither should be
+ // enabled.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertInitialized();
+ mTestPrimaryLocationTimeZoneProvider.assertInitialized();
+ mTestSecondaryLocationTimeZoneProvider.assertInitialized();
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void enabled_uncertaintySuggestionSentIfNoEventReceived() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate time passing with no event being received.
+ // Simulate time passing with no provider event being received from the primary.
mTestThreadingDomain.executeNext();
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
+ // The primary should have reported uncertainty, which should trigger the controller to
+ // start the uncertainty timeout and enable the secondary.
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate time passing with no provider event being received from either the primary or
+ // secondary.
+ mTestThreadingDomain.executeNext();
+
+ // Now both initialization timeouts should have triggered. The uncertainty timeout should
+ // still not be triggered.
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Finally, the uncertainty timeout should cause the controller to make an uncertain
+ // suggestion.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertUncertainSuggestionMadeAndCommit();
- mTestThreadingDomain.assertQueueEmpty();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void enabled_uncertaintySuggestionCancelledIfEventReceived() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void enabled_eventReceivedBeforeInitializationTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void enabled_repeatedCertainty() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate time passing with no provider event being received from the primary.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made and the secondary to be shut down.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_eventReceivedFromSecondaryAfterInitializationTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate time passing with no provider event being received from the primary.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate a location event being received from the secondary provider. This should cause a
+ // suggestion to be made.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_repeatedPrimaryCertainty() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// A second, identical event should not cause another suggestion.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_repeatedSecondaryCertainty() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate time passing with no provider event being received from the primary.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate a location event being received from the secondary provider. This should cause a
+ // suggestion to be made.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // A second, identical event should not cause another suggestion.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // And a third, different event should cause another suggestion.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made and ensure the primary is considered initialized.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate an uncertain event being received from the primary provider. This should not
+ // cause a suggestion to be made straight away, but the uncertainty timeout should be
+ // started and the secondary should be enabled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate a location event being received from the secondary provider. This should cause a
+ // suggestion to be made, cancel the uncertainty timeout and ensure the secondary is
+ // considered initialized.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate an uncertain event being received from the secondary provider. This should not
+ // cause a suggestion to be made straight away, but the uncertainty timeout should be
+ // started. Both providers are now enabled, with no initialization timeout set.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate time passing. This means the uncertainty timeout should fire and the uncertain
+ // suggestion should be made.
+ mTestThreadingDomain.executeNext();
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void enabled_briefUncertaintyTriggersNoSuggestion() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate a location event being received from the primary provider. This should cause a
+ // suggestion to be made.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Uncertainty should cause
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty
+ // timeout should be started and the secondary should be enabled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
- // And a third event should cause yet another suggestion.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // And a success event from the primary provider should cause the controller to make another
+ // suggestion, the uncertainty timeout should be cancelled and the secondary should be
+ // disabled again.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void configChanges_enableAndDisable() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void configChanges_enableAndDisableWithNoPreviousSuggestion() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertNextQueueItemIsDelayed(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
- public void configChanges_disableWithPreviousSuggestion() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ public void configChanges_enableAndDisableWithPreviousSuggestion() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
- mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is enabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
- // Simulate a location event being received by the provider. This should cause a suggestion
- // to be made, and the timeout to be cleared.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a success event being received from the primary provider.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate the user disabling the provider.
- testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
-
+ // Now signal a config change so that geo detection is disabled.
// Because there had been a previous suggestion, the controller should withdraw it
// immediately to let the downstream components know that the provider can no longer be sure
// of the time zone.
- mTestLocationTimeZoneProvider.assertIsDisabled();
- mTestThreadingDomain.assertQueueEmpty();
- mTestCallback.assertSuggestionMadeAndCommit(null);
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
}
@Test
public void configChanges_userSwitch_enabledToEnabled() {
- ControllerImpl controllerImpl =
- new ControllerImpl(mTestThreadingDomain, mTestLocationTimeZoneProvider);
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
TestEnvironment testEnvironment = new TestEnvironment(
mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
controllerImpl.initialize(testEnvironment, mTestCallback);
- // There should be a runnable scheduled to suggest uncertainty if no event is received.
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Have the provider suggest a time zone.
- mTestLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ // Simulate the primary provider suggesting a time zone.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
// Receiving a "success" provider event should cause a suggestion to be made synchronously,
// and also clear the scheduled uncertainty suggestion.
- mTestLocationTimeZoneProvider.assertIsEnabled(USER1_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertSuggestionMadeAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
// Simulate the user change (but geo detection still enabled).
testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED);
// We expect the provider to end up in PROVIDER_STATE_ENABLED, but it should have been
// disabled when the user changed.
- // The controller should schedule a runnable to make a suggestion if the provider doesn't
- // send a success event.
- int[] expectedStateTransitions = { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED };
- mTestLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
- mTestLocationTimeZoneProvider.assertConfig(USER2_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertSingleDelayedQueueItem(UNCERTAINTY_DELAY);
+ int[] expectedStateTransitions =
+ { PROVIDER_STATE_DISABLED, PROVIDER_STATE_ENABLED_INITIALIZING };
+ mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(expectedStateTransitions);
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void primaryPermFailure_secondaryEventsReceived() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
- // Simulate no event being received, and time passing.
- mTestThreadingDomain.executeNext();
+ // Simulate a failure location event being received from the primary provider. This should
+ // cause the secondary to be enabled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate uncertainty from the secondary.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // And a success event from the secondary provider should cause the controller to make
+ // another suggestion, the uncertainty timeout should be cancelled.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate uncertainty from the secondary.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ }
+
+ @Test
+ public void primaryPermFailure_disableAndEnable() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a failure location event being received from the primary provider. This should
+ // cause the secondary to be enabled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is disabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is enabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void secondaryPermFailure_primaryEventsReceived() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate an uncertain event from the primary. This will enable the secondary, which will
+ // give this test the opportunity to simulate its failure. Then it will be possible to
+ // demonstrate controller behavior with only the primary working.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate failure event from the secondary. This should just affect the secondary's state.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // And a success event from the primary provider should cause the controller to make
+ // a suggestion, the uncertainty timeout should be cancelled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertSuggestionMadeAndCommit(
+ USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2.getTimeZoneIds());
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate uncertainty from the primary. The secondary cannot be enabled.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+ }
+
+ @Test
+ public void secondaryPermFailure_disableAndEnable() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate an uncertain event from the primary. This will enable the secondary, which will
+ // give this test the opportunity to simulate its failure. Then it will be possible to
+ // demonstrate controller behavior with only the primary working.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Simulate failure event from the secondary. This should just affect the secondary's state.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertUncertaintyTimeoutSet(testEnvironment, controllerImpl);
+
+ // Now signal a config change so that geo detection is disabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Now signal a config change so that geo detection is enabled. Only the primary can be
+ // enabled.
+ testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ @Test
+ public void bothPermFailure_disableAndEnable() {
+ ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain,
+ mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider);
+ TestEnvironment testEnvironment = new TestEnvironment(
+ mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED);
+
+ // Initialize and check initial state.
+ controllerImpl.initialize(testEnvironment, mTestCallback);
+
+ mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestSecondaryLocationTimeZoneProvider.assertIsDisabledAndCommit();
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate a failure event from the primary. This will enable the secondary.
+ mTestPrimaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
- mTestLocationTimeZoneProvider.assertIsEnabled(USER2_CONFIG_GEO_DETECTION_ENABLED);
- mTestThreadingDomain.assertQueueEmpty();
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
+ PROVIDER_STATE_ENABLED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
+ mTestCallback.assertNoSuggestionMade();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+
+ // Simulate failure event from the secondary.
+ mTestSecondaryLocationTimeZoneProvider.simulateLocationTimeZoneEvent(
+ USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT);
+
+ mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
+ mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ assertFalse(controllerImpl.isUncertaintyTimeoutSet());
+ }
+
+ private static void assertUncertaintyTimeoutSet(
+ LocationTimeZoneProviderController.Environment environment,
+ LocationTimeZoneProviderController controller) {
+ assertTrue(controller.isUncertaintyTimeoutSet());
+ assertEquals(environment.getUncertaintyDelay().toMillis(),
+ controller.getUncertaintyTimeoutDelayMillis());
}
private static LocationTimeZoneEvent createLocationTimeZoneEvent(@UserIdInt int userId,
@@ -351,8 +948,17 @@ public class ControllerImplTest {
return builder.build();
}
+
private static class TestEnvironment extends LocationTimeZoneProviderController.Environment {
+ // These timeouts are set deliberately so that:
+ // (initialization timeout * 2) < uncertainty delay
+ //
+ // That makes the order of initialization timeout Vs uncertainty delay deterministic.
+ static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+ static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
+ private static final Duration UNCERTAINTY_DELAY = Duration.ofMinutes(15);
+
private final LocationTimeZoneProviderController mController;
private ConfigurationInternal mConfigurationInternal;
@@ -369,6 +975,21 @@ public class ControllerImplTest {
return mConfigurationInternal;
}
+ @Override
+ Duration getProviderInitializationTimeout() {
+ return PROVIDER_INITIALIZATION_TIMEOUT;
+ }
+
+ @Override
+ Duration getProviderInitializationTimeoutFuzz() {
+ return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+ }
+
+ @Override
+ Duration getUncertaintyDelay() {
+ return UNCERTAINTY_DELAY;
+ }
+
void simulateConfigChange(ConfigurationInternal newConfig) {
ConfigurationInternal oldConfig = mConfigurationInternal;
mConfigurationInternal = Objects.requireNonNull(newConfig);
@@ -432,7 +1053,7 @@ public class ControllerImplTest {
}
@Override
- void onEnable() {
+ void onEnable(Duration initializationTimeout) {
// Nothing needed for tests.
}
@@ -456,38 +1077,57 @@ public class ControllerImplTest {
assertTrue(mInitialized);
}
- void assertIsDisabled() {
- // Disabled providers don't hold config.
- assertConfig(null);
- assertIsEnabledAndCommit(false);
+ public void assertIsPermFailedAndCommit() {
+ // A failed provider doesn't hold config.
+ assertStateEnumAndConfig(PROVIDER_STATE_PERM_FAILED, null /* config */);
+ mTestProviderState.commitLatest();
+ }
+
+ void assertIsDisabledAndCommit() {
+ // A disabled provider doesn't hold config.
+ assertStateEnumAndConfig(PROVIDER_STATE_DISABLED, null /* config */);
+ mTestProviderState.commitLatest();
}
/**
- * Asserts the provider's config matches the expected, and the current state is set
- * accordinly. Commits the latest changes to the state.
+ * Asserts the provider's state enum and config matches the expected.
+ * Commits the latest changes to the state.
*/
- void assertIsEnabled(@NonNull ConfigurationInternal expectedConfig) {
- assertConfig(expectedConfig);
-
- boolean expectIsEnabled = expectedConfig.getAutoDetectionEnabledBehavior();
- assertIsEnabledAndCommit(expectIsEnabled);
+ void assertStateEnumAndConfigAndCommit(
+ @ProviderStateEnum int expectedStateEnum,
+ @Nullable ConfigurationInternal expectedConfig) {
+ assertStateEnumAndConfig(expectedStateEnum, expectedConfig);
+ mTestProviderState.commitLatest();
}
- private void assertIsEnabledAndCommit(boolean enabled) {
+ /**
+ * Asserts the provider's state enum and config matches the expected.
+ * Does not commit any state changes.
+ */
+ void assertStateEnumAndConfig(
+ @ProviderStateEnum int expectedStateEnum,
+ @Nullable ConfigurationInternal expectedConfig) {
ProviderState currentState = mCurrentState.get();
- if (enabled) {
- assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
- } else {
- assertEquals(PROVIDER_STATE_DISABLED, currentState.stateEnum);
- }
- mTestProviderState.commitLatest();
+ assertEquals(expectedStateEnum, currentState.stateEnum);
+
+ // If and only if the controller is initializing, the initialization timeout must be
+ // set.
+ assertEquals(expectedStateEnum == PROVIDER_STATE_ENABLED_INITIALIZING,
+ isInitializationTimeoutSet());
+
+ assertConfig(expectedConfig);
}
- void assertConfig(@NonNull ConfigurationInternal expectedConfig) {
+ private void assertConfig(@Nullable ConfigurationInternal expectedConfig) {
ProviderState currentState = mCurrentState.get();
assertEquals(expectedConfig, currentState.currentUserConfiguration);
}
+ void assertInitializationTimeoutSet(Duration expectedTimeout) {
+ assertTrue(isInitializationTimeoutSet());
+ assertEquals(expectedTimeout, getInitializationTimeoutDelay());
+ }
+
void simulateLocationTimeZoneEvent(@NonNull LocationTimeZoneEvent event) {
handleLocationTimeZoneEvent(event);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
index cbaf0f391375..4d6775dcaecf 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
@@ -115,28 +115,7 @@ public class HandlerThreadingDomainTest {
}
@Test
- public void singleRunnableHandler_runSynchronously() throws Exception {
- ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
- SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
-
- AtomicBoolean testPassed = new AtomicBoolean(false);
- // Calls to SingleRunnableQueue must be made on the handler thread it is associated with,
- // so this uses runWithScissors() to block until the lambda has completed.
- mTestHandler.runWithScissors(() -> {
- Thread testThread = Thread.currentThread();
- CountDownLatch latch = new CountDownLatch(1);
- singleRunnableQueue.runSynchronously(() -> {
- assertSame(Thread.currentThread(), testThread);
- latch.countDown();
- });
- assertTrue(awaitWithRuntimeException(latch, 60, TimeUnit.SECONDS));
- testPassed.set(true);
- }, TimeUnit.SECONDS.toMillis(60));
- assertTrue(testPassed.get());
- }
-
- @Test
- public void singleRunnableHandler_runDelayed() throws Exception {
+ public void singleRunnableQueue_runDelayed() throws Exception {
ThreadingDomain domain = new HandlerThreadingDomain(mTestHandler);
SingleRunnableQueue singleRunnableQueue = domain.createSingleRunnableQueue();
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
index 7c882fc1f154..5403a654f7e4 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/NullLocationTimeZoneProviderTest.java
@@ -16,7 +16,7 @@
package com.android.server.location.timezone;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DISABLED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED;
+import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_ENABLED_INITIALIZING;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
@@ -34,6 +34,8 @@ import com.android.server.timezonedetector.TestState;
import org.junit.Before;
import org.junit.Test;
+import java.time.Duration;
+
/**
* Tests for {@link NullLocationTimeZoneProvider} and, indirectly, the class it extends
* {@link LocationTimeZoneProvider}.
@@ -73,15 +75,17 @@ public class NullLocationTimeZoneProviderTest {
provider.initialize(providerState -> mTestController.onProviderStateChange(providerState));
ConfigurationInternal config = USER1_CONFIG_GEO_DETECTION_ENABLED;
- provider.enable(config);
+ Duration arbitraryInitializationTimeout = Duration.ofMinutes(5);
+ Duration arbitraryInitializationTimeoutFuzz = Duration.ofMinutes(2);
+ provider.enable(config, arbitraryInitializationTimeout, arbitraryInitializationTimeoutFuzz);
- // The StubbedProvider should enters enabled state, but immediately schedule a runnable to
- // switch to perm failure.
+ // The NullProvider should enter the enabled state, but have schedule an immediate runnable
+ // to switch to perm failure.
ProviderState currentState = provider.getCurrentState();
assertSame(provider, currentState.provider);
- assertEquals(PROVIDER_STATE_ENABLED, currentState.stateEnum);
+ assertEquals(PROVIDER_STATE_ENABLED_INITIALIZING, currentState.stateEnum);
assertEquals(config, currentState.currentUserConfiguration);
- mTestThreadingDomain.assertSingleImmediateQueueItem();
+ mTestThreadingDomain.assertNextQueueItemIsImmediate();
// Entering enabled() does not trigger an onProviderStateChanged() as it is requested by the
// controller.
mTestController.assertProviderChangeNotTriggered();
@@ -113,6 +117,18 @@ public class NullLocationTimeZoneProviderTest {
// Not needed for provider testing.
}
+ @Override
+ boolean isUncertaintyTimeoutSet() {
+ // Not needed for provider testing.
+ return false;
+ }
+
+ @Override
+ long getUncertaintyTimeoutDelayMillis() {
+ // Not needed for provider testing.
+ return 0;
+ }
+
void onProviderStateChange(ProviderState providerState) {
this.mProviderState.set(providerState);
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
index 192ade7847b0..48105634c69d 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
@@ -45,6 +45,7 @@ final class TestSupport {
return new ConfigurationInternal.Builder(userId)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
index 70ff22d17513..7359abd11c3a 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
@@ -16,13 +16,15 @@
package com.android.server.location.timezone;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.annotation.NonNull;
import android.annotation.Nullable;
import java.time.Duration;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Objects;
/**
@@ -33,6 +35,10 @@ import java.util.Objects;
class TestThreadingDomain extends ThreadingDomain {
static class QueuedRunnable {
+
+ static final Comparator<? super QueuedRunnable> COMPARATOR =
+ (o1, o2) -> (int) (o1.executionTimeMillis - o2.executionTimeMillis);
+
@NonNull public final Runnable runnable;
@Nullable public final Object token;
public final long executionTimeMillis;
@@ -55,7 +61,7 @@ class TestThreadingDomain extends ThreadingDomain {
}
private long mCurrentTimeMillis;
- private LinkedList<QueuedRunnable> mQueue = new LinkedList<>();
+ private ArrayList<QueuedRunnable> mQueue = new ArrayList<>();
TestThreadingDomain() {
// Pick an arbitrary time.
@@ -69,22 +75,23 @@ class TestThreadingDomain extends ThreadingDomain {
@Override
void post(Runnable r) {
- mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis));
+ postDelayed(r, null, 0);
}
@Override
void postDelayed(Runnable r, long delayMillis) {
- mQueue.add(new QueuedRunnable(r, null, mCurrentTimeMillis + delayMillis));
+ postDelayed(r, null, delayMillis);
}
@Override
void postDelayed(Runnable r, Object token, long delayMillis) {
mQueue.add(new QueuedRunnable(r, token, mCurrentTimeMillis + delayMillis));
+ mQueue.sort(QueuedRunnable.COMPARATOR);
}
@Override
void removeQueuedRunnables(Object token) {
- mQueue.removeIf(runnable -> runnable.token != null && runnable.token.equals(token));
+ mQueue.removeIf(runnable -> runnable.token != null && runnable.token == token);
}
void assertSingleDelayedQueueItem(Duration expectedDelay) {
@@ -105,8 +112,8 @@ class TestThreadingDomain extends ThreadingDomain {
assertTrue(getNextQueueItemDelayMillis() == 0);
}
- void assertNextQueueItemIsDelayed(Duration expectedDelay) {
- assertTrue(getNextQueueItemDelayMillis() == expectedDelay.toMillis());
+ private void assertNextQueueItemIsDelayed(Duration expectedDelay) {
+ assertEquals(getNextQueueItemDelayMillis(), expectedDelay.toMillis());
}
void assertQueueEmpty() {
@@ -114,14 +121,14 @@ class TestThreadingDomain extends ThreadingDomain {
}
long getNextQueueItemDelayMillis() {
- assertQueueLength(1);
- return mQueue.getFirst().executionTimeMillis - mCurrentTimeMillis;
+ assertFalse(mQueue.isEmpty());
+ return mQueue.get(0).executionTimeMillis - mCurrentTimeMillis;
}
void executeNext() {
- assertQueueLength(1);
+ assertFalse(mQueue.isEmpty());
+ QueuedRunnable queued = mQueue.remove(0);
- QueuedRunnable queued = mQueue.removeFirst();
mCurrentTimeMillis = queued.executionTimeMillis;
queued.runnable.run();
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 2bd0e55f7d21..2d6605ae222f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -511,10 +511,24 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
LockscreenCredential password = newPassword("password");
initializeCredentialUnderSP(password, PRIMARY_USER_ID);
assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID));
+ assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testAddingEscrowToken_NoOrphanedFilesLeft() throws Exception {
+ final byte[] token = "some-high-entropy-secure-token".getBytes();
+ for (int i = 0; i < 16; i++) {
+ long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
+ assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+ mLocalService.removeEscrowToken(handle, PRIMARY_USER_ID);
+ }
+ assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
+ }
+ private void assertNoOrphanedFilesLeft(int userId) {
String handleString = String.format("%016x",
- mService.getSyntheticPasswordHandleLocked(PRIMARY_USER_ID));
- File directory = mStorage.getSyntheticPasswordDirectoryForUser(PRIMARY_USER_ID);
+ mService.getSyntheticPasswordHandleLocked(userId));
+ File directory = mStorage.getSyntheticPasswordDirectoryForUser(userId);
for (File file : directory.listFiles()) {
String[] parts = file.getName().split("\\.");
if (!parts[0].equals(handleString) && !parts[0].equals("0000000000000000")) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
index 6921bb27ceb2..8d5687c33419 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/TestOnlyInsecureCertificateHelperTest.java
@@ -71,7 +71,7 @@ public class TestOnlyInsecureCertificateHelperTest {
Map<String, Pair<SecretKey, byte[]>> filteredKeys =
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
- assertThat(filteredKeys.entrySet()).containsAllIn(rawKeys.entrySet());
+ assertThat(filteredKeys.entrySet()).containsAtLeastElementsIn(rawKeys.entrySet());
}
@Test
@@ -85,7 +85,7 @@ public class TestOnlyInsecureCertificateHelperTest {
Map<String, Pair<SecretKey, byte[]>> filteredKeys =
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
- assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+ assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
}
@Test
@@ -100,7 +100,7 @@ public class TestOnlyInsecureCertificateHelperTest {
Map<String, Pair<SecretKey, byte[]>> filteredKeys =
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
- assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+ assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
}
@Test
@@ -122,7 +122,7 @@ public class TestOnlyInsecureCertificateHelperTest {
Map<String, Pair<SecretKey, byte[]>> filteredKeys =
mHelper.keepOnlyWhitelistedInsecureKeys(rawKeys);
assertThat(filteredKeys.entrySet()).containsExactlyElementsIn(expectedResult.entrySet());
- assertThat(rawKeys.entrySet()).containsAllIn(filteredKeys.entrySet());
+ assertThat(rawKeys.entrySet()).containsAtLeastElementsIn(filteredKeys.entrySet());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
index 9836c64ea5b5..b0cb2ea85bf4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/certificate/CertXmlTest.java
@@ -70,7 +70,7 @@ public final class CertXmlTest {
CertXml certXml = CertXml.parse(certXmlBytes);
List<X509Certificate> endpointCerts = certXml.getAllEndpointCerts();
assertThat(endpointCerts).hasSize(3);
- assertThat(endpointCerts).containsAllOf(TestData.LEAF_CERT_1, TestData.LEAF_CERT_2);
+ assertThat(endpointCerts).containsAtLeast(TestData.LEAF_CERT_1, TestData.LEAF_CERT_2);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 154d42ce3f8d..3a292deeaac9 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -72,7 +72,8 @@ class OverlayActorEnforcerTests {
@JvmStatic
fun checkAllCasesHandled() {
// Assert that all states have been tested at least once.
- assertThat(CASES.map { it.state }.distinct()).containsAllIn(ActorState.values())
+ assertThat(CASES.map { it.state }.distinct())
+ .containsAtLeastElementsIn(ActorState.values())
}
@BeforeClass
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index c5d94875b684..c6823ebfd655 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -37,6 +37,7 @@ public final class ConversationInfoTest {
private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890");
private static final String PHONE_NUMBER = "+1234567890";
private static final String NOTIFICATION_CHANNEL_ID = "test : abc";
+ private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test";
@Test
public void testBuild() {
@@ -46,6 +47,8 @@ public final class ConversationInfoTest {
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true)
@@ -62,6 +65,9 @@ public final class ConversationInfoTest {
assertEquals(CONTACT_URI, conversationInfo.getContactUri());
assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
+ conversationInfo.getParentNotificationChannelId());
+ assertEquals(100L, conversationInfo.getLastEventTimestamp());
assertTrue(conversationInfo.isShortcutLongLived());
assertTrue(conversationInfo.isShortcutCachedForNotification());
assertTrue(conversationInfo.isImportant());
@@ -84,6 +90,8 @@ public final class ConversationInfoTest {
assertNull(conversationInfo.getContactUri());
assertNull(conversationInfo.getContactPhoneNumber());
assertNull(conversationInfo.getNotificationChannelId());
+ assertNull(conversationInfo.getParentNotificationChannelId());
+ assertEquals(0L, conversationInfo.getLastEventTimestamp());
assertFalse(conversationInfo.isShortcutLongLived());
assertFalse(conversationInfo.isShortcutCachedForNotification());
assertFalse(conversationInfo.isImportant());
@@ -103,6 +111,8 @@ public final class ConversationInfoTest {
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
.setImportant(true)
.setNotificationSilenced(true)
@@ -122,6 +132,8 @@ public final class ConversationInfoTest {
assertEquals(CONTACT_URI, destination.getContactUri());
assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
+ assertEquals(100L, destination.getLastEventTimestamp());
assertTrue(destination.isShortcutLongLived());
assertFalse(destination.isImportant());
assertTrue(destination.isNotificationSilenced());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index b2f7abbf84df..f37054d269b1 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -29,6 +29,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
@@ -45,6 +46,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Person;
import android.app.job.JobScheduler;
+import android.app.people.ConversationChannel;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
@@ -112,6 +114,7 @@ public final class DataManagerTest {
private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
private static final String PHONE_NUMBER = "+1234567890";
private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
+ private static final String PARENT_NOTIFICATION_CHANNEL_ID = "test";
private static final long MILLIS_PER_MINUTE = 1000L * 60L;
@Mock private Context mContext;
@@ -133,10 +136,12 @@ public final class DataManagerTest {
private ScheduledExecutorService mExecutorService;
private NotificationChannel mNotificationChannel;
+ private NotificationChannel mParentNotificationChannel;
private DataManager mDataManager;
private CancellationSignal mCancellationSignal;
private ShortcutChangeCallback mShortcutChangeCallback;
private BroadcastReceiver mShutdownBroadcastReceiver;
+ private ShortcutInfo mShortcutInfo;
private TestInjector mInjector;
@Before
@@ -157,6 +162,11 @@ public final class DataManagerTest {
}).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
addLocalServiceMock(NotificationManagerInternal.class, mNotificationManagerInternal);
+ mParentNotificationChannel = new NotificationChannel(
+ PARENT_NOTIFICATION_CHANNEL_ID, "test channel",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ when(mNotificationManagerInternal.getNotificationChannel(anyString(), anyInt(),
+ anyString())).thenReturn(mParentNotificationChannel);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
@@ -199,6 +209,7 @@ public final class DataManagerTest {
when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY));
when(mStatusBarNotification.getPostTime()).thenReturn(System.currentTimeMillis());
when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID);
+ when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
mNotificationChannel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT);
@@ -212,6 +223,13 @@ public final class DataManagerTest {
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
anyString(), anyInt(), any())).thenReturn(true);
+
+ mShortcutInfo = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ when(mShortcutServiceInternal.getShortcuts(
+ anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
+ anyInt(), anyInt(), anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(mShortcutInfo));
verify(mShortcutServiceInternal).addShortcutChangeCallback(
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
@@ -417,29 +435,28 @@ public final class DataManagerTest {
List<Range<Long>> activeNotificationOpenTimeSlots = getActiveSlotsForTestShortcut(
Event.NOTIFICATION_EVENT_TYPES);
assertEquals(1, activeNotificationOpenTimeSlots.size());
- verify(mShortcutServiceInternal).uncacheShortcuts(
- anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
- eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
- public void testNotificationDismissed() {
+ public void testUncacheShortcutsWhenNotificationsDismissed() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
-
- ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
- buildPerson());
- mDataManager.addOrUpdateConversationInfo(shortcut);
-
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- // Post one notification.
- shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- mDataManager.addOrUpdateConversationInfo(shortcut);
- listenerService.onNotificationPosted(mStatusBarNotification);
+ // The cached conversations are above the limit because every conversation has active
+ // notifications. To uncache one of them, the notifications for that conversation need to
+ // be dismissed.
+ for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ when(mNotification.getShortcutId()).thenReturn(shortcutId);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ }
- // Post another notification.
+ // Post another notification for the last conversation.
listenerService.onNotificationPosted(mStatusBarNotification);
// Removing one of the two notifications does not un-cache the shortcut.
@@ -452,13 +469,12 @@ public final class DataManagerTest {
listenerService.onNotificationRemoved(mStatusBarNotification, null,
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal).uncacheShortcuts(
- anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ anyInt(), any(), eq(TEST_PKG_NAME), anyList(), eq(USER_ID_PRIMARY),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
- public void testShortcutNotUncachedIfNotificationChannelCreated() {
+ public void testConversationIsNotRecentIfCustomized() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -472,15 +488,12 @@ public final class DataManagerTest {
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
+ assertEquals(1, mDataManager.getRecentConversations(USER_ID_PRIMARY).size());
+
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
- listenerService.onNotificationRemoved(mStatusBarNotification, null,
- NotificationListenerService.REASON_CANCEL_ALL);
- verify(mShortcutServiceInternal, never()).uncacheShortcuts(
- anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
- eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ assertTrue(mDataManager.getRecentConversations(USER_ID_PRIMARY).isEmpty());
}
@Test
@@ -561,53 +574,6 @@ public final class DataManagerTest {
}
@Test
- public void testUncacheShortcutWhenShutdown() {
- mDataManager.onUserUnlocked(USER_ID_PRIMARY);
-
- ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
- buildPerson());
- mDataManager.addOrUpdateConversationInfo(shortcut);
-
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
-
- listenerService.onNotificationPosted(mStatusBarNotification);
- shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- mDataManager.addOrUpdateConversationInfo(shortcut);
-
- mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
- verify(mShortcutServiceInternal).uncacheShortcuts(
- anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
- eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
- }
-
- @Test
- public void testDoNotUncacheShortcutWhenShutdownIfNotificationChannelCreated() {
- mDataManager.onUserUnlocked(USER_ID_PRIMARY);
-
- ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
- buildPerson());
- mDataManager.addOrUpdateConversationInfo(shortcut);
-
- NotificationListenerService listenerService =
- mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
-
- listenerService.onNotificationPosted(mStatusBarNotification);
- shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- mDataManager.addOrUpdateConversationInfo(shortcut);
-
- listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
- mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
-
- mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
- verify(mShortcutServiceInternal, never()).uncacheShortcuts(
- anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
- eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
- }
-
- @Test
public void testShortcutAddedOrUpdated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -623,17 +589,19 @@ public final class DataManagerTest {
}
@Test
- public void testShortcutDeleted() {
+ public void testShortcutsDeleted() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc1",
buildPerson());
ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc2",
buildPerson());
+ ShortcutInfo shortcut3 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc3",
+ buildPerson());
mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
- Arrays.asList(shortcut1, shortcut2), UserHandle.of(USER_ID_PRIMARY));
+ Arrays.asList(shortcut1, shortcut2, shortcut3), UserHandle.of(USER_ID_PRIMARY));
mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
- Collections.singletonList(shortcut1), UserHandle.of(USER_ID_PRIMARY));
+ List.of(shortcut1, shortcut3), UserHandle.of(USER_ID_PRIMARY));
List<ConversationInfo> conversations = getConversationsInPrimary();
@@ -641,7 +609,7 @@ public final class DataManagerTest {
assertEquals("sc2", conversations.get(0).getShortcutId());
verify(mNotificationManagerInternal)
- .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, "sc1");
+ .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, Set.of("sc1", "sc3"));
}
@Test
@@ -767,20 +735,57 @@ public final class DataManagerTest {
}
@Test
- public void testPruneInactiveCachedShortcuts() {
+ public void testDoNotUncacheShortcutWithActiveNotifications() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
- ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
- buildPerson());
- shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
- mDataManager.addOrUpdateConversationInfo(shortcut);
+ for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ when(mNotification.getShortcutId()).thenReturn(shortcutId);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ }
mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(), anyList(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testUncacheOldestCachedShortcut() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+ for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ when(mNotification.getShortcutId()).thenReturn(shortcutId);
+ when(mStatusBarNotification.getPostTime()).thenReturn(100L + i);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ NotificationListenerService.REASON_CANCEL);
+ }
+
+ // Only the shortcut #0 is uncached, all the others are not.
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
}
@Test
@@ -810,6 +815,148 @@ public final class DataManagerTest {
assertEquals(conversationInfo.getShortcutId(), TEST_SHORTCUT_ID);
}
+ @Test
+ public void testGetRecentConversations() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ assertEquals(1, result.size());
+ assertEquals(shortcut.getId(), result.get(0).getShortcutInfo().getId());
+ assertEquals(mParentNotificationChannel.getId(),
+ result.get(0).getParentNotificationChannel().getId());
+ assertEquals(mStatusBarNotification.getPostTime(), result.get(0).getLastEventTimestamp());
+ assertTrue(result.get(0).hasActiveNotifications());
+ }
+
+ @Test
+ public void testGetLastInteraction() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ assertEquals(mStatusBarNotification.getPostTime(),
+ mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID));
+ assertEquals(0L,
+ mDataManager.getLastInteraction("not_test_pkg", USER_ID_PRIMARY, TEST_SHORTCUT_ID));
+ assertEquals(0L,
+ mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_PRIMARY_MANAGED,
+ TEST_SHORTCUT_ID));
+ assertEquals(0L,
+ mDataManager.getLastInteraction(TEST_PKG_NAME, USER_ID_SECONDARY,
+ TEST_SHORTCUT_ID));
+ }
+
+ @Test
+ public void testNonCachedShortcutNotInRecentList() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY_MANAGED,
+ TEST_SHORTCUT_ID, buildPerson());
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ public void testCustomizedConversationNotInRecentList() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ // Post a notification and customize the notification settings.
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+ mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+ List<ConversationChannel> result = mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ public void testRemoveRecentConversation() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ NotificationListenerService.REASON_CANCEL);
+ mDataManager.removeRecentConversation(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ USER_ID_PRIMARY);
+
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)),
+ eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+
+ @Test
+ public void testRemoveAllRecentConversations() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "1",
+ buildPerson());
+ shortcut1.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut1);
+
+ ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "2",
+ buildPerson());
+ shortcut2.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.addOrUpdateConversationInfo(shortcut2);
+
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+ // Post a notification and then dismiss it for conversation #1.
+ when(mNotification.getShortcutId()).thenReturn("1");
+ listenerService.onNotificationPosted(mStatusBarNotification);
+ listenerService.onNotificationRemoved(mStatusBarNotification, null,
+ NotificationListenerService.REASON_CANCEL);
+
+ // Post a notification for conversation #2, but don't dismiss it. Its shortcut won't be
+ // uncached when removeAllRecentConversations() is called.
+ when(mNotification.getShortcutId()).thenReturn("2");
+ listenerService.onNotificationPosted(mStatusBarNotification);
+
+ mDataManager.removeAllRecentConversations(USER_ID_PRIMARY);
+
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("1")),
+ eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME), eq(Collections.singletonList("2")),
+ eq(USER_ID_PRIMARY), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+
private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
new file mode 100644
index 000000000000..c4c2f68e8219
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.pm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.IDataLoaderStatusListener;
+import android.content.pm.PackageManager;
+import android.os.ConditionVariable;
+import android.os.incremental.IStorageHealthListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit tests for {@link IncrementalStates}.
+ * Run with: atest -c FrameworksServicesTests:com.android.server.pm.IncrementalStatesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class IncrementalStatesTest {
+ private IncrementalStates mIncrementalStates;
+ private ConditionVariable mUnstartableCalled = new ConditionVariable();
+ private ConditionVariable mStartableCalled = new ConditionVariable();
+ private ConditionVariable mFullyLoadedCalled = new ConditionVariable();
+ private AtomicInteger mUnstartableReason = new AtomicInteger(0);
+ private static final int WAIT_TIMEOUT_MILLIS = 1000; /* 1 second */
+ private IncrementalStates.Callback mCallback = new IncrementalStates.Callback() {
+ @Override
+ public void onPackageUnstartable(int reason) {
+ mUnstartableCalled.open();
+ mUnstartableReason.set(reason);
+ }
+
+ @Override
+ public void onPackageStartable() {
+ mStartableCalled.open();
+ }
+
+ @Override
+ public void onPackageFullyLoaded() {
+ mFullyLoadedCalled.open();
+ }
+ };
+
+ /**
+ * Setup the tests as if the package has just been committed.
+ * By default the package is now startable and is loading.
+ */
+ @Before
+ public void setUp() {
+ mIncrementalStates = new IncrementalStates();
+ assertFalse(mIncrementalStates.isStartable());
+ mIncrementalStates.setCallback(mCallback);
+ mIncrementalStates.onCommit(true);
+ // Test that package is now startable and loading
+ assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ assertTrue(mIncrementalStates.isLoading());
+ mStartableCalled.close();
+ mUnstartableCalled.close();
+ mFullyLoadedCalled.close();
+ }
+
+ /**
+ * Test that startable state changes to false when Incremental Storage is unhealthy.
+ */
+ @Test
+ public void testStartableTransition_IncrementalStorageUnhealthy() {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+ // Test that package is now unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get());
+ }
+
+ /**
+ * Test that the package is still startable when Incremental Storage has pending reads.
+ */
+ @Test
+ public void testStartableTransition_IncrementalStorageReadsPending()
+ throws InterruptedException {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_READS_PENDING);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that the package is still startable when Incremental Storage is at blocked status.
+ */
+ @Test
+ public void testStartableTransition_IncrementalStorageBlocked() {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_BLOCKED);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that the package is still startable when Data Loader has unknown transportation issues.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderTransportError() {
+ mIncrementalStates.onStreamStatusChanged(
+ IDataLoaderStatusListener.STREAM_TRANSPORT_ERROR);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Data Loader has data integrity issues.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderIntegrityError() {
+ mIncrementalStates.onStreamStatusChanged(
+ IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+ // Test that package is now unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
+ mUnstartableReason.get());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Data Loader has data source issues.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderSourceError() {
+ mIncrementalStates.onStreamStatusChanged(
+ IDataLoaderStatusListener.STREAM_SOURCE_ERROR);
+ // Test that package is now unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
+ mUnstartableReason.get());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Data Loader hits limited storage while
+ * Incremental storage has a pending reads.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderStorageErrorWhenIncrementalStoragePending()
+ throws InterruptedException {
+ mIncrementalStates.onStreamStatusChanged(
+ IDataLoaderStatusListener.STREAM_STORAGE_ERROR);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_READS_PENDING);
+ // Test that package is now unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
+ mUnstartableReason.get());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Data Loader hits limited storage while
+ * Incremental storage is at blocked status.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderStorageErrorWhenIncrementalStorageBlocked()
+ throws InterruptedException {
+ mIncrementalStates.onStreamStatusChanged(
+ IDataLoaderStatusListener.STREAM_STORAGE_ERROR);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_BLOCKED);
+ // Test that package is now unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
+ mUnstartableReason.get());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Incremental Storage is unhealthy, and it
+ * becomes startable again when Incremental Storage is healthy again.
+ */
+ @Test
+ public void testStartableTransition_IncrementalStorageUnhealthyBackToHealthy()
+ throws InterruptedException {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+ // Test that package is unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_OK);
+ // Test that package is now startable
+ assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that the package becomes unstartable when Data Loader has data integrity issue, and it
+ * becomes startable again when Data Loader is healthy again.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderUnhealthyBackToHealthy()
+ throws InterruptedException {
+ mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+ // Test that package is unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+
+ mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_HEALTHY);
+ // Test that package is now startable
+ assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that the package becomes unstartable when both Incremental Storage and Data Loader
+ * are unhealthy, and it becomes startable again when both Incremental Storage and Data Loader
+ * are healthy again.
+ */
+ @Test
+ public void testStartableTransition_DataLoaderAndIncrementalStorageUnhealthyBackToHealthy()
+ throws InterruptedException {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+ mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_INTEGRITY_ERROR);
+ // Test that package is unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+
+ mIncrementalStates.onStreamStatusChanged(IDataLoaderStatusListener.STREAM_HEALTHY);
+ // Test that package is still unstartable
+ assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ mIncrementalStates.onStorageHealthStatusChanged(IStorageHealthListener.HEALTH_STATUS_OK);
+ // Test that package is now startable
+ assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that when loading progress is 1, the package becomes fully loaded, and the change of
+ * Incremental Storage health status does not affect the startable state.
+ */
+ @Test
+ public void testStartableTransition_HealthStatusChangeWhenFullyLoaded()
+ throws InterruptedException {
+ mIncrementalStates.setProgress(1.0f);
+ // Test that package is now fully loaded
+ assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isLoading());
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+ // Test that package is still startable
+ assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ }
+
+ /**
+ * Test that when loading progress is 1, the package becomes fully loaded, and if the package
+ * was unstartable, it becomes startable.
+ */
+ @Test
+ public void testLoadingTransition_FullyLoadedWhenUnstartable() throws InterruptedException {
+ mIncrementalStates.onStorageHealthStatusChanged(
+ IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
+ // Test that package is unstartable
+ assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ // Test that package is still loading
+ assertTrue(mIncrementalStates.isLoading());
+
+ mIncrementalStates.setProgress(0.5f);
+ // Test that package is still unstartable
+ assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isStartable());
+ mIncrementalStates.setProgress(1.0f);
+ // Test that package is now startable
+ assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertTrue(mIncrementalStates.isStartable());
+ // Test that package is now fully loaded
+ assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
+ assertFalse(mIncrementalStates.isLoading());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index dac05424e01f..6255630712ae 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -62,6 +62,7 @@ import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.server.LocalServices;
import com.android.server.pm.parsing.pkg.PackageImpl;
import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.PermissionSettings;
import com.google.common.truth.Truth;
@@ -94,6 +95,8 @@ public class PackageManagerSettingsTests {
PermissionSettings mPermissionSettings;
@Mock
RuntimePermissionsPersistence mRuntimePermissionsPersistence;
+ @Mock
+ LegacyPermissionDataProvider mPermissionDataProvider;
@Before
public void initializeMocks() {
@@ -115,7 +118,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
verifyKeySetMetaData(settings);
}
@@ -129,7 +132,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
// write out, read back in and verify the same
@@ -145,7 +148,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -167,13 +170,13 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
settings.writeLPr();
// Create Settings again to make it read from the new files
settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -196,7 +199,8 @@ public class PackageManagerSettingsTests {
writePackageRestrictions_noSuspendingPackageXml(0);
final Object lock = new Object();
final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, lock);
+ final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null,
+ lock);
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, createPackageSetting(PACKAGE_NAME_2));
settingsUnderTest.readPackageRestrictionsLPr(0);
@@ -219,7 +223,8 @@ public class PackageManagerSettingsTests {
writePackageRestrictions_noSuspendParamsMapXml(0);
final Object lock = new Object();
final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, lock);
+ final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null,
+ lock);
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, createPackageSetting(PACKAGE_NAME_1));
settingsUnderTest.readPackageRestrictionsLPr(0);
@@ -246,7 +251,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_suspendInfo() {
final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
+ final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null,
new Object());
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
@@ -344,7 +349,7 @@ public class PackageManagerSettingsTests {
@Test
public void testReadWritePackageRestrictions_distractionFlags() {
final Context context = InstrumentationRegistry.getTargetContext();
- final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
+ final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null, null,
new Object());
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
@@ -389,7 +394,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getTargetContext();
final Object lock = new Object();
final Settings settingsUnderTest = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
ps1.appId = Process.FIRST_APPLICATION_UID;
ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
@@ -465,7 +470,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
Settings settings = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
assertThat(settings.readLPw(createFakeUsers()), is(true));
// Enable/Disable a package
@@ -638,7 +643,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
final Settings testSettings01 = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 =
@@ -748,7 +753,7 @@ public class PackageManagerSettingsTests {
final Context context = InstrumentationRegistry.getContext();
final Object lock = new Object();
final Settings testSettings01 = new Settings(context.getFilesDir(), mPermissionSettings,
- mRuntimePermissionsPersistence, lock);
+ mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
final SharedUserSetting testUserSetting01 = createSharedUserSetting(
testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
final PackageSetting testPkgSetting01 = Settings.createNewSetting(
@@ -886,8 +891,10 @@ public class PackageManagerSettingsTests {
assertNotSame(origPkgSetting.mimeGroups, testPkgSetting.mimeGroups);
}
assertThat(origPkgSetting.mimeGroups, is(testPkgSetting.mimeGroups));
- assertNotSame(origPkgSetting.mPermissionsState, testPkgSetting.mPermissionsState);
- assertThat(origPkgSetting.mPermissionsState, is(testPkgSetting.mPermissionsState));
+ assertNotSame(origPkgSetting.mLegacyPermissionsState,
+ testPkgSetting.mLegacyPermissionsState);
+ assertThat(origPkgSetting.mLegacyPermissionsState,
+ is(testPkgSetting.mLegacyPermissionsState));
assertThat(origPkgSetting.name, is(testPkgSetting.name));
// mOldCodePaths is _not_ copied
// assertNotSame(origPkgSetting.mOldCodePaths, testPkgSetting.mOldCodePaths);
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index a550b27a62a2..f1930d7268d7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -44,7 +44,8 @@ import org.mockito.junit.MockitoJUnitRunner;
public class SELinuxMMACTest {
private static final String PACKAGE_NAME = "my.package";
- private static final int OPT_IN_VERSION = Build.VERSION_CODES.R;
+ private static final int LATEST_OPT_IN_VERSION = Build.VERSION_CODES.S;
+ private static final int R_OPT_IN_VERSION = Build.VERSION_CODES.R;
@Mock
PlatformCompat mMockCompatibility;
@@ -56,7 +57,17 @@ public class SELinuxMMACTest {
argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
.thenReturn(true);
assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
- is("default:targetSdkVersion=" + OPT_IN_VERSION));
+ is("default:targetSdkVersion=" + LATEST_OPT_IN_VERSION));
+ }
+
+ @Test
+ public void getSeInfoOptInToR() {
+ AndroidPackage pkg = makePackage(Build.VERSION_CODES.P);
+ when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
+ argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+ .thenReturn(true);
+ assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+ is("default:targetSdkVersion=" + R_OPT_IN_VERSION));
}
@Test
@@ -70,13 +81,33 @@ public class SELinuxMMACTest {
}
@Test
- public void getSeInfoNoOptInButAlreadyR() {
- AndroidPackage pkg = makePackage(OPT_IN_VERSION);
+ public void getSeInfoNoOptInButAlreadyLatest() {
+ AndroidPackage pkg = makePackage(LATEST_OPT_IN_VERSION);
when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
.thenReturn(false);
assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
- is("default:targetSdkVersion=" + OPT_IN_VERSION));
+ is("default:targetSdkVersion=" + LATEST_OPT_IN_VERSION));
+ }
+
+ @Test
+ public void getSeInfoNoOptInButAlreadyR() {
+ AndroidPackage pkg = makePackage(R_OPT_IN_VERSION);
+ when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
+ argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+ .thenReturn(false);
+ assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+ is("default:targetSdkVersion=" + R_OPT_IN_VERSION));
+ }
+
+ @Test
+ public void getSeInfoOptInRButLater() {
+ AndroidPackage pkg = makePackage(R_OPT_IN_VERSION + 1);
+ when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
+ argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+ .thenReturn(true);
+ assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+ is("default:targetSdkVersion=" + (R_OPT_IN_VERSION + 1)));
}
private AndroidPackage makePackage(int targetSdkVersion) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 0ccc02663dc5..2250185cf3d7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import static android.content.pm.UserInfo.FLAG_DEMO;
+import static android.content.pm.UserInfo.FLAG_DISABLED;
import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.content.pm.UserInfo.FLAG_GUEST;
@@ -166,6 +167,23 @@ public class UserManagerServiceUserInfoTest {
assertTrue(mUserManagerService.isUserOfType(testId, typeName));
}
+ /** Test UserInfo.supportsSwitchTo() for partial user. */
+ @Test
+ public void testSupportSwitchTo_partial() throws Exception {
+ UserInfo userInfo = createUser(100, FLAG_FULL, null);
+ userInfo.partial = true;
+ assertFalse("Switching to a partial user should be disabled",
+ userInfo.supportsSwitchTo());
+ }
+
+ /** Test UserInfo.supportsSwitchTo() for disabled user. */
+ @Test
+ public void testSupportSwitchTo_disabled() throws Exception {
+ UserInfo userInfo = createUser(100, FLAG_DISABLED, null);
+ assertFalse("Switching to a DISABLED user should be disabled",
+ userInfo.supportsSwitchTo());
+ }
+
/** Test UserInfo.supportsSwitchTo() for precreated users. */
@Test
public void testSupportSwitchTo_preCreated() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 44bb58f62253..22b07157e94e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -639,7 +639,7 @@ public final class UserManagerTest {
UserInfo user1 = createUser("User 1", 0);
UserInfo user2 = createUser("User 2", 0);
long[] serialNumbersOfUsers = mUserManager.getSerialNumbersOfUsers(false);
- assertThat(serialNumbersOfUsers).asList().containsAllOf(
+ assertThat(serialNumbersOfUsers).asList().containsAtLeast(
(long) user1.serialNumber, (long) user2.serialNumber);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
index 4381bfdb0b56..af161ee8b818 100644
--- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -39,7 +39,6 @@ import static org.mockito.Mockito.when;
import android.attention.AttentionManagerInternal;
import android.attention.AttentionManagerInternal.AttentionCallbackInternal;
-import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
@@ -64,8 +63,6 @@ public class AttentionDetectorTest extends AndroidTestCase {
private static final long DEFAULT_DIM_DURATION_MILLIS = 6_000L;
@Mock
- private PackageManager mPackageManager;
- @Mock
private AttentionManagerInternal mAttentionManagerInternal;
@Mock
private WindowManagerInternal mWindowManagerInternal;
@@ -80,9 +77,6 @@ public class AttentionDetectorTest extends AndroidTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mPackageManager.getAttentionServicePackageName()).thenReturn("com.google.android.as");
- when(mPackageManager.checkPermission(any(), any())).thenReturn(
- PackageManager.PERMISSION_GRANTED);
when(mAttentionManagerInternal.checkAttention(anyLong(), any()))
.thenReturn(true);
when(mWindowManagerInternal.isKeyguardShowingAndNotOccluded()).thenReturn(false);
@@ -157,16 +151,6 @@ public class AttentionDetectorTest extends AndroidTestCase {
}
@Test
- public void testOnUserActivity_doesntCheckIfNotSufficientPermissions() {
- when(mPackageManager.checkPermission(any(), any())).thenReturn(
- PackageManager.PERMISSION_DENIED);
-
- long when = registerAttention();
- verify(mAttentionManagerInternal, never()).checkAttention(anyLong(), any());
- assertThat(mNextDimming).isEqualTo(when);
- }
-
- @Test
public void testOnUserActivity_doesntCrashIfNoAttentionService() {
mAttentionManagerInternal = null;
registerAttention();
@@ -452,7 +436,6 @@ public class AttentionDetectorTest extends AndroidTestCase {
super(AttentionDetectorTest.this.mOnUserAttention, new Object());
mAttentionManager = mAttentionManagerInternal;
mWindowManager = mWindowManagerInternal;
- mPackageManager = AttentionDetectorTest.this.mPackageManager;
mContentResolver = getContext().getContentResolver();
}
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 419fb14df340..6febae00f0fb 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -80,6 +80,7 @@ import com.android.server.SystemService;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.BatteryReceiver;
+import com.android.server.power.PowerManagerService.BinderService;
import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
@@ -179,6 +180,7 @@ public class PowerManagerServiceTest {
when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+ when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
mDisplayPowerRequest = new DisplayPowerRequest();
addLocalServiceMock(LightsManager.class, mLightsManagerMock);
@@ -983,6 +985,74 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testIsAmbientDisplaySuppressedForTokenByApp_ambientDisplayUnavailable()
+ throws Exception {
+ createService();
+ when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(false);
+
+ BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsAmbientDisplaySuppressedForTokenByApp_default()
+ throws Exception {
+ createService();
+
+ BinderService service = mService.getBinderServiceInstance();
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsAmbientDisplaySuppressedForTokenByApp_suppressedByCallingApp()
+ throws Exception {
+ createService();
+ BinderService service = mService.getBinderServiceInstance();
+ service.suppressAmbientDisplay("test", true);
+
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+ .isTrue();
+ // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsAmbientDisplaySuppressedForTokenByApp_notSuppressedByCallingApp()
+ throws Exception {
+ createService();
+ BinderService service = mService.getBinderServiceInstance();
+ service.suppressAmbientDisplay("test", false);
+
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", Binder.getCallingUid()))
+ .isFalse();
+ // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test", /* appUid= */ 123))
+ .isFalse();
+ }
+
+ @Test
+ public void testIsAmbientDisplaySuppressedForTokenByApp_multipleTokensSuppressedByCallingApp()
+ throws Exception {
+ createService();
+ BinderService service = mService.getBinderServiceInstance();
+ service.suppressAmbientDisplay("test1", true);
+ service.suppressAmbientDisplay("test2", true);
+
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", Binder.getCallingUid()))
+ .isTrue();
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", Binder.getCallingUid()))
+ .isTrue();
+ // Check that isAmbientDisplaySuppressedForTokenByApp doesn't return true for another app.
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test1", /* appUid= */ 123))
+ .isFalse();
+ assertThat(service.isAmbientDisplaySuppressedForTokenByApp("test2", /* appUid= */ 123))
+ .isFalse();
+ }
+
+ @Test
public void testSetPowerBoost_redirectsCallToNativeWrapper() {
createService();
mService.systemReady(null);
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index b6a0979e5016..eedc9781aa40 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -46,41 +46,25 @@ public class RollbackStoreTest {
private static final String INSTALLER = "some.installer";
private static final Correspondence<VersionedPackage, VersionedPackage> VER_PKG_CORR =
- new Correspondence<VersionedPackage, VersionedPackage>() {
- @Override
- public boolean compare(VersionedPackage a, VersionedPackage b) {
- if (a == null || b == null) {
- return a == b;
- }
- return a.equals(b);
+ Correspondence.from((VersionedPackage a, VersionedPackage b) -> {
+ if (a == null || b == null) {
+ return a == b;
}
-
- @Override
- public String toString() {
- return "is the same as";
- }
- };
+ return a.equals(b);
+ }, "is the same as");
private static final Correspondence<PackageRollbackInfo.RestoreInfo,
PackageRollbackInfo.RestoreInfo>
RESTORE_INFO_CORR =
- new Correspondence<PackageRollbackInfo.RestoreInfo, PackageRollbackInfo.RestoreInfo>() {
- @Override
- public boolean compare(PackageRollbackInfo.RestoreInfo a,
- PackageRollbackInfo.RestoreInfo b) {
- if (a == null || b == null) {
- return a == b;
- }
- return a.userId == b.userId
- && a.appId == b.appId
- && Objects.equals(a.seInfo, b.seInfo);
- }
-
- @Override
- public String toString() {
- return "is the same as";
+ Correspondence.from((PackageRollbackInfo.RestoreInfo a,
+ PackageRollbackInfo.RestoreInfo b) -> {
+ if (a == null || b == null) {
+ return a == b;
}
- };
+ return a.userId == b.userId
+ && a.appId == b.appId
+ && Objects.equals(a.seInfo, b.seInfo);
+ }, "is the same as");
private static final String JSON_ROLLBACK_NO_EXT = "{'info':{'rollbackId':123,'packages':"
+ "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index ebcf10dd019f..509eb2563376 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -59,6 +59,7 @@ import android.os.HidlMemoryUtil;
import android.os.HwParcel;
import android.os.IHwBinder;
import android.os.IHwInterface;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.system.ErrnoException;
@@ -126,7 +127,7 @@ public class SoundTriggerMiddlewareImplTest {
model.uuid = "12345678-2345-3456-4567-abcdef987654";
model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
byte[] data = new byte[]{91, 92, 93, 94, 95};
- model.data = byteArrayToFileDescriptor(data);
+ model.data = new ParcelFileDescriptor(byteArrayToFileDescriptor(data));
model.dataSize = data.length;
return model;
}
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
index 46224cb8f855..8fb2e6838412 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java
@@ -132,7 +132,7 @@ public class DiskStatsFileLoggerTest extends AndroidTestCase {
appSizes.getLong(i), cacheSizes.getLong(i));
apps.add(app);
}
- assertThat(apps).containsAllOf(new AppSizeGrouping("com.test.app", 1100, 20),
+ assertThat(apps).containsAtLeast(new AppSizeGrouping("com.test.app", 1100, 20),
new AppSizeGrouping("com.test.app2", 11, 2));
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index d7ed96fd5833..682a80c9b297 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -16,22 +16,23 @@
package com.android.server.timezonedetector;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
-import static android.app.timezonedetector.TimeZoneCapabilities.CAPABILITY_POSSESSED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_ALLOWED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_APPLICABLE;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_NOT_SUPPORTED;
+import static android.app.time.TimeZoneCapabilities.CAPABILITY_POSSESSED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import android.app.timezonedetector.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import org.junit.Test;
/**
- * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilities} and
- * {@link android.app.timezonedetector.TimeZoneConfiguration} that can be generated from it.
+ * Tests for {@link ConfigurationInternal} and the {@link TimeZoneCapabilitiesAndConfig}.
*/
public class ConfigurationInternalTest {
@@ -46,6 +47,7 @@ public class ConfigurationInternalTest {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -59,13 +61,20 @@ public class ConfigurationInternalTest {
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_APPLICABLE, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_APPLICABLE,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
@@ -77,13 +86,20 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_APPLICABLE,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
@@ -93,6 +109,7 @@ public class ConfigurationInternalTest {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -106,13 +123,20 @@ public class ConfigurationInternalTest {
assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
@@ -124,13 +148,20 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_ALLOWED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_ALLOWED,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
@@ -140,6 +171,7 @@ public class ConfigurationInternalTest {
ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(false)
+ .setGeoDetectionSupported(false)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -153,13 +185,82 @@ public class ConfigurationInternalTest {
assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOnConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOnConfig.asConfiguration(), capabilities.getConfiguration());
- assertTrue(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
+ }
+ {
+ ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(false)
+ .build();
+ assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
+ assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
+ }
+ }
+
+ /**
+ * Tests when {@link ConfigurationInternal#isAutoDetectionSupported()} is true, but
+ * {@link ConfigurationInternal#isGeoDetectionSupported()} is false.
+ */
+ @Test
+ public void test_geoDetectNotSupported() {
+ ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+ .setUserConfigAllowed(true)
+ .setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(false)
+ .setAutoDetectionEnabled(true)
+ .setLocationEnabled(true)
+ .setGeoDetectionEnabled(true)
+ .build();
+ {
+ ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
+ .setAutoDetectionEnabled(true)
+ .build();
+ assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
+ assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
+ assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOnConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_APPLICABLE,
+ capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertTrue(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
@@ -170,13 +271,19 @@ public class ConfigurationInternalTest {
assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
- TimeZoneCapabilities capabilities = autoOffConfig.createCapabilities();
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureAutoDetectionEnabled());
- assertEquals(CAPABILITY_NOT_SUPPORTED, capabilities.getConfigureGeoDetectionEnabled());
- assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZone());
- assertEquals(autoOffConfig.asConfiguration(), capabilities.getConfiguration());
- assertFalse(capabilities.getConfiguration().isAutoDetectionEnabled());
- assertTrue(capabilities.getConfiguration().isGeoDetectionEnabled());
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ autoOffConfig.createCapabilitiesAndConfig();
+
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ assertEquals(CAPABILITY_POSSESSED,
+ capabilities.getConfigureAutoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_NOT_SUPPORTED,
+ capabilities.getConfigureGeoDetectionEnabledCapability());
+ assertEquals(CAPABILITY_POSSESSED, capabilities.getSuggestManualTimeZoneCapability());
+
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ assertFalse(configuration.isAutoDetectionEnabled());
+ assertTrue(configuration.isGeoDetectionEnabled());
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 4ef20829f2dc..bad380acf4b3 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -22,10 +22,11 @@ import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilities;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneCapabilities;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
@@ -67,20 +68,25 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
}
@Override
- public boolean updateConfiguration(@NonNull TimeZoneConfiguration requestedChanges) {
+ public boolean updateConfiguration(
+ @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges) {
assertNotNull(mConfigurationInternal);
assertNotNull(requestedChanges);
// Simulate the real strategy's behavior: the new configuration will be updated to be the
// old configuration merged with the new if the user has the capability to up the settings.
// Then, if the configuration changed, the change listener is invoked.
- TimeZoneCapabilities capabilities = mConfigurationInternal.createCapabilities();
- TimeZoneConfiguration newConfiguration = capabilities.applyUpdate(requestedChanges);
+ TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
+ mConfigurationInternal.createCapabilitiesAndConfig();
+ TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+ TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+ TimeZoneConfiguration newConfiguration =
+ capabilities.tryApplyConfigChanges(configuration, requestedChanges);
if (newConfiguration == null) {
return false;
}
- if (!newConfiguration.equals(capabilities.getConfiguration())) {
+ if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
// Note: Unlike the real strategy, the listeners is invoked synchronously.
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index 27b04b6ab17d..d2452eaee657 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -31,10 +31,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import android.app.timezonedetector.ITimeZoneConfigurationListener;
+import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.HandlerThread;
@@ -91,85 +91,85 @@ public class TimeZoneDetectorServiceTest {
}
@Test(expected = SecurityException.class)
- public void testGetCapabilities_withoutPermission() {
+ public void testGetCapabilitiesAndConfig_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
try {
- mTimeZoneDetectorService.getCapabilities();
+ mTimeZoneDetectorService.getCapabilitiesAndConfig();
fail();
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
- public void testGetCapabilities() {
+ public void testGetCapabilitiesAndConfig() {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(configuration);
- assertEquals(configuration.createCapabilities(),
- mTimeZoneDetectorService.getCapabilities());
+ assertEquals(configuration.createCapabilitiesAndConfig(),
+ mTimeZoneDetectorService.getCapabilitiesAndConfig());
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
@Test(expected = SecurityException.class)
- public void testAddConfigurationListener_withoutPermission() {
+ public void testAddListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
try {
- mTimeZoneDetectorService.addConfigurationListener(mockListener);
+ mTimeZoneDetectorService.addListener(mockListener);
fail();
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test(expected = SecurityException.class)
- public void testRemoveConfigurationListener_withoutPermission() {
+ public void testRemoveListener_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
try {
- mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+ mTimeZoneDetectorService.removeListener(mockListener);
fail("Expected a SecurityException");
} finally {
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
}
}
@Test
- public void testConfigurationChangeListenerRegistrationAndCallbacks() throws Exception {
+ public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
mFakeTimeZoneDetectorStrategy.initializeConfiguration(initialConfiguration);
IBinder mockListenerBinder = mock(IBinder.class);
- ITimeZoneConfigurationListener mockListener = mock(ITimeZoneConfigurationListener.class);
+ ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
{
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
when(mockListener.asBinder()).thenReturn(mockListenerBinder);
- mTimeZoneDetectorService.addConfigurationListener(mockListener);
+ mTimeZoneDetectorService.addListener(mockListener);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).linkToDeath(any(), anyInt());
@@ -186,7 +186,7 @@ public class TimeZoneDetectorServiceTest {
mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -200,10 +200,10 @@ public class TimeZoneDetectorServiceTest {
// Now remove the listener, change the config again, and verify the listener is not
// called.
- mTimeZoneDetectorService.removeConfigurationListener(mockListener);
+ mTimeZoneDetectorService.removeListener(mockListener);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener).asBinder();
verify(mockListenerBinder).unlinkToDeath(any(), eq(0));
@@ -219,7 +219,7 @@ public class TimeZoneDetectorServiceTest {
mTimeZoneDetectorService.updateConfiguration(autoDetectDisabledConfiguration);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.WRITE_SECURE_SETTINGS),
+ eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION),
anyString());
verify(mockListener, never()).onChange();
verifyNoMoreInteractions(mockListenerBinder, mockListener, mMockContext);
@@ -354,7 +354,7 @@ public class TimeZoneDetectorServiceTest {
}
private static TimeZoneConfiguration createTimeZoneConfiguration(boolean autoDetectionEnabled) {
- return new TimeZoneConfiguration.Builder(ARBITRARY_USER_ID)
+ return new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(autoDetectionEnabled)
.build();
}
@@ -365,6 +365,7 @@ public class TimeZoneDetectorServiceTest {
final boolean geoDetectionEnabled = autoDetectionEnabled;
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(autoDetectionEnabled)
.setLocationEnabled(geoDetectionEnabled)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 1cdf19319209..a6ffd201c16e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -39,11 +39,11 @@ import static org.junit.Assert.fail;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.TimeZoneConfiguration;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
-import android.app.timezonedetector.TimeZoneConfiguration;
import android.util.IndentingPrintWriter;
import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -91,6 +91,7 @@ public class TimeZoneDetectorStrategyImplTest {
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
.setGeoDetectionEnabled(false)
@@ -100,6 +101,7 @@ public class TimeZoneDetectorStrategyImplTest {
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(false)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
.setGeoDetectionEnabled(true)
@@ -109,32 +111,36 @@ public class TimeZoneDetectorStrategyImplTest {
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(false)
+ .setGeoDetectionSupported(false)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
.setGeoDetectionEnabled(false)
.build();
- private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
+ private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
- .setAutoDetectionEnabled(false)
+ .setGeoDetectionSupported(false)
+ .setAutoDetectionEnabled(true)
.setLocationEnabled(true)
- .setGeoDetectionEnabled(false)
+ .setGeoDetectionEnabled(true)
.build();
- private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_ENABLED =
+ private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setUserConfigAllowed(true)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setAutoDetectionEnabled(false)
.setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setGeoDetectionEnabled(false)
.build();
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED =
new ConfigurationInternal.Builder(USER_ID)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
@@ -144,6 +150,7 @@ public class TimeZoneDetectorStrategyImplTest {
private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED =
new ConfigurationInternal.Builder(USER_ID)
.setAutoDetectionSupported(true)
+ .setGeoDetectionSupported(true)
.setUserConfigAllowed(true)
.setAutoDetectionEnabled(true)
.setLocationEnabled(true)
@@ -186,26 +193,27 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Set the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// Nothing should have happened: it was initialized in this state.
script.verifyConfigurationNotChanged();
// Update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_DISABLED_GEO_DISABLED);
// Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */);
// The settings should have been changed and the StrategyListener onChange() called.
script.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_ENABLED);
@@ -216,20 +224,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script().initializeConfig(CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
- // Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ // Try to update the configuration with auto detection enabled.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
- // Try to update the configuration to enable geolocation time zone detection.
+ // Try to update the configuration to enable geolocation time zone detection.
script.simulateUpdateConfiguration(
- CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
@@ -240,19 +250,53 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script().initializeConfig(CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED);
// Try to update the configuration with auto detection disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, false /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
- // Update the configuration with auto detection enabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, false /* expectedResult */);
+ // Try to update the configuration with auto detection enabled.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, false /* expectedResult */);
// The settings should not have been changed: user shouldn't have the capabilities.
script.verifyConfigurationNotChanged();
}
@Test
+ public void testUpdateConfiguration_autoDetectSupportedGeoNotSupported() {
+ Script script = new Script().initializeConfig(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED);
+
+ // Update the configuration with auto detection disabled.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */);
+
+ // The settings should have been changed and the StrategyListener onChange() called.
+ ConfigurationInternal expectedConfig =
+ new ConfigurationInternal.Builder(CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED)
+ .setAutoDetectionEnabled(false)
+ .build();
+ script.verifyConfigurationChangedAndReset(expectedConfig);
+
+ // Try to update the configuration with geo detection disabled.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_DISABLED, false /* expectedResult */);
+
+ // The settings should not have been changed: user shouldn't have the capability to modify
+ // the setting when the feature is disabled.
+ script.verifyConfigurationNotChanged();
+
+ // Try to update the configuration with geo detection enabled.
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, false /* expectedResult */);
+
+ // The settings should not have been changed: user shouldn't have the capability to modify
+ // the setting when the feature is disabled.
+ script.verifyConfigurationNotChanged();
+ }
+
+ @Test
public void testEmptyTelephonySuggestions() {
TelephonyTimeZoneSuggestion slotIndex1TimeZoneSuggestion =
createEmptySlotIndex1Suggestion();
@@ -389,7 +433,8 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting on should cause the device setting to be set.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */);
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */);
// When time zone detection is already enabled the suggestion (if it scores highly
// enough) should be set immediately.
@@ -406,7 +451,8 @@ public class TimeZoneDetectorStrategyImplTest {
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Toggling the time zone setting should off should do nothing.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Assert internal service state.
@@ -588,18 +634,20 @@ public class TimeZoneDetectorStrategyImplTest {
// Toggling time zone detection should set the device time zone only if the current setting
// value is different from the most recent telephony suggestion.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
- .simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ .simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged();
// Simulate a user turning auto detection off, a new suggestion being made while auto
// detection is off, and the user turning it on again.
- script.simulateUpdateConfiguration(CONFIG_AUTO_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_DISABLED, true /* expectedResult */)
.simulateTelephonyTimeZoneSuggestion(newYorkSuggestion)
.verifyTimeZoneNotChanged();
// Latest suggestion should be used.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(newYorkSuggestion);
}
@@ -784,7 +832,7 @@ public class TimeZoneDetectorStrategyImplTest {
assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
// Turn off geo detection and verify the latest suggestion is cleared.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_GEO_DETECTION_DISABLED, true)
.verifyConfigurationChangedAndReset(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
// Assert internal service state.
@@ -824,19 +872,21 @@ public class TimeZoneDetectorStrategyImplTest {
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
// disabled.
- script.simulateUpdateConfiguration(CONFIG_AUTO_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(USER_ID, CONFIG_AUTO_ENABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
// Changing the detection to enable geo detection won't cause the device tz setting to
// change because the geo suggestion is empty.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_ENABLED, true /* expectedResult */)
.verifyTimeZoneNotChanged()
.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
.verifyTimeZoneChangedAndReset(geolocationSuggestion.getZoneIds().get(0));
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
- script.simulateUpdateConfiguration(CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
+ script.simulateUpdateConfiguration(
+ USER_ID, CONFIG_GEO_DETECTION_DISABLED, true /* expectedResult */)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
assertNull(mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
@@ -898,7 +948,7 @@ public class TimeZoneDetectorStrategyImplTest {
private static TimeZoneConfiguration createConfig(
@Nullable Boolean autoDetection, @Nullable Boolean geoDetection) {
- TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder(USER_ID);
+ TimeZoneConfiguration.Builder builder = new TimeZoneConfiguration.Builder();
if (autoDetection != null) {
builder.setAutoDetectionEnabled(autoDetection);
}
@@ -957,9 +1007,10 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Override
- public void storeConfiguration(TimeZoneConfiguration newConfiguration) {
+ public void storeConfiguration(
+ @UserIdInt int userId, TimeZoneConfiguration newConfiguration) {
ConfigurationInternal oldConfiguration = mConfigurationInternal.getLatest();
- if (newConfiguration.getUserId() != oldConfiguration.getUserId()) {
+ if (userId != oldConfiguration.getUserId()) {
fail("FakeCallback does not support multiple users");
}
@@ -1014,9 +1065,9 @@ public class TimeZoneDetectorStrategyImplTest {
* the return value.
*/
Script simulateUpdateConfiguration(
- TimeZoneConfiguration configuration, boolean expectedResult) {
+ int userId, TimeZoneConfiguration configuration, boolean expectedResult) {
assertEquals(expectedResult,
- mTimeZoneDetectorStrategy.updateConfiguration(configuration));
+ mTimeZoneDetectorStrategy.updateConfiguration(userId, configuration));
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 7b07102356f0..1055069c746b 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -81,9 +81,7 @@ public class TunerResourceManagerServiceTest {
// A correspondence to compare a FrontendResource and a TunerFrontendInfo.
private static final Correspondence<FrontendResource, TunerFrontendInfo> FR_TFI_COMPARE =
- new Correspondence<FrontendResource, TunerFrontendInfo>() {
- @Override
- public boolean compare(FrontendResource actual, TunerFrontendInfo expected) {
+ Correspondence.from((FrontendResource actual, TunerFrontendInfo expected) -> {
if (actual == null || expected == null) {
return (actual == null) && (expected == null);
}
@@ -91,13 +89,7 @@ public class TunerResourceManagerServiceTest {
return actual.getId() == expected.getId()
&& actual.getType() == expected.getFrontendType()
&& actual.getExclusiveGroupId() == expected.getExclusiveGroupId();
- }
-
- @Override
- public String toString() {
- return "is correctly configured from ";
- }
- };
+ }, "is correctly configured from ");
@Before
public void setUp() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 2c645268e190..0c6d6388b458 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -84,6 +84,7 @@ import com.android.server.SystemService;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -703,6 +704,7 @@ public class AppStandbyControllerTests {
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
}
+ @Ignore
@Test
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index abcc14c6be93..9766cb546616 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -402,31 +402,33 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(),
+ any(AudioAttributes.class));
}
private void verifyVibrate() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
- anyString(), any());
+ anyString(), any(AudioAttributes.class));
}
private void verifyVibrate(int times) {
- verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), any());
+ verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(),
+ any(AudioAttributes.class));
}
private void verifyVibrateLooped() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
- anyString(), any());
+ anyString(), any(AudioAttributes.class));
}
private void verifyDelayedVibrateLooped() {
verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
- argThat(mVibrateLoopMatcher), anyString(), any());
+ argThat(mVibrateLoopMatcher), anyString(), any(AudioAttributes.class));
}
private void verifyDelayedVibrate() {
verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
- argThat(mVibrateOnceMatcher), anyString(), any());
+ argThat(mVibrateOnceMatcher), anyString(), any(AudioAttributes.class));
}
private void verifyStopVibrate() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index d7e431f3bb51..e304083dfd27 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -690,6 +690,39 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ public void unbindOtherUserServices() throws PackageManager.NameNotFoundException {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.registerService(cn, 0);
+ service.registerService(cn, 10);
+ service.registerService(cn, 11);
+ service.unbindOtherUserServices(11);
+
+ assertFalse(service.isBound(cn, 0));
+ assertFalse(service.isBound(cn, 10));
+ assertTrue(service.isBound(cn, 11));
+ }
+
+ @Test
public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index a2d987fb0a8d..f6d6624d7e1c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -44,15 +44,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.internal.matchers.Not;
import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
@@ -309,22 +307,22 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
public void testRemoveConversationRunnable() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveConversationRunnable rcr =
- mDataBase.new RemoveConversationRunnable("pkg", "convo");
+ mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo", "another"));
rcr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
- when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(true);
+ when(nh.removeConversationsFromWrite("pkg", Set.of("convo", "another"))).thenReturn(true);
mDataBase.mBuffer = mock(NotificationHistory.class);
rcr.run();
- verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
+ verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg",Set.of("convo", "another"));
verify(af).openRead();
- verify(nh).removeConversationFromWrite("pkg", "convo");
+ verify(nh).removeConversationsFromWrite("pkg",Set.of("convo", "another"));
verify(af).startWrite();
}
@@ -332,22 +330,22 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
public void testRemoveConversationRunnable_noChanges() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveConversationRunnable rcr =
- mDataBase.new RemoveConversationRunnable("pkg", "convo");
+ mDataBase.new RemoveConversationRunnable("pkg", Set.of("convo"));
rcr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
- when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(false);
+ when(nh.removeConversationsFromWrite("pkg", Set.of("convo"))).thenReturn(false);
mDataBase.mBuffer = mock(NotificationHistory.class);
rcr.run();
- verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
+ verify(mDataBase.mBuffer).removeConversationsFromWrite("pkg", Set.of("convo"));
verify(af).openRead();
- verify(nh).removeConversationFromWrite("pkg", "convo");
+ verify(nh).removeConversationsFromWrite("pkg", Set.of("convo"));
verify(af, never()).startWrite();
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 2341c10a9c91..a0293b7ad12a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -47,6 +47,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
@RunWith(AndroidJUnit4.class)
@@ -365,15 +366,15 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
@Test
public void testDeleteConversation_userUnlocked() {
String pkg = "pkg";
- String convo = "convo";
+ Set<String> convos = Set.of("convo", "another");
NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
mHistoryManager.onUserUnlocked(USER_SYSTEM);
mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
- mHistoryManager.deleteConversation(pkg, 1, convo);
+ mHistoryManager.deleteConversations(pkg, 1, convos);
- verify(userHistory, times(1)).deleteConversation(pkg, convo);
+ verify(userHistory, times(1)).deleteConversations(pkg, convos);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 9e7226e7cacf..6ee583164284 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -43,6 +43,9 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static android.app.PendingIntent.FLAG_MUTABLE;
+import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -69,10 +72,12 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -102,10 +107,13 @@ import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.ICompanionDeviceManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IIntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -182,6 +190,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer;
@@ -243,6 +252,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Resources mResources;
@Mock
RankingHandler mRankingHandler;
+ @Mock
+ ActivityManagerInternal mAmi;
+
+ @Mock
+ IIntentSender pi1;
private static final int MAX_POST_DELAY = 1000;
@@ -282,6 +296,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationHistoryManager mHistoryManager;
@Mock
StatsManager mStatsManager;
+ BroadcastReceiver mPackageIntentReceiver;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -386,7 +401,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class);
when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L);
- ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
@@ -397,7 +411,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
LocalServices.removeServiceForTest(DeviceIdleInternal.class);
LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal);
+ LocalServices.addService(ActivityManagerInternal.class, mAmi);
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
@@ -471,7 +485,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mGroupHelper, mAm, mAtm, mAppUsageStats,
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mUm, mHistoryManager, mStatsManager,
- mock(TelephonyManager.class));
+ mock(TelephonyManager.class), mAmi);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.setAudioManager(mAudioManager);
@@ -480,6 +494,28 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mShortcutHelper.setLauncherApps(mLauncherApps);
mShortcutHelper.setShortcutServiceInternal(mShortcutServiceInternal);
+ // Capture PackageIntentReceiver
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+
+ verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(),
+ any(), intentFilterCaptor.capture(), any(), any());
+ List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+ List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+ for (int i = 0; i < intentFilters.size(); i++) {
+ final IntentFilter filter = intentFilters.get(i);
+ if (filter.hasAction(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)
+ && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
+ && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
+ mPackageIntentReceiver = broadcastReceivers.get(i);
+ break;
+ }
+ }
+ assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
+
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
ShortcutInfo info = mock(ShortcutInfo.class);
@@ -526,11 +562,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
- mService.unregisterDeviceConfigChange();
+
+ try {
+ mService.onDestroy();
+ } catch (IllegalStateException e) {
+ // can throw if a broadcast receiver was never registered
+ }
+
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
}
+ private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
+ int uid) {
+ // mimics receive broadcast that package is (un)suspended
+ // but does not actually (un)suspend the package
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+ new String[]{pkg});
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+
+ final String action = suspend ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED;
+ final Intent intent = new Intent(action);
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
+ private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) {
+ // mimics receive broadcast that package is (un)distracting
+ // but does not actually register that info with packagemanager
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgs);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, flag);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+
+ final Intent intent = new Intent(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED);
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
changed.put(true, new ArrayList<>());
@@ -609,7 +682,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addAction(new Notification.Action.Builder(null, "test", null).build());
if (extender != null) {
nb.extend(extender);
}
@@ -745,6 +819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
PendingIntent pendingIntent = mock(PendingIntent.class);
Intent intent = mock(Intent.class);
when(pendingIntent.getIntent()).thenReturn(intent);
+ when(pendingIntent.getTarget()).thenReturn(pi1);
ActivityInfo info = new ActivityInfo();
info.resizeMode = RESIZE_MODE_RESIZEABLE;
@@ -2533,10 +2608,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testHasCompanionDevice_noService() {
- mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
+ NotificationManagerService noManService =
+ new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
mNotificationInstanceIdSequence);
- assertFalse(mService.hasCompanionDevice(mListener));
+ assertFalse(noManService.hasCompanionDevice(mListener));
}
@Test
@@ -4347,13 +4423,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(notif2);
// on broadcast, hide the 2 notifications
- mService.simulatePackageSuspendBroadcast(true, PKG);
+ simulatePackageSuspendBroadcast(true, PKG, notif1.getUid());
ArgumentCaptor<List> captorHide = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
assertEquals(2, captorHide.getValue().size());
// on broadcast, unhide the 2 notifications
- mService.simulatePackageSuspendBroadcast(false, PKG);
+ simulatePackageSuspendBroadcast(false, PKG, notif1.getUid());
ArgumentCaptor<List> captorUnhide = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
assertEquals(2, captorUnhide.getValue().size());
@@ -4370,7 +4446,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(notif2);
// on broadcast, nothing is hidden since no notifications are of package "test_package"
- mService.simulatePackageSuspendBroadcast(true, "test_package");
+ simulatePackageSuspendBroadcast(true, "test_package", notif1.getUid());
+ ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
+ verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
+ assertEquals(0, captor.getValue().size());
+ }
+
+ @Test
+ public void testNotificationFromDifferentUserHidden() {
+ // post 2 notification from this package
+ final NotificationRecord notif1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ final NotificationRecord notif2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, false);
+ mService.addNotification(notif1);
+ mService.addNotification(notif2);
+
+ // on broadcast, nothing is hidden since no notifications are of user 10 with package PKG
+ simulatePackageSuspendBroadcast(true, PKG, 10);
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
assertEquals(0, captor.getValue().size());
@@ -4387,16 +4480,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(pkgB);
// on broadcast, hide one of the packages
- mService.simulatePackageDistractionBroadcast(
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"});
+ simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a"},
+ new int[] {1000});
ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyHiddenLocked(captorHide.capture());
assertEquals(1, captorHide.getValue().size());
assertEquals("a", captorHide.getValue().get(0).getSbn().getPackageName());
// on broadcast, unhide the package
- mService.simulatePackageDistractionBroadcast(
- PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"});
+ simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a"},
+ new int[] {1000});
ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyUnhiddenLocked(captorUnhide.capture());
assertEquals(1, captorUnhide.getValue().size());
@@ -4414,8 +4509,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(pkgB);
// on broadcast, hide one of the packages
- mService.simulatePackageDistractionBroadcast(
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"});
+ simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"a", "b"},
+ new int[] {1000, 1001});
ArgumentCaptor<List<NotificationRecord>> captorHide = ArgumentCaptor.forClass(List.class);
// should be called only once.
@@ -4425,8 +4521,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals("b", captorHide.getValue().get(1).getSbn().getPackageName());
// on broadcast, unhide the package
- mService.simulatePackageDistractionBroadcast(
- PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"});
+ simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS, new String[] {"a", "b"},
+ new int[] {1000, 1001});
ArgumentCaptor<List<NotificationRecord>> captorUnhide = ArgumentCaptor.forClass(List.class);
// should be called only once.
@@ -4444,8 +4541,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(notif1);
// on broadcast, nothing is hidden since no notifications are of package "test_package"
- mService.simulatePackageDistractionBroadcast(
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"});
+ simulatePackageDistractionBroadcast(
+ PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, new String[] {"test_package"},
+ new int[]{notif1.getUid()});
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
verify(mListeners, times(1)).notifyHiddenLocked(captor.capture());
assertEquals(0, captor.getValue().size());
@@ -7011,4 +7109,194 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertTrue(mService.isVisibleToListener(sbn, info));
}
+ @Test
+ public void testUserInitiatedCancelAll_groupCancellationOrder_groupPostedFirst() {
+ final NotificationRecord parent = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true));
+ final NotificationRecord child = spy(generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false));
+ mService.addNotification(parent);
+ mService.addNotification(child);
+
+ InOrder inOrder = inOrder(parent, child);
+
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+ parent.getUserId());
+ waitForIdle();
+ inOrder.verify(parent).recordDismissalSentiment(anyInt());
+ inOrder.verify(child).recordDismissalSentiment(anyInt());
+ }
+
+ @Test
+ public void testUserInitiatedCancelAll_groupCancellationOrder_groupPostedSecond() {
+ final NotificationRecord parent = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true));
+ final NotificationRecord child = spy(generateNotificationRecord(
+ mTestNotificationChannel, 2, "group", false));
+ mService.addNotification(child);
+ mService.addNotification(parent);
+
+ InOrder inOrder = inOrder(parent, child);
+
+ mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+ parent.getUserId());
+ waitForIdle();
+ inOrder.verify(parent).recordDismissalSentiment(anyInt());
+ inOrder.verify(child).recordDismissalSentiment(anyInt());
+ }
+
+ @Test
+ public void testImmutableBubbleIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(pi1))
+ .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ NotificationRecord r = generateMessageBubbleNotifRecord(true,
+ mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+ try {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+ waitForIdle();
+ fail("Allowed a bubble with an immutable intent to be posted");
+ } catch (IllegalArgumentException e) {
+ // good
+ }
+ }
+
+ @Test
+ public void testMutableBubbleIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(pi1))
+ .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+ NotificationRecord r = generateMessageBubbleNotifRecord(true,
+ mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testImmutableDirectReplyActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ NotificationRecord r = generateMessageBubbleNotifRecord(false,
+ mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+ try {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+ waitForIdle();
+ fail("Allowed a direct reply with an immutable intent to be posted");
+ } catch (IllegalArgumentException e) {
+ // good
+ }
+ }
+
+ @Test
+ public void testMutableDirectReplyActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+ NotificationRecord r = generateMessageBubbleNotifRecord(false,
+ mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testImmutableDirectReplyContextualActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ ArrayList<Notification.Action> extraAction = new ArrayList<>();
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ extraAction.add(replyAction);
+ Bundle signals = new Bundle();
+ signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+ r.getUser());
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ try {
+ mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+ r.getSbn().getTag(), r,false);
+ fail("Allowed a contextual direct reply with an immutable intent to be posted");
+ } catch (IllegalArgumentException e) {
+ // good
+ }
+ }
+
+ @Test
+ public void testMutableDirectReplyContextualActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ ArrayList<Notification.Action> extraAction = new ArrayList<>();
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ extraAction.add(replyAction);
+ Bundle signals = new Bundle();
+ signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+ r.getUser());
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+ r.getSbn().getTag(), r,false);
+ }
+
+ @Test
+ public void testImmutableActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(),
+ r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
+
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(r.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+ }
+
+ @Test
+ public void testImmutableContextualActionIntent() throws Exception {
+ when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
+ .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ ArrayList<Notification.Action> extraAction = new ArrayList<>();
+ extraAction.add(new Notification.Action(0, "hello", null));
+ Bundle signals = new Bundle();
+ signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
+ Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
+ r.getUser());
+ r.addAdjustment(adjustment);
+ r.applyAdjustments();
+
+ mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(),
+ r.getSbn().getTag(), r,false);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0be1bf3fe5c2..3e779a9b2435 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -127,6 +127,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
@SmallTest
@@ -3502,8 +3503,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void testDeleteConversation() {
+ public void testDeleteConversations() {
String convoId = "convo";
+ String convoIdC = "convoC";
NotificationChannel messages =
new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
@@ -3526,10 +3528,16 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.setConversationId(calls.getId(), convoId);
mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+ NotificationChannel channel3 =
+ new NotificationChannel("C person msgs", "msgs from C", IMPORTANCE_DEFAULT);
+ channel3.setConversationId(messages.getId(), convoIdC);
+ mHelper.createNotificationChannel(PKG_O, UID_O, channel3, true, false);
+
assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), false));
assertEquals(channel2,
mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), false));
- assertEquals(2, mHelper.deleteConversation(PKG_O, UID_O, convoId).size());
+ List<String> deleted = mHelper.deleteConversations(PKG_O, UID_O, Set.of(convoId, convoIdC));
+ assertEquals(3, deleted.size());
assertEquals(messages,
mHelper.getNotificationChannel(PKG_O, UID_O, messages.getId(), false));
@@ -3542,7 +3550,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
assertEquals(channel2,
mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), true));
- assertEquals(7, mLogger.getCalls().size());
+ assertEquals(9, mLogger.getCalls().size());
assertEquals(
NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED,
mLogger.get(0).event); // Channel messages
@@ -3563,12 +3571,20 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mLogger.get(4).event); // Channel channel2 - Conversation A person calls
assertEquals(
NotificationChannelLogger.NotificationChannelEvent
+ .NOTIFICATION_CHANNEL_CONVERSATION_CREATED,
+ mLogger.get(5).event); // Channel channel3 - Conversation C person msgs
+ assertEquals(
+ NotificationChannelLogger.NotificationChannelEvent
+ .NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
+ mLogger.get(6).event); // Delete Channel channel - Conversation A person msgs
+ assertEquals(
+ NotificationChannelLogger.NotificationChannelEvent
.NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
- mLogger.get(5).event); // Delete Channel channel - Conversation A person msgs
+ mLogger.get(7).event); // Delete Channel channel2 - Conversation A person calls
assertEquals(
NotificationChannelLogger.NotificationChannelEvent
.NOTIFICATION_CHANNEL_CONVERSATION_DELETED,
- mLogger.get(6).event); // Delete Channel channel2 - Conversation A person calls
+ mLogger.get(8).event); // Delete Channel channel3 - Conversation C person msgs
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 3281c3f4cfb9..a80f62ab09ee 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IUriGrantsManager;
@@ -154,7 +155,8 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
mock(UriGrantsManagerInternal.class),
mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
- mock(StatsManager.class), mock(TelephonyManager.class));
+ mock(StatsManager.class), mock(TelephonyManager.class),
+ mock(ActivityManagerInternal.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index eca71b69ec0b..e5ae2d3f63ab 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -305,6 +305,7 @@ public class ShortcutHelperTest extends UiServiceTestCase {
//when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
// anyString(), anyInt(), any())).thenReturn(true);
- assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM)).isSameAs(si);
+ assertThat(mShortcutHelper.getValidShortcutInfo("a", "p", UserHandle.SYSTEM))
+ .isSameInstanceAs(si);
}
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index e3c795d03381..9099272a0fd6 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -39,6 +39,8 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.STATUS_BAR" />
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
+ <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+ <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
index cf07183a007d..f026b852f08c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java
@@ -300,15 +300,15 @@ public class ActivityDisplayTests extends WindowTestsBase {
}
@Test
- public void testRemoveStackInWindowingModes() {
- removeStackTests(() -> mRootWindowContainer.removeStacksInWindowingModes(
+ public void testRemoveRootTaskInWindowingModes() {
+ removeStackTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
WINDOWING_MODE_FULLSCREEN));
}
@Test
public void testRemoveStackWithActivityTypes() {
- removeStackTests(
- () -> mRootWindowContainer.removeStacksWithActivityTypes(ACTIVITY_TYPE_STANDARD));
+ removeStackTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes(
+ ACTIVITY_TYPE_STANDARD));
}
private void removeStackTests(Runnable runnable) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 89a0c7c4885e..dd85484605d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -550,7 +550,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
try {
doReturn(false).when(stack).isTranslucent(any());
- assertFalse(mStack.shouldBeVisible(null /* starting */));
+ assertTrue(mStack.shouldBeVisible(null /* starting */));
mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
mActivity.getConfiguration()));
@@ -1126,7 +1126,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Verify the stack-top activity is occluded keyguard.
assertEquals(topActivity, mStack.topRunningActivity());
- assertTrue(mStack.topActivityOccludesKeyguard());
+ assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
// Finish the top activity
topActivity.setState(PAUSED, "true");
@@ -1135,7 +1135,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Verify new top activity does not occlude keyguard.
assertEquals(mActivity, mStack.topRunningActivity());
- assertFalse(mStack.topActivityOccludesKeyguard());
+ assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
}
/**
@@ -1522,7 +1522,7 @@ public class ActivityRecordTests extends WindowTestsBase {
try {
// Return error to skip unnecessary operation.
doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
- any() /* window */, anyInt() /* seq */, any() /* attrs */,
+ any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */, any() /* outFrame */,
any() /* outContentInsets */, any() /* outStableInsets */,
any() /* outDisplayCutout */, any() /* outInputChannel */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index a60f93a3b14a..caf8a720e26c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -1142,7 +1142,8 @@ public class ActivityStackTests extends WindowTestsBase {
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(mTask).build();
topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
- mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity);
+ mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity,
+ "test");
verify(mStack).completePauseLocked(anyBoolean(), eq(topActivity));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index b8d0f2d1ecfb..25d003cbbf46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -63,6 +63,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.notNull;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -675,7 +676,7 @@ public class ActivityStarterTests extends WindowTestsBase {
doReturn(callerIsRecents).when(recentTasks).isCallerRecents(callingUid);
// caller is temp allowed
if (callerIsTempAllowed) {
- callerApp.addAllowBackgroundActivityStartsToken(new Binder(), null);
+ callerApp.addOrUpdateAllowBackgroundActivityStartsToken(new Binder(), null);
}
// caller is instrumenting with background activity starts privileges
callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges,
@@ -805,6 +806,7 @@ public class ActivityStarterTests extends WindowTestsBase {
final Task topTask = new TaskBuilder(mSupervisor).setParentTask(topStack).build();
new ActivityBuilder(mAtm).setTask(topTask).build();
+ doReturn(mActivityMetricsLogger).when(mSupervisor).getActivityMetricsLogger();
// Start activity with the same intent as {@code singleTaskActivity} on secondary display.
final ActivityOptions options = ActivityOptions.makeBasic()
.setLaunchDisplayId(secondaryDisplay.mDisplayId);
@@ -818,6 +820,9 @@ public class ActivityStarterTests extends WindowTestsBase {
// Ensure secondary display only creates two stacks.
verify(secondaryTaskContainer, times(2)).createStack(anyInt(), anyInt(), anyBoolean());
+ // The metrics logger should receive the same result and non-null options.
+ verify(mActivityMetricsLogger).notifyActivityLaunched(any() /* launchingState */,
+ eq(result), eq(singleTaskActivity), notNull() /* options */);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 031165727207..3349c6dda87f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -160,7 +160,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
};
mAtm.mWindowManager.registerDisplayWindowListener(listener);
// Check that existing displays call added
- assertEquals(1, added.size());
+ assertEquals(mRootWindowContainer.getChildCount(), added.size());
assertEquals(0, changed.size());
assertEquals(0, removed.size());
added.clear();
@@ -233,7 +233,11 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
.setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
.build();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.setState(Task.ActivityState.RESUMED, "test");
mSupervisor.endDeferResume();
+
+ assertEquals(activity.app, mAtm.mInternal.getTopApp());
+
// Assume the activity is finishing and hidden because it was crashed.
activity.finishing = true;
activity.mVisibleRequested = false;
@@ -246,6 +250,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
null /* finishInstrumentationCallback */);
assertEquals(Task.ActivityState.RESUMED, homeActivity.getState());
+ assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
new file mode 100644
index 000000000000..9a668b91c656
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link DisplayAreaGroup} container.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayAreaGroupTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DisplayAreaGroupTest extends WindowTestsBase {
+
+ private DisplayAreaGroup mDisplayAreaGroup;
+ private TaskDisplayArea mTaskDisplayArea;
+ private Task mStack;
+ private ActivityRecord mActivity;
+
+ @Before
+ public void setUp() {
+ mDisplayAreaGroup = new DisplayAreaGroup(
+ mWm, "DisplayAreaGroup", FEATURE_VENDOR_FIRST);
+ final TaskDisplayArea defaultTda = mDisplayContent.getDefaultTaskDisplayArea();
+ final WindowContainer parentDA = defaultTda.getParent();
+ parentDA.addChild(mDisplayAreaGroup, parentDA.mChildren.indexOf(defaultTda) + 1);
+ mTaskDisplayArea = new TaskDisplayArea(
+ mDisplayContent, mWm, "TDA1", FEATURE_VENDOR_FIRST + 1);
+ mDisplayAreaGroup.addChild(mTaskDisplayArea, POSITION_TOP);
+ mStack = mTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ mActivity = new ActivityBuilder(mAtm).setCreateTask(true).setStack(mStack).build();
+ mDisplayContent.setLastFocusedTaskDisplayArea(mTaskDisplayArea);
+ }
+
+ @Test
+ public void testIsOrientationDifferentFromDisplay() {
+ // Display is portrait, DisplayAreaGroup inherits that
+ mDisplayContent.setBounds(0, 0, 600, 900);
+
+ assertThat(mDisplayAreaGroup.isOrientationDifferentFromDisplay()).isFalse();
+
+ // DisplayAreaGroup is landscape, different Display
+ mDisplayAreaGroup.setBounds(0, 0, 600, 450);
+
+ assertThat(mDisplayAreaGroup.isOrientationDifferentFromDisplay()).isTrue();
+
+ // DisplayAreaGroup is portrait, same as Display
+ mDisplayAreaGroup.setBounds(0, 0, 300, 900);
+
+ assertThat(mDisplayAreaGroup.isOrientationDifferentFromDisplay()).isFalse();
+ }
+
+ @Test
+ public void testGetOrientation() {
+ doReturn(true).when(mDisplayContent).onDescendantOrientationChanged(any(), any());
+ mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ // Display is portrait, DisplayAreaGroup inherits that
+ mDisplayContent.setBounds(0, 0, 600, 900);
+
+ assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
+ assertThat(mActivity.getRequestedConfigurationOrientation())
+ .isEqualTo(ORIENTATION_PORTRAIT);
+
+ // DisplayAreaGroup is landscape, different from Display
+ mDisplayAreaGroup.setBounds(0, 0, 600, 450);
+
+ assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+ assertThat(mActivity.getRequestedConfigurationOrientation())
+ .isEqualTo(ORIENTATION_LANDSCAPE);
+
+ // DisplayAreaGroup is portrait, same as Display
+ mDisplayAreaGroup.setBounds(0, 0, 300, 900);
+
+ assertThat(mDisplayAreaGroup.getOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
+ assertThat(mActivity.getRequestedConfigurationOrientation())
+ .isEqualTo(ORIENTATION_PORTRAIT);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index f4b50dc6b553..54b2b3b4a009 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -18,6 +18,8 @@ package com.android.server.wm;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -39,6 +41,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+/**
+ * Build/Install/Run:
+ * atest WmTests:DisplayAreaOrganizerTest
+ */
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
@@ -61,14 +67,22 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase {
}
private IDisplayAreaOrganizer registerMockOrganizer(int feature) {
- final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
- when(organizer.asBinder()).thenReturn(new Binder());
+ return registerMockOrganizer(feature, new Binder());
+ }
+ private IDisplayAreaOrganizer registerMockOrganizer(int feature, Binder binder) {
+ final IDisplayAreaOrganizer organizer = createMockOrganizer(binder);
mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
.registerOrganizer(organizer, feature);
return organizer;
}
+ private IDisplayAreaOrganizer createMockOrganizer(Binder binder) {
+ final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
+ when(organizer.asBinder()).thenReturn(binder);
+ return organizer;
+ }
+
private void unregisterMockOrganizer(IDisplayAreaOrganizer organizer) {
mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
.unregisterOrganizer(organizer);
@@ -99,4 +113,16 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase {
// Ensure it was still only called once if the bounds didn't change
verify(organizer).onDisplayAreaInfoChanged(any());
}
+
+ @Test
+ public void testUnregisterOrganizer() {
+ final Binder binder = new Binder();
+ registerMockOrganizer(FEATURE_VENDOR_FIRST, binder);
+
+ assertThat(mTestDisplayArea.mOrganizer).isNotNull();
+
+ unregisterMockOrganizer(createMockOrganizer(binder));
+
+ assertThat(mTestDisplayArea.mOrganizer).isNull();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index e17601e99010..01c1f1f73ee9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -23,6 +23,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
import static com.android.server.wm.DisplayArea.Type.ANY;
@@ -37,14 +39,18 @@ import static com.android.server.wm.testing.Assert.assertThrows;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
import com.google.android.collect.Lists;
@@ -405,6 +411,55 @@ public class DisplayAreaTest {
childBounds1, windowToken.getMaxBounds());
}
+ @Test
+ public void testGetOrientation() {
+ final DisplayArea.Tokens area = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "test");
+ final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
+ spyOn(token);
+ doReturn(mock(DisplayContent.class)).when(token).getDisplayContent();
+ doNothing().when(token).setParent(any());
+ final WindowState win = createWindowState(token);
+ spyOn(win);
+ doNothing().when(win).setParent(any());
+ win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ token.addChild(win, 0);
+ area.addChild(token);
+
+ doReturn(true).when(win).isVisible();
+
+ assertEquals("Visible window can request orientation",
+ ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
+ area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+
+ doReturn(false).when(win).isVisible();
+
+ assertEquals("Invisible window cannot request orientation",
+ ActivityInfo.SCREEN_ORIENTATION_NOSENSOR,
+ area.getOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR));
+ }
+
+ @Test
+ public void testSetIgnoreOrientationRequest() {
+ final DisplayArea.Tokens area = new DisplayArea.Tokens(mWms, ABOVE_TASKS, "test");
+ final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
+ spyOn(token);
+ doReturn(mock(DisplayContent.class)).when(token).getDisplayContent();
+ doNothing().when(token).setParent(any());
+ final WindowState win = createWindowState(token);
+ spyOn(win);
+ doNothing().when(win).setParent(any());
+ win.mAttrs.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ token.addChild(win, 0);
+ area.addChild(token);
+ doReturn(true).when(win).isVisible();
+
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, area.getOrientation());
+
+ area.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ assertEquals(ActivityInfo.SCREEN_ORIENTATION_UNSET, area.getOrientation());
+ }
+
private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
private TestDisplayArea(WindowManagerService wms, Rect bounds) {
super(wms, ANY, "half display area");
@@ -417,6 +472,13 @@ public class DisplayAreaTest {
}
}
+ private WindowState createWindowState(WindowToken token) {
+ return new WindowState(mWms, mock(Session.class), new TestIWindow(), token,
+ null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
+ View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
+ false /* ownerCanAddInternalSystemWindow */);
+ }
+
private WindowToken createWindowToken(int type) {
return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
type, false /* persist */, null /* displayContent */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 89a34cfc77f3..4d0d3b27ee99 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -526,7 +526,8 @@ public class DisplayContentTests extends WindowTestsBase {
for (int i = 0; i < types.length; i++) {
final int type = types[i];
windows[i] = createWindow(null /* parent */, type, displayContent, "window-" + type);
- windows[i].mHasSurface = false;
+ windows[i].setHasSurface(true);
+ windows[i].mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
}
return windows;
}
@@ -1163,6 +1164,8 @@ public class DisplayContentTests extends WindowTestsBase {
mDisplayContent.getDisplayRotation().setRotation(ROTATION_0);
mDisplayContent.computeScreenConfiguration(config);
mDisplayContent.onRequestedOverrideConfigurationChanged(config);
+ assertNotEquals(config90.windowConfiguration.getMaxBounds(),
+ config.windowConfiguration.getMaxBounds());
final ActivityRecord app = mAppWindow.mActivityRecord;
app.setVisible(false);
@@ -1218,8 +1221,9 @@ public class DisplayContentTests extends WindowTestsBase {
verify(t, never()).setPosition(any(), eq(0), eq(0));
// Launch another activity before the transition is finished.
- final ActivityRecord app2 = new TaskBuilder(mSupervisor)
- .setDisplay(mDisplayContent).setCreateActivity(true).build().getTopMostActivity();
+ final Task task2 = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
+ final ActivityRecord app2 = new ActivityBuilder(mAtm).setStack(task2)
+ .setUseProcess(app.app).build();
app2.setVisible(false);
mDisplayContent.mOpeningApps.add(app2);
app2.setRequestedOrientation(newOrientation);
@@ -1229,6 +1233,12 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(app.hasFixedRotationTransform(app2));
assertTrue(mDisplayContent.isFixedRotationLaunchingApp(app2));
+ final Configuration expectedProcConfig = new Configuration(app2.app.getConfiguration());
+ expectedProcConfig.windowConfiguration.setActivityType(
+ WindowConfiguration.ACTIVITY_TYPE_UNDEFINED);
+ assertEquals("The process should receive rotated configuration for compatibility",
+ expectedProcConfig, app2.app.getConfiguration());
+
// The fixed rotation transform can only be finished when all animation finished.
doReturn(false).when(app2).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token);
@@ -1340,6 +1350,27 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
+ public void testNoFixedRotationOnResumedScheduledApp() {
+ unblockDisplayRotation(mDisplayContent);
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ app.setVisible(false);
+ app.setState(Task.ActivityState.RESUMED, "test");
+ mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_ACTIVITY_OPEN,
+ false /* alwaysKeepCurrent */);
+ mDisplayContent.mOpeningApps.add(app);
+ final int newOrientation = getRotatedOrientation(mDisplayContent);
+ app.setRequestedOrientation(newOrientation);
+
+ // The condition should reject using fixed rotation because the resumed client in real case
+ // might get display info immediately. And the fixed rotation adjustments haven't arrived
+ // client side so the info may be inconsistent with the requested orientation.
+ verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
+ eq(true) /* checkOpening */);
+ assertFalse(app.isFixedRotationTransforming());
+ assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
+ }
+
+ @Test
public void testRecentsNotRotatingWithFixedRotation() {
unblockDisplayRotation(mDisplayContent);
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
@@ -1553,6 +1584,28 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(publicDc.forceDesktopMode());
}
+ @Test
+ public void testDisplaySettingsReappliedWhenDisplayChanged() {
+ final DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.copyFrom(mDisplayInfo);
+ final DisplayContent dc = createNewDisplay(displayInfo);
+
+ // Generate width/height/density values different from the default of the display.
+ final int forcedWidth = dc.mBaseDisplayWidth + 1;
+ final int forcedHeight = dc.mBaseDisplayHeight + 1;;
+ final int forcedDensity = dc.mBaseDisplayDensity + 1;;
+ // Update the forced size and density in settings and the unique id to simualate a display
+ // remap.
+ dc.mWmService.mDisplayWindowSettings.setForcedSize(dc, forcedWidth, forcedHeight);
+ dc.mWmService.mDisplayWindowSettings.setForcedDensity(dc, forcedDensity, 0 /* userId */);
+ dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test";
+ // Trigger display changed.
+ dc.onDisplayChanged();
+ // Ensure overridden size and denisty match the most up-to-date values in settings for the
+ // display.
+ verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
+ }
+
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 951118125f6e..2920c1da3f24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -20,6 +20,8 @@ import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.LEFT;
import static android.view.Gravity.RIGHT;
import static android.view.Gravity.TOP;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_GESTURES;
@@ -218,10 +220,15 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
@Test
public void addingWindow_throwsException_WithMultipleInsetTypes() {
- WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
- win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+ WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+ win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+
+ expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
+
+ WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+ win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
- expectThrows(IllegalArgumentException.class, () -> addWindow(win));
+ expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateDenylistTest.java
index f53894ad9ec5..c3e1922a09cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateDenylistTest.java
@@ -40,107 +40,107 @@ import java.util.concurrent.Executor;
/**
* Build/Install/Run:
- * atest WmTests:HighRefreshRateBlacklistTest
+ * atest WmTests:HighRefreshRateDenylistTest
*/
@SmallTest
@Presubmit
-public class HighRefreshRateBlacklistTest {
+public class HighRefreshRateDenylistTest {
private static final String APP1 = "com.android.sample1";
private static final String APP2 = "com.android.sample2";
private static final String APP3 = "com.android.sample3";
- private HighRefreshRateBlacklist mBlacklist;
+ private HighRefreshRateDenylist mDenylist;
@After
public void tearDown() {
- mBlacklist.dispose();
+ mDenylist.dispose();
}
@Test
- public void testDefaultBlacklist() {
+ public void testDefaultDenylist() {
final Resources r = createResources(APP1, APP2);
- mBlacklist = new HighRefreshRateBlacklist(r, new FakeDeviceConfig());
+ mDenylist = new HighRefreshRateDenylist(r, new FakeDeviceConfig());
- assertTrue(mBlacklist.isBlacklisted(APP1));
- assertTrue(mBlacklist.isBlacklisted(APP2));
- assertFalse(mBlacklist.isBlacklisted(APP3));
+ assertTrue(mDenylist.isDenylisted(APP1));
+ assertTrue(mDenylist.isDenylisted(APP2));
+ assertFalse(mDenylist.isDenylisted(APP3));
}
@Test
- public void testNoDefaultBlacklist() {
+ public void testNoDefaultDenylist() {
final Resources r = createResources();
- mBlacklist = new HighRefreshRateBlacklist(r, new FakeDeviceConfig());
+ mDenylist = new HighRefreshRateDenylist(r, new FakeDeviceConfig());
- assertFalse(mBlacklist.isBlacklisted(APP1));
+ assertFalse(mDenylist.isDenylisted(APP1));
}
@Test
- public void testDefaultBlacklistIsOverriddenByDeviceConfig() {
+ public void testDefaultDenylistIsOverriddenByDeviceConfig() {
final Resources r = createResources(APP1);
final FakeDeviceConfig config = new FakeDeviceConfig();
- config.setBlacklist(APP2 + "," + APP3);
- mBlacklist = new HighRefreshRateBlacklist(r, config);
+ config.setDenylist(APP2 + "," + APP3);
+ mDenylist = new HighRefreshRateDenylist(r, config);
- assertFalse(mBlacklist.isBlacklisted(APP1));
- assertTrue(mBlacklist.isBlacklisted(APP2));
- assertTrue(mBlacklist.isBlacklisted(APP3));
+ assertFalse(mDenylist.isDenylisted(APP1));
+ assertTrue(mDenylist.isDenylisted(APP2));
+ assertTrue(mDenylist.isDenylisted(APP3));
}
@Test
- public void testDefaultBlacklistIsOverriddenByEmptyDeviceConfig() {
+ public void testDefaultDenylistIsOverriddenByEmptyDeviceConfig() {
final Resources r = createResources(APP1);
final FakeDeviceConfig config = new FakeDeviceConfig();
- config.setBlacklist("");
- mBlacklist = new HighRefreshRateBlacklist(r, config);
+ config.setDenylist("");
+ mDenylist = new HighRefreshRateDenylist(r, config);
- assertFalse(mBlacklist.isBlacklisted(APP1));
+ assertFalse(mDenylist.isDenylisted(APP1));
}
@Test
- public void testDefaultBlacklistIsOverriddenByDeviceConfigUpdate() {
+ public void testDefaultDenylistIsOverriddenByDeviceConfigUpdate() {
final Resources r = createResources(APP1);
final FakeDeviceConfig config = new FakeDeviceConfig();
- mBlacklist = new HighRefreshRateBlacklist(r, config);
+ mDenylist = new HighRefreshRateDenylist(r, config);
// First check that the default denylist is in effect
- assertTrue(mBlacklist.isBlacklisted(APP1));
- assertFalse(mBlacklist.isBlacklisted(APP2));
- assertFalse(mBlacklist.isBlacklisted(APP3));
+ assertTrue(mDenylist.isDenylisted(APP1));
+ assertFalse(mDenylist.isDenylisted(APP2));
+ assertFalse(mDenylist.isDenylisted(APP3));
// Then confirm that the DeviceConfig list has propagated and taken effect.
- config.setBlacklist(APP2 + "," + APP3);
- assertFalse(mBlacklist.isBlacklisted(APP1));
- assertTrue(mBlacklist.isBlacklisted(APP2));
- assertTrue(mBlacklist.isBlacklisted(APP3));
+ config.setDenylist(APP2 + "," + APP3);
+ assertFalse(mDenylist.isDenylisted(APP1));
+ assertTrue(mDenylist.isDenylisted(APP2));
+ assertTrue(mDenylist.isDenylisted(APP3));
// Finally make sure we go back to the default list if the DeviceConfig gets deleted.
- config.setBlacklist(null);
- assertTrue(mBlacklist.isBlacklisted(APP1));
- assertFalse(mBlacklist.isBlacklisted(APP2));
- assertFalse(mBlacklist.isBlacklisted(APP3));
+ config.setDenylist(null);
+ assertTrue(mDenylist.isDenylisted(APP1));
+ assertFalse(mDenylist.isDenylisted(APP2));
+ assertFalse(mDenylist.isDenylisted(APP3));
}
@Test
public void testOverriddenByDeviceConfigUnrelatedFlagChanged() {
final Resources r = createResources(APP1);
final FakeDeviceConfig config = new FakeDeviceConfig();
- mBlacklist = new HighRefreshRateBlacklist(r, config);
- config.setBlacklist(APP2 + "," + APP3);
- assertFalse(mBlacklist.isBlacklisted(APP1));
- assertTrue(mBlacklist.isBlacklisted(APP2));
- assertTrue(mBlacklist.isBlacklisted(APP3));
+ mDenylist = new HighRefreshRateDenylist(r, config);
+ config.setDenylist(APP2 + "," + APP3);
+ assertFalse(mDenylist.isDenylisted(APP1));
+ assertTrue(mDenylist.isDenylisted(APP2));
+ assertTrue(mDenylist.isDenylisted(APP3));
// Change an unrelated flag in our namespace and verify that the denylist is intact
config.putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, "someKey", "someValue");
- assertFalse(mBlacklist.isBlacklisted(APP1));
- assertTrue(mBlacklist.isBlacklisted(APP2));
- assertTrue(mBlacklist.isBlacklisted(APP3));
+ assertFalse(mDenylist.isDenylisted(APP1));
+ assertTrue(mDenylist.isDenylisted(APP2));
+ assertTrue(mDenylist.isDenylisted(APP3));
}
- private Resources createResources(String... defaultBlacklist) {
+ private Resources createResources(String... defaultDenylist) {
Resources r = mock(Resources.class);
when(r.getStringArray(R.array.config_highRefreshRateBlacklist))
- .thenReturn(defaultBlacklist);
+ .thenReturn(defaultDenylist);
return r;
}
@@ -160,9 +160,9 @@ public class HighRefreshRateBlacklistTest {
super.addOnPropertiesChangedListener(namespace, executor, listener);
}
- void setBlacklist(String blacklist) {
+ void setDenylist(String denylist) {
putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- KEY_HIGH_REFRESH_RATE_BLACKLIST, blacklist);
+ KEY_HIGH_REFRESH_RATE_BLACKLIST, denylist);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index ca739c0dd389..91cfd4e6a89d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -56,4 +56,12 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
mImeProvider.scheduleShowImePostLayout(appWin);
assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame());
}
+
+ @Test
+ public void testInputMethodInputTargetCanShowIme() {
+ WindowState target = createWindow(null, TYPE_APPLICATION, "app");
+ mDisplayContent.mInputMethodTarget = target;
+ mImeProvider.scheduleShowImePostLayout(target);
+ assertTrue(mImeProvider.isImeTargetFromDisplayContentAndImeSame());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
new file mode 100644
index 000000000000..0a960bef015e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.WindowManager;
+
+import com.android.server.inputmethod.InputMethodManagerService;
+import com.android.server.inputmethod.InputMethodMenuController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
+// scenario there.
+/**
+ * Build/Install/Run:
+ * atest WmTests:InputMethodMenuControllerTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class InputMethodMenuControllerTest extends WindowTestsBase {
+
+ private InputMethodMenuController mController;
+
+ @Before
+ public void setUp() {
+ mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
+ }
+
+ @Test
+ public void testGetSettingsContext() {
+ final Context contextOnDefaultDisplay = mController.getSettingsContext(DEFAULT_DISPLAY);
+
+ assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);
+
+ // Obtain the context again and check they are the same instance and match the display
+ // metrics of the secondary display.
+ final Context contextOnSecondaryDisplay = mController.getSettingsContext(
+ mDisplayContent.getDisplayId());
+
+ assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mDisplayContent);
+ assertThat(contextOnDefaultDisplay.getActivityToken())
+ .isEqualTo(contextOnSecondaryDisplay.getActivityToken());
+ }
+
+ private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
+ assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
+
+ final Rect contextBounds = context.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics().getBounds();
+ final Rect imeContainerBounds = dc.getImeContainer().getBounds();
+ assertThat(contextBounds).isEqualTo(imeContainerBounds);
+ }
+}
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 821480daa930..8c02faf7ac09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -37,16 +37,21 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import android.app.StatusBarManager;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import androidx.test.filters.SmallTest;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -205,6 +210,23 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertNotNull(fullscreenAppControls);
assertEquals(1, fullscreenAppControls.length);
assertEquals(ITYPE_STATUS_BAR, fullscreenAppControls[0].getType());
+
+ // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
+ final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
+ final InsetsState newRequestedState = new InsetsState();
+ newRequestedState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ newFocusedFullscreenApp.updateRequestedInsetsState(newRequestedState);
+ // Make sure status bar is hidden by previous insets state.
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp);
+
+ final StatusBarManagerInternal sbmi =
+ mDisplayContent.getDisplayPolicy().getStatusBarManagerInternal();
+ clearInvocations(sbmi);
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(newFocusedFullscreenApp);
+ // The status bar should be shown by newFocusedFullscreenApp even
+ // mTopFullscreenOpaqueWindowState is still fullscreenApp.
+ verify(sbmi).setWindowState(mDisplayContent.mDisplayId, StatusBarManager.WINDOW_STATUS_BAR,
+ StatusBarManager.WINDOW_STATE_SHOWING);
}
@UseTestDisplay(addWindows = W_ACTIVITY)
@@ -295,6 +317,15 @@ public class InsetsPolicyTest extends WindowTestsBase {
final InsetsState state = policy.getInsetsForDispatch(mAppWindow);
state.setSourceVisible(ITYPE_STATUS_BAR, true);
state.setSourceVisible(ITYPE_NAVIGATION_BAR, true);
+
+ final InsetsState clientState = mAppWindow.getInsetsState();
+ // The transient bar states for client should be invisible.
+ assertFalse(clientState.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertFalse(clientState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ // The original state shouldn't be modified.
+ assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
+ assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+
policy.onInsetsModified(mAppWindow, state);
waitUntilWindowAnimatorIdle();
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 4a4974355ea2..e2cd8a909266 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,6 +20,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -284,12 +286,17 @@ public class InsetsStateControllerTest extends WindowTestsBase {
public void testBarControllingWinChanged() {
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
+ final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar");
+ final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar");
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+ getController().getSourceProvider(ITYPE_CLIMATE_BAR).setWindow(climateBar, null, null);
+ getController().getSourceProvider(ITYPE_EXTRA_NAVIGATION_BAR).setWindow(extraNavBar, null,
+ null);
getController().onBarControlTargetChanged(app, null, app, null);
InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
- assertEquals(2, controls.length);
+ assertEquals(4, controls.length);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
index 820eca4a49a8..bc7516f6514f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -319,6 +320,29 @@ public class LaunchParamsControllerTests extends WindowTestsBase {
}
/**
+ * Ensures that {@link LaunchParamsModifier} doesn't alter non-root tasks' windowingMode.
+ */
+ @Test
+ public void testLayoutNonRootTaskWindowingModeChange() {
+ final LaunchParams params = new LaunchParams();
+ final int windowingMode = WINDOWING_MODE_FREEFORM;
+ params.mWindowingMode = windowingMode;
+ final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params);
+ final Task task = new TaskBuilder(mAtm.mStackSupervisor).setCreateParentTask(true).build();
+ task.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+
+ mController.registerModifier(positioner);
+
+ final int beforeWindowMode = task.getWindowingMode();
+ assertNotEquals(windowingMode, beforeWindowMode);
+
+ mController.layoutTask(task, null /* windowLayout */);
+
+ final int afterWindowMode = task.getWindowingMode();
+ assertEquals(afterWindowMode, beforeWindowMode);
+ }
+
+ /**
* Ensures that {@link LaunchParamsModifier} requests specifying bounds during
* layout are honored if window is in freeform.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 58d994c6cae3..1bf83aced590 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -44,7 +44,6 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -292,8 +291,7 @@ public class RecentTasksTest extends WindowTestsBase {
mRecentTasks.add(mTasks.get(1));
invocation.callRealMethod();
return null;
- }).when(mSupervisor).endActivityVisibilityUpdate(any(), anyInt(), anyBoolean(),
- anyBoolean());
+ }).when(mSupervisor).endActivityVisibilityUpdate();
mTaskContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
false /* preserveWindows */, false /* notifyClients */);
@@ -1156,22 +1154,22 @@ public class RecentTasksTest extends WindowTestsBase {
}
private void doTestRecentTasksApis(boolean expectCallable) {
- assertSecurityException(expectCallable, () -> mAtm.removeStack(INVALID_STACK_ID));
+ assertSecurityException(expectCallable, () -> mAtm.removeTask(INVALID_STACK_ID));
assertSecurityException(expectCallable,
- () -> mAtm.removeStacksInWindowingModes(
+ () -> mAtm.removeRootTasksInWindowingModes(
new int[]{WINDOWING_MODE_UNDEFINED}));
assertSecurityException(expectCallable,
- () -> mAtm.removeStacksWithActivityTypes(
+ () -> mAtm.removeRootTasksWithActivityTypes(
new int[]{ACTIVITY_TYPE_UNDEFINED}));
assertSecurityException(expectCallable, () -> mAtm.removeTask(0));
assertSecurityException(expectCallable,
() -> mAtm.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
assertSecurityException(expectCallable,
- () -> mAtm.moveTaskToStack(0, INVALID_STACK_ID, true));
+ () -> mAtm.moveTaskToRootTask(0, INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
() -> mAtm.setTaskWindowingModeSplitScreenPrimary(0, true));
assertSecurityException(expectCallable,
- () -> mAtm.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+ () -> mAtm.moveTopActivityToPinnedRootTask(INVALID_STACK_ID, new Rect()));
assertSecurityException(expectCallable, () -> mAtm.getAllRootTaskInfos());
assertSecurityException(expectCallable,
() -> mAtm.getRootTaskInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
@@ -1190,7 +1188,7 @@ public class RecentTasksTest extends WindowTestsBase {
() -> mAtm.unregisterTaskStackListener(null));
assertSecurityException(expectCallable, () -> mAtm.getTaskDescription(0));
assertSecurityException(expectCallable, () -> mAtm.cancelTaskWindowTransition(0));
- assertSecurityException(expectCallable, () -> mAtm.startRecentsActivity(null, null,
+ assertSecurityException(expectCallable, () -> mAtm.startRecentsActivity(null, 0,
null));
assertSecurityException(expectCallable, () -> mAtm.cancelRecentsAnimation(true));
assertSecurityException(expectCallable, () -> mAtm.stopAppSwitches());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index d821d38ea297..c10d4fa7f189 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -147,7 +147,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
// Null animation indicates to preload.
- mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+ mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
null /* recentsAnimationRunner */);
Task recentsStack = defaultTaskDisplayArea.getStack(WINDOWING_MODE_FULLSCREEN,
@@ -167,7 +167,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
spyOn(recentsActivity);
// Start when the recents activity exists. It should ensure the configuration.
- mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+ mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
null /* recentsAnimationRunner */);
verify(recentsActivity).ensureActivityConfiguration(anyInt() /* globalChanges */,
@@ -381,7 +381,7 @@ public class RecentsAnimationTest extends WindowTestsBase {
Intent recentsIntent = new Intent();
recentsIntent.setComponent(recentsComponent);
- mAtm.startRecentsActivity(recentsIntent, null /* assistDataReceiver */,
+ mAtm.startRecentsActivity(recentsIntent, 0 /* eventTime */,
mock(IRecentsAnimationRunner.class));
return recentsAnimation[0];
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index e887be0c48c2..77a4b0507a42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -48,7 +48,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
private static final int LOW_MODE_ID = 3;
private RefreshRatePolicy mPolicy;
- private HighRefreshRateBlacklist mBlacklist = mock(HighRefreshRateBlacklist.class);
+ private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
@Before
public void setUp() {
@@ -61,7 +61,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
};
di.defaultModeId = 1;
- mPolicy = new RefreshRatePolicy(mWm, di, mBlacklist);
+ mPolicy = new RefreshRatePolicy(mWm, di, mDenylist);
}
@Test
@@ -81,7 +81,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
final WindowState blacklistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
"blacklistedWindow");
blacklistedWindow.mAttrs.packageName = "com.android.test";
- when(mBlacklist.isBlacklisted("com.android.test")).thenReturn(true);
+ when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(blacklistedWindow));
}
@@ -90,7 +90,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
final WindowState overrideWindow = createWindow(null, TYPE_BASE_APPLICATION,
"overrideWindow");
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
- when(mBlacklist.isBlacklisted("com.android.test")).thenReturn(true);
+ when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 13f04d23ccd3..2efd4b53efcc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -117,7 +117,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
assertEquals(win.mActivityRecord.getPrefixOrderIndex(), app.prefixOrderIndex);
assertEquals(win.mActivityRecord.getTask().mTaskId, app.taskId);
assertEquals(mMockLeash, app.leash);
- assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
assertEquals(false, app.isTranslucent);
verify(mMockTransaction).setPosition(mMockLeash, app.position.x, app.position.y);
verify(mMockTransaction).setWindowCrop(mMockLeash, 100, 50);
@@ -274,7 +273,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
assertEquals(new Rect(0, 0, 200, 200), app.startBounds);
assertEquals(mMockLeash, app.leash);
assertEquals(mMockThumbnailLeash, app.startLeash);
- assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
assertEquals(false, app.isTranslucent);
verify(mMockTransaction).setPosition(
mMockLeash, app.startBounds.left, app.startBounds.top);
@@ -325,7 +323,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
assertEquals(new Rect(50, 100, 150, 150), app.startBounds);
assertEquals(mMockLeash, app.leash);
assertEquals(mMockThumbnailLeash, app.startLeash);
- assertEquals(win.mWinAnimator.mLastClipRect, app.clipRect);
assertEquals(false, app.isTranslucent);
verify(mMockTransaction).setPosition(
mMockLeash, app.startBounds.left, app.startBounds.top);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 3053fe6ec55f..cc8b2a1bb392 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -173,6 +173,35 @@ public class RootWindowContainerTests extends WindowTestsBase {
}
@Test
+ public void testTaskLayerRank() {
+ final Task rootTask = new TaskBuilder(mSupervisor).build();
+ final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ new ActivityBuilder(mAtm).setStack(task1).build().mVisibleRequested = true;
+ // RootWindowContainer#invalidateTaskLayers should post to update.
+ waitHandlerIdle(mWm.mH);
+
+ assertEquals(1, task1.mLayerRank);
+ // Only tasks that directly contain activities have a ranking.
+ assertEquals(Task.LAYER_RANK_INVISIBLE, rootTask.mLayerRank);
+
+ final Task task2 = new TaskBuilder(mSupervisor).build();
+ new ActivityBuilder(mAtm).setStack(task2).build().mVisibleRequested = true;
+ waitHandlerIdle(mWm.mH);
+
+ // Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
+ // activities have the visible rank.
+ assertEquals(2, task1.mLayerRank);
+ // The task2 is the top task, so it has a lower rank as a higher priority oom score.
+ assertEquals(1, task2.mLayerRank);
+
+ task2.moveToBack("test", null /* task */);
+ waitHandlerIdle(mWm.mH);
+
+ assertEquals(1, task1.mLayerRank);
+ assertEquals(2, task2.mLayerRank);
+ }
+
+ @Test
public void testForceStopPackage() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
index ef74861e9422..25ba6db38e05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -68,6 +68,7 @@ import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
@@ -142,6 +143,9 @@ public class ScreenDecorWindowTests {
assertInsetGreaterOrEqual(mTestActivity, RIGHT, mDecorThickness);
}
+ // Decor windows (i.e windows using PRIVATE_FLAG_IS_SCREEN_DECOR) are no longer supported.
+ // PRIVATE_FLAG_IS_SCREEN_DECOR and related code will be deprecated/removed soon.
+ @Ignore
@Test
public void testMultipleDecors() {
// Test 2 decor windows on-top.
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index d7eedd990f04..ecbfac8b091b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -147,13 +147,6 @@ public class StubTransaction extends SurfaceControl.Transaction {
}
@Override
- public SurfaceControl.Transaction deferTransactionUntilSurface(SurfaceControl sc,
- Surface barrierSurface,
- long frameNumber) {
- return this;
- }
-
- @Override
public SurfaceControl.Transaction reparentChildren(SurfaceControl sc,
SurfaceControl newParent) {
return this;
@@ -170,12 +163,6 @@ public class StubTransaction extends SurfaceControl.Transaction {
}
@Override
- public SurfaceControl.Transaction setOverrideScalingMode(SurfaceControl sc,
- int overrideScalingMode) {
- return this;
- }
-
- @Override
public SurfaceControl.Transaction setColor(SurfaceControl sc, float[] color) {
return this;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 6a29c5b5424a..6f5389ddacce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -236,6 +236,7 @@ public class SystemServicesTestRule implements TestRule {
inputChannels[0].dispose();
mInputChannel = inputChannels[1];
doReturn(mInputChannel).when(mImService).monitorInput(anyString(), anyInt());
+ doReturn(mInputChannel).when(mImService).createInputChannel(anyString());
// StatusBarManagerInternal
final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
@@ -318,6 +319,9 @@ public class SystemServicesTestRule implements TestRule {
display.setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
spyOn(display);
final TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+
+ // Set the default focused TDA.
+ display.setLastFocusedTaskDisplayArea(taskDisplayArea);
spyOn(taskDisplayArea);
final Task homeStack = taskDisplayArea.getStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
@@ -334,7 +338,7 @@ public class SystemServicesTestRule implements TestRule {
// HighRefreshRateBlacklist with DeviceConfig. We need to undo that here to avoid
// leaking mWmService.
mWmService.mConstants.dispose();
- mWmService.mHighRefreshRateBlacklist.dispose();
+ mWmService.mHighRefreshRateDenylist.dispose();
// This makes sure the posted messages without delay are processed, e.g.
// DisplayPolicy#release, WindowManagerService#setAnimationScale.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index bc3b3a4d7ad1..8b025e34401a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -29,6 +29,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -242,6 +245,58 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
assertEquals(rootWindowContainer.getOrientation(), rootHomeTask.getOrientation());
}
+ @Test
+ public void testIsLastFocused() {
+ final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+ mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
+ FEATURE_VENDOR_FIRST);
+ final Task firstStack = firstTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final Task secondStack = secondTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(firstStack).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(secondStack).build();
+
+ // Activity on TDA1 is focused
+ mDisplayContent.setFocusedApp(firstActivity);
+
+ assertThat(firstTaskDisplayArea.isLastFocused()).isTrue();
+ assertThat(secondTaskDisplayArea.isLastFocused()).isFalse();
+
+ // No focused app, TDA1 is still recorded as last focused.
+ mDisplayContent.setFocusedApp(null);
+
+ assertThat(firstTaskDisplayArea.isLastFocused()).isTrue();
+ assertThat(secondTaskDisplayArea.isLastFocused()).isFalse();
+
+ // Activity on TDA2 is focused
+ mDisplayContent.setFocusedApp(secondActivity);
+
+ assertThat(firstTaskDisplayArea.isLastFocused()).isFalse();
+ assertThat(secondTaskDisplayArea.isLastFocused()).isTrue();
+ }
+
+ @Test
+ public void testIgnoreOrientationRequest() {
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final Task stack = taskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(stack).build();
+
+ mDisplayContent.setFocusedApp(activity);
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+
+ taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
+ }
+
private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
boolean reuseCandidate) {
final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index a908bfef98de..7975899dacbc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -33,6 +33,7 @@ import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
+import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1001,14 +1002,54 @@ public class TaskRecordTests extends WindowTestsBase {
public void testNotSpecifyOrientationByFloatingTask() {
final Task task = getTestTask();
final ActivityRecord activity = task.getTopMostActivity();
- final WindowContainer<?> taskDisplayArea = task.getParent();
+ final WindowContainer<?> parentContainer = task.getParent();
+ final TaskDisplayArea taskDisplayArea = task.getDisplayArea();
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parentContainer.getOrientation());
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
task.setWindowingMode(WINDOWING_MODE_PINNED);
- assertEquals(SCREEN_ORIENTATION_UNSET, taskDisplayArea.getOrientation());
+ // TDA returns the last orientation when child returns UNSET
+ assertEquals(SCREEN_ORIENTATION_UNSET, parentContainer.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, taskDisplayArea.getOrientation());
+ }
+
+ @Test
+ public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() {
+ final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
+ mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
+ FEATURE_VENDOR_FIRST);
+ final Task firstStack = firstTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final Task secondStack = secondTaskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(firstStack).build();
+ final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(secondStack).build();
+ firstActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ secondActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity on TDA1 is focused
+ mDisplayContent.setFocusedApp(firstActivity);
+
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
+
+ // No focused app, TDA1 is still recorded as last focused.
+ mDisplayContent.setFocusedApp(null);
+
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_UNSET, secondTaskDisplayArea.getOrientation());
+
+ // Activity on TDA2 is focused
+ mDisplayContent.setFocusedApp(secondActivity);
+
+ assertEquals(SCREEN_ORIENTATION_UNSET, firstTaskDisplayArea.getOrientation());
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, secondTaskDisplayArea.getOrientation());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 213c1f52aa37..ee16a76bcff8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -156,6 +156,10 @@ class TestDisplayContent extends DisplayContent {
// threads immediately after adding it to hierarchy. Calling doAnswer() type of stubbing
// reduces chance of races, but still doesn't eliminate race conditions.
mService.mRootWindowContainer.addChild(newDisplay, mPosition);
+
+ // Set the default focused TDA.
+ newDisplay.setLastFocusedTaskDisplayArea(newDisplay.getDefaultTaskDisplayArea());
+
return newDisplay;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index d37f3f402c30..ea1223312cb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -94,11 +94,6 @@ public class TestIWindow extends IWindow.Stub {
}
@Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility, int localValue,
- int localChanges) throws RemoteException {
- }
-
- @Override
public void dispatchWindowShown() throws RemoteException {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index dc859046db42..db5c7965ebee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -197,19 +197,19 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void screenTurningOn(ScreenOnListener screenOnListener) {
+ public void screenTurningOn(int displayId, ScreenOnListener screenOnListener) {
}
@Override
- public void screenTurnedOn() {
+ public void screenTurnedOn(int displayId) {
}
@Override
- public void screenTurningOff(ScreenOffListener screenOffListener) {
+ public void screenTurningOff(int displayId, ScreenOffListener screenOffListener) {
}
@Override
- public void screenTurnedOff() {
+ public void screenTurnedOff(int displayId) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
new file mode 100644
index 000000000000..ce22205c75f0
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -0,0 +1,178 @@
+/*
+ * 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 android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.ArrayMap;
+import android.window.ITaskOrganizer;
+import android.window.TransitionInfo;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:TransitionRecordTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TransitionTests extends WindowTestsBase {
+
+ @Test
+ public void testCreateInfo_NewTask() {
+ final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ newTask.setHasBeenVisible(true);
+ oldTask.setHasBeenVisible(false);
+ final ActivityRecord closing = createActivityRecordInTask(oldTask);
+ final ActivityRecord opening = createActivityRecordInTask(newTask);
+ closing.setVisible(true);
+ closing.mVisibleRequested = false;
+ opening.setVisible(false);
+ opening.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check basic both tasks participating
+ participants.put(oldTask, new Transition.ChangeInfo());
+ participants.put(newTask, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+
+ // Check that children are pruned
+ participants.put(opening, new Transition.ChangeInfo());
+ participants.put(closing, new Transition.ChangeInfo());
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check combined prune and promote
+ participants.remove(newTask);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check multi promote
+ participants.remove(oldTask);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+ }
+
+ @Test
+ public void testCreateInfo_NestedTasks() {
+ final Task newTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task newNestedTask = createTaskInStack(newTask, 0);
+ final Task newNestedTask2 = createTaskInStack(newTask, 0);
+ final Task oldTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ newTask.setHasBeenVisible(true);
+ oldTask.setHasBeenVisible(false);
+ final ActivityRecord closing = createActivityRecordInTask(oldTask);
+ final ActivityRecord opening = createActivityRecordInTask(newNestedTask);
+ final ActivityRecord opening2 = createActivityRecordInTask(newNestedTask2);
+ closing.setVisible(true);
+ closing.mVisibleRequested = false;
+ opening.setVisible(false);
+ opening.mVisibleRequested = true;
+ opening2.setVisible(false);
+ opening2.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check full promotion from leaf
+ participants.put(oldTask, new Transition.ChangeInfo());
+ participants.put(opening, new Transition.ChangeInfo());
+ participants.put(opening2, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+ assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+
+ // Check that unchanging but visible descendant of sibling prevents promotion
+ participants.remove(opening2);
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
+ }
+
+ @Test
+ public void testCreateInfo_DisplayArea() {
+ final Task showTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final Task showNestedTask = createTaskInStack(showTask, 0);
+ final Task showTask2 = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, mDisplayContent);
+ final DisplayArea tda = showTask.getDisplayArea();
+ showTask.setHasBeenVisible(true);
+ showTask2.setHasBeenVisible(true);
+ final ActivityRecord showing = createActivityRecordInTask(showNestedTask);
+ final ActivityRecord showing2 = createActivityRecordInTask(showTask2);
+ showing.setVisible(false);
+ showing.mVisibleRequested = true;
+ showing2.setVisible(false);
+ showing2.mVisibleRequested = true;
+ ArrayMap<WindowContainer, Transition.ChangeInfo> participants = new ArrayMap<>();
+
+ int transitType = TRANSIT_TASK_OPEN;
+
+ // Check promotion to DisplayArea
+ participants.put(showing, new Transition.ChangeInfo());
+ participants.put(showing2, new Transition.ChangeInfo());
+ TransitionInfo info =
+ Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(1, info.getChanges().size());
+ assertEquals(transitType, info.getType());
+ assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+
+ ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
+ // Check that organized tasks get reported even if not top
+ showTask.mTaskOrganizer = mockOrg;
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
+ assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
+ // Even if DisplayArea explicitly participating
+ participants.put(tda, new Transition.ChangeInfo());
+ info = Transition.calculateTransitionInfo(transitType, participants);
+ assertEquals(2, info.getChanges().size());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index ca3626d09062..0cf63f4ff21d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -115,12 +115,6 @@ public class WindowFrameTests extends WindowTestsBase {
expectedRect.bottom);
}
- private void assertPolicyCrop(WindowState w, int left, int top, int right, int bottom) {
- Rect policyCrop = new Rect();
- w.calculatePolicyCrop(policyCrop);
- assertRect(policyCrop, left, top, right, bottom);
- }
-
@Test
public void testLayoutInFullscreenTaskInsets() {
// fullscreen task doesn't use bounds for computeFrame
@@ -335,12 +329,10 @@ public class WindowFrameTests extends WindowTestsBase {
final WindowFrames windowFrames = w.getWindowFrames();
windowFrames.setFrames(pf, df, cf, vf, dcf, sf);
w.computeFrame();
- assertPolicyCrop(w, 0, cf.top, logicalWidth, cf.bottom);
windowFrames.mDecorFrame.setEmpty();
// Likewise with no decor frame we would get no crop
w.computeFrame();
- assertPolicyCrop(w, 0, 0, logicalWidth, logicalHeight);
// Now we set up a window which doesn't fill the entire decor frame.
// Normally it would be cropped to it's frame but in the case of docked resizing
@@ -355,16 +347,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mRequestedHeight = logicalHeight / 2;
w.computeFrame();
- // Normally the crop is shrunk from the decor frame
- // to the computed window frame.
- assertPolicyCrop(w, 0, 0, logicalWidth / 2, logicalHeight / 2);
-
doReturn(true).when(w).isDockedResizing();
- // But if we are docked resizing it won't be, however we will still be
- // shrunk to the decor frame and the display.
- assertPolicyCrop(w, 0, 0,
- Math.min(pf.width(), displayInfo.logicalWidth),
- Math.min(pf.height(), displayInfo.logicalHeight));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 5b7cf5a72f0a..749f33e9fec2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.Process.INVALID_UID;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
@@ -32,8 +34,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -176,4 +181,31 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.dismissKeyguard(null, "test-dismiss-keyguard");
verify(mWm.mAtmService.mStackSupervisor).wakeUp(anyString());
}
+
+ @Test
+ public void testMoveWindowTokenToDisplay_NullToken_DoNothing() {
+ mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId());
+
+ verify(mDisplayContent, never()).reParentWindowToken(any());
+ }
+
+ @Test
+ public void testMoveWindowTokenToDisplay_SameDisplay_DoNothing() {
+ final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD_DIALOG,
+ mDisplayContent);
+
+ mWm.moveWindowTokenToDisplay(windowToken.token, mDisplayContent.getDisplayId());
+
+ verify(mDisplayContent, never()).reParentWindowToken(any());
+ }
+
+ @Test
+ public void testMoveWindowTokenToDisplay_DifferentDisplay_DoMoveDisplay() {
+ final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD_DIALOG,
+ mDisplayContent);
+
+ mWm.moveWindowTokenToDisplay(windowToken.token, DEFAULT_DISPLAY);
+
+ assertThat(windowToken.getDisplayContent()).isEqualTo(mDefaultDisplay);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 11eaf8c5cea5..aac83974eb51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -26,6 +26,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -42,6 +45,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -49,12 +54,14 @@ 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 static org.mockito.Mockito.atLeastOnce;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.PictureInPictureParams;
import android.content.pm.ActivityInfo;
+import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -67,6 +74,7 @@ import android.view.Display;
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
+import android.window.TaskAppearedInfo;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -74,8 +82,10 @@ import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
/**
@@ -88,14 +98,27 @@ import java.util.List;
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowOrganizerTests extends WindowTestsBase {
- private ITaskOrganizer registerMockOrganizer() {
+
+ private ITaskOrganizer createMockOrganizer() {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
when(organizer.asBinder()).thenReturn(new Binder());
+ return organizer;
+ }
- mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
+ private ITaskOrganizer registerMockOrganizer(ArrayList<TaskAppearedInfo> existingTasks) {
+ final ITaskOrganizer organizer = createMockOrganizer();
+ ParceledListSlice<TaskAppearedInfo> tasks =
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
+ if (existingTasks != null) {
+ existingTasks.addAll(tasks.getList());
+ }
return organizer;
}
+ private ITaskOrganizer registerMockOrganizer() {
+ return registerMockOrganizer(null);
+ }
+
Task createTask(Task stack, boolean fakeDraw) {
final Task task = createTaskInStack(stack, 0);
@@ -123,27 +146,21 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testAppearVanish() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack);
- final ITaskOrganizer organizer = registerMockOrganizer();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- stack.setTaskOrganizer(organizer);
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
-
stack.removeImmediately();
verify(organizer).onTaskVanished(any());
}
@Test
public void testAppearWaitsForVisibility() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack, false);
- final ITaskOrganizer organizer = registerMockOrganizer();
-
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- stack.setTaskOrganizer(organizer);
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
@@ -158,9 +175,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testNoVanishedIfNoAppear() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack, false /* hasBeenVisible */);
- final ITaskOrganizer organizer = registerMockOrganizer();
// In this test we skip making the Task visible, and verify
// that even though a TaskOrganizer is set remove doesn't emit
@@ -174,28 +191,25 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testTaskNoDraw() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack, false /* fakeDraw */);
- final ITaskOrganizer organizer = registerMockOrganizer();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(stack.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
- verify(organizer, never()).onTaskVanished(any());
+ assertTaskVanished(organizer, false /* expectVanished */, stack);
assertFalse(stack.isOrganized());
}
@Test
public void testClearOrganizer() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack);
- final ITaskOrganizer organizer = registerMockOrganizer();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- stack.setTaskOrganizer(organizer);
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(stack.isOrganized());
@@ -206,16 +220,15 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testUnregisterOrganizer() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack);
- final ITaskOrganizer organizer = registerMockOrganizer();
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(stack.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
- verify(organizer).onTaskVanished(any());
+ assertTaskVanished(organizer, true /* expectVanished */, stack);
assertFalse(stack.isOrganized());
}
@@ -227,37 +240,47 @@ public class WindowOrganizerTests extends WindowTestsBase {
final Task task2 = createTask(stack2);
final Task stack3 = createStack();
final Task task3 = createTask(stack3);
- final ITaskOrganizer organizer = registerMockOrganizer();
-
- // verify that tasks are appeared on registration
- verify(organizer, times(3))
- .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+ final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+ final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
+
+ // verify that tasks are returned and taskAppeared is not called
+ assertContainsTasks(existingTasks, stack, stack2, stack3);
+ verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+ any(SurfaceControl.class));
+ verify(organizer, times(0)).onTaskVanished(any());
assertTrue(stack.isOrganized());
- // Now we replace the registration and1 verify the new organizer receives tasks
- final ITaskOrganizer organizer2 = registerMockOrganizer();
- verify(organizer2, times(3))
- .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+ // Now we replace the registration and verify the new organizer receives existing tasks
+ final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
+ final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
+ assertContainsTasks(existingTasks2, stack, stack2, stack3);
+ verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
+ any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
- // One for task
- verify(organizer, times(3)).onTaskVanished(any());
+ // Removed tasks from the original organizer
+ assertTaskVanished(organizer, true /* expectVanished */, stack, stack2, stack3);
assertTrue(stack2.isOrganized());
// Now we unregister the second one, the first one should automatically be reregistered
// so we verify that it's now seeing changes.
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2);
- verify(organizer, times(6))
+ verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- verify(organizer2, times(3)).onTaskVanished(any());
+ assertTaskVanished(organizer2, true /* expectVanished */, stack, stack2, stack3);
}
@Test
public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
final Task stack = createStack();
final Task task = createTask(stack);
+ final Task stack2 = createStack();
+ final Task task2 = createTask(stack2);
+ ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+ final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
+ assertContainsTasks(existingTasks, stack, stack2);
- final ITaskOrganizer organizer = registerMockOrganizer();
- verify(organizer, times(1))
+ // Verify we don't get onTaskAppeared if we are returned the tasks
+ verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
}
@@ -361,6 +384,77 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testSetIgnoreOrientationRequest_taskDisplayArea() {
+ removeGlobalMinSizeRestriction();
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final Task stack = taskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(stack).build();
+ taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mDisplayContent.setFocusedApp(activity);
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ // TDA returns UNSET when ignoreOrientationRequest == true
+ // DC is UNSPECIFIED when child returns UNSET
+ assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
+
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ t.setIgnoreOrientationRequest(
+ taskDisplayArea.mRemoteToken.toWindowContainerToken(),
+ false /* ignoreOrientationRequest */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+ // TDA returns app request orientation when ignoreOrientationRequest == false
+ // DC uses the same as TDA returns when it is not UNSET.
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+ assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+
+ t.setIgnoreOrientationRequest(
+ taskDisplayArea.mRemoteToken.toWindowContainerToken(),
+ true /* ignoreOrientationRequest */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+ // TDA returns UNSET when ignoreOrientationRequest == true
+ // DC is UNSPECIFIED when child returns UNSET
+ assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testSetIgnoreOrientationRequest_displayContent() {
+ removeGlobalMinSizeRestriction();
+ final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final Task stack = taskDisplayArea.createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setStack(stack).build();
+ mDisplayContent.setFocusedApp(activity);
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ // DC uses the orientation request from app
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ t.setIgnoreOrientationRequest(
+ mDisplayContent.mRemoteToken.toWindowContainerToken(),
+ true /* ignoreOrientationRequest */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+ // DC returns UNSPECIFIED when ignoreOrientationRequest == true
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
+
+ t.setIgnoreOrientationRequest(
+ mDisplayContent.mRemoteToken.toWindowContainerToken(),
+ false /* ignoreOrientationRequest */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+
+ // DC uses the orientation request from app after mIgnoreOrientationRequest is set to false
+ assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
public void testOverrideConfigSize() {
removeGlobalMinSizeRestriction();
final Task stack = new TaskBuilder(mSupervisor)
@@ -878,9 +972,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testPreventDuplicateAppear() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack, false /* fakeDraw */);
- final ITaskOrganizer organizer = registerMockOrganizer();
stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
stack.setTaskOrganizer(organizer);
@@ -901,17 +995,14 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stack = createStack();
final Task task = createTask(stack);
final ActivityRecord activity = createActivityRecordInTask(stack.mDisplayContent, task);
final Task stack2 = createStack();
final Task task2 = createTask(stack2);
final ActivityRecord activity2 = createActivityRecordInTask(stack.mDisplayContent, task2);
- final ITaskOrganizer organizer = registerMockOrganizer();
- // Setup the task to be controlled by the MW mode organizer
- stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertTrue(stack.isOrganized());
assertTrue(stack2.isOrganized());
@@ -938,9 +1029,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testBLASTCallbackWithMultipleWindows() throws Exception {
+ final ITaskOrganizer organizer = registerMockOrganizer();
final Task stackController = createStack();
final Task task = createTask(stackController);
- final ITaskOrganizer organizer = registerMockOrganizer();
final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1");
final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2");
makeWindowVisible(w1);
@@ -991,4 +1082,37 @@ public class WindowOrganizerTests extends WindowTestsBase {
assertFalse(daTask.isForceHidden());
});
}
+
+ /**
+ * Verifies that task vanished is called for a specific task.
+ */
+ private void assertTaskVanished(ITaskOrganizer organizer, boolean expectVanished, Task... tasks)
+ throws RemoteException {
+ ArgumentCaptor<RunningTaskInfo> arg = ArgumentCaptor.forClass(RunningTaskInfo.class);
+ verify(organizer, atLeastOnce()).onTaskVanished(arg.capture());
+ List<RunningTaskInfo> taskInfos = arg.getAllValues();
+
+ HashSet<Integer> vanishedTaskIds = new HashSet<>();
+ for (int i = 0; i < taskInfos.size(); i++) {
+ vanishedTaskIds.add(taskInfos.get(i).taskId);
+ }
+ HashSet<Integer> taskIds = new HashSet<>();
+ for (int i = 0; i < tasks.length; i++) {
+ taskIds.add(tasks[i].mTaskId);
+ }
+
+ assertTrue(expectVanished
+ ? vanishedTaskIds.containsAll(taskIds)
+ : !vanishedTaskIds.removeAll(taskIds));
+ }
+
+ private void assertContainsTasks(List<TaskAppearedInfo> taskInfos, Task... expectedTasks) {
+ HashSet<Integer> taskIds = new HashSet<>();
+ for (int i = 0; i < taskInfos.size(); i++) {
+ taskIds.add(taskInfos.get(i).getTaskInfo().taskId);
+ }
+ for (int i = 0; i < expectedTasks.length; i++) {
+ assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 189e540af0ca..94bbfca012b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -28,7 +28,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.IApplicationThread;
@@ -160,31 +159,6 @@ public class WindowProcessControllerTests extends WindowTestsBase {
}
@Test
- public void testDelayingConfigurationChange() {
- when(mMockListener.isCached()).thenReturn(false);
-
- Configuration tmpConfig = new Configuration(mWpc.getConfiguration());
- invertOrientation(tmpConfig);
- mWpc.onConfigurationChanged(tmpConfig);
-
- // The last reported config should be the current config as the process is not cached.
- Configuration originalConfig = new Configuration(mWpc.getConfiguration());
- assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);
-
- when(mMockListener.isCached()).thenReturn(true);
- invertOrientation(tmpConfig);
- mWpc.onConfigurationChanged(tmpConfig);
-
- Configuration newConfig = new Configuration(mWpc.getConfiguration());
-
- // Last reported config hasn't changed because the process is in a cached state.
- assertEquals(mWpc.getLastReportedConfiguration(), originalConfig);
-
- mWpc.onProcCachedStateChanged(false);
- assertEquals(mWpc.getLastReportedConfiguration(), newConfig);
- }
-
- @Test
public void testActivityNotOverridingSystemUiProcessConfig() {
final ComponentName systemUiServiceComponent = mAtm.getSysUiServiceComponentLocked();
ApplicationInfo applicationInfo = mock(ApplicationInfo.class);
@@ -266,6 +240,78 @@ public class WindowProcessControllerTests extends WindowTestsBase {
mWpc.onMergedOverrideConfigurationChanged(config);
assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType());
assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType());
+
+ final int globalSeq = 100;
+ mRootWindowContainer.getConfiguration().seq = globalSeq;
+ invertOrientation(mWpc.getConfiguration());
+ new ActivityBuilder(mAtm).setCreateTask(true).setUseProcess(mWpc).build();
+
+ assertTrue(mWpc.registeredForActivityConfigChanges());
+ assertEquals("Config seq of process should not be affected by activity",
+ mWpc.getConfiguration().seq, globalSeq);
+ }
+
+ @Test
+ public void testComputeOomAdjFromActivities() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .setUseProcess(mWpc)
+ .build();
+ activity.mVisibleRequested = true;
+ final int[] callbackResult = { 0 };
+ final int visible = 1;
+ final int paused = 2;
+ final int stopping = 4;
+ final int other = 8;
+ final WindowProcessController.ComputeOomAdjCallback callback =
+ new WindowProcessController.ComputeOomAdjCallback() {
+ @Override
+ public void onVisibleActivity() {
+ callbackResult[0] |= visible;
+ }
+
+ @Override
+ public void onPausedActivity() {
+ callbackResult[0] |= paused;
+ }
+
+ @Override
+ public void onStoppingActivity(boolean finishing) {
+ callbackResult[0] |= stopping;
+ }
+
+ @Override
+ public void onOtherActivity() {
+ callbackResult[0] |= other;
+ }
+ };
+
+ // onStartActivity should refresh the state immediately.
+ mWpc.onStartActivity(0 /* topProcessState */, activity.info);
+ assertEquals(1 /* minTaskLayer */, mWpc.computeOomAdjFromActivities(callback));
+ assertEquals(visible, callbackResult[0]);
+
+ // The oom state will be updated in handler from activity state change.
+ callbackResult[0] = 0;
+ activity.mVisibleRequested = false;
+ activity.setState(Task.ActivityState.PAUSED, "test");
+ waitHandlerIdle(mAtm.mH);
+ mWpc.computeOomAdjFromActivities(callback);
+ assertEquals(paused, callbackResult[0]);
+
+ // updateProcessInfo with updateOomAdj=true should refresh the state immediately.
+ callbackResult[0] = 0;
+ activity.setState(Task.ActivityState.STOPPING, "test");
+ mWpc.updateProcessInfo(false /* updateServiceConnectionActivities */,
+ true /* activityChange */, true /* updateOomAdj */, false /* addPendingTopUid */);
+ mWpc.computeOomAdjFromActivities(callback);
+ assertEquals(stopping, callbackResult[0]);
+
+ callbackResult[0] = 0;
+ activity.setState(Task.ActivityState.STOPPED, "test");
+ waitHandlerIdle(mAtm.mH);
+ mWpc.computeOomAdjFromActivities(callback);
+ assertEquals(other, callbackResult[0]);
}
private TestDisplayContent createTestDisplayContentInContainer() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 7daddd8720ab..6237be0f4b26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -265,6 +265,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
return activity;
}
+ /** Creates an {@link ActivityRecord} and adds it to the specified {@link Task}. */
+ static ActivityRecord createActivityRecordInTask(Task task) {
+ return createActivityRecordInTask(task.getDisplayContent(), task);
+ }
+
static ActivityRecord createTestActivityRecord(DisplayContent dc) {
final ActivityRecord activity = new ActivityBuilder(dc.mWmService.mAtmService).build();
postCreateActivitySetup(activity, dc);
@@ -360,7 +365,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
attrs.setTitle(name);
final WindowState w = new WindowState(service, session, iWindow, token, parent,
- OP_NONE, 0, attrs, VISIBLE, ownerId, userId,
+ OP_NONE, attrs, VISIBLE, ownerId, userId,
ownerCanAddInternalSystemWindow,
powerManagerWrapper);
// TODO: Probably better to make this call in the WindowState ctor to avoid errors with
@@ -1088,7 +1093,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
TestWindowState(WindowManagerService service, Session session, IWindow window,
WindowManager.LayoutParams attrs, WindowToken token) {
- super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
+ super(service, session, window, token, null, OP_NONE, attrs, 0, 0, 0,
false /* ownerCanAddInternalSystemWindow */);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index f151d9ca2420..8e56e5bb2d85 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -37,7 +37,6 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.PendingIntent;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.AppStandbyInfo;
import android.app.usage.ConfigurationStats;
@@ -1431,10 +1430,11 @@ public class UsageStatsService extends SystemService implements
private boolean hasObserverPermission() {
final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
+ //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
if (callingUid == Process.SYSTEM_UID
|| (dpmInternal != null
- && dpmInternal.isActiveAdminWithPolicy(callingUid,
- DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) {
+ && (dpmInternal.isActiveProfileOwner(callingUid)
+ || dpmInternal.isActiveDeviceOwner(callingUid)))) {
// Caller is the system or the profile owner, so proceed.
return true;
}
diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
index 462ee19124ff..39f2f296a305 100644
--- a/services/usb/java/com/android/server/usb/MtpNotificationManager.java
+++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
@@ -64,12 +64,13 @@ class MtpNotificationManager {
private final Context mContext;
private final OnOpenInAppListener mListener;
+ private final Receiver mReceiver;
MtpNotificationManager(Context context, OnOpenInAppListener listener) {
mContext = context;
mListener = listener;
- final Receiver receiver = new Receiver();
- context.registerReceiver(receiver, new IntentFilter(ACTION_OPEN_IN_APPS));
+ mReceiver = new Receiver();
+ context.registerReceiver(mReceiver, new IntentFilter(ACTION_OPEN_IN_APPS));
}
void showNotification(UsbDevice device) {
@@ -154,4 +155,8 @@ class MtpNotificationManager {
static interface OnOpenInAppListener {
void onOpenInApp(UsbDevice device);
}
+
+ public void unregister() {
+ mContext.unregisterReceiver(mReceiver);
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index d7b6b5d0d36a..26ee03c25013 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -261,6 +261,15 @@ class UsbProfileGroupSettingsManager {
}
/**
+ * Unregister all broadcast receivers. Must be called explicitly before
+ * object deletion.
+ */
+ public void unregisterReceivers() {
+ mPackageMonitor.unregister();
+ mMtpNotificationManager.unregister();
+ }
+
+ /**
* Remove all defaults and denied packages for a user.
*
* @param userToRemove The user
diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java
index 095e8e9b7b5b..f241e65ba755 100644
--- a/services/usb/java/com/android/server/usb/UsbSerialReader.java
+++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java
@@ -77,7 +77,7 @@ class UsbSerialReader extends IUsbSerialReader.Stub {
UserHandle user = Binder.getCallingUserHandle();
int packageTargetSdkVersion;
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
PackageInfo pkg;
try {
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 14c7f04b9e82..444cb5cb89c7 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -281,7 +281,7 @@ public class UsbService extends IUsbManager.Stub {
int pid = Binder.getCallingPid();
int user = UserHandle.getUserId(uid);
- long ident = clearCallingIdentity();
+ final long ident = clearCallingIdentity();
try {
synchronized (mLock) {
if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
@@ -318,7 +318,7 @@ public class UsbService extends IUsbManager.Stub {
int uid = Binder.getCallingUid();
int user = UserHandle.getUserId(uid);
- long ident = clearCallingIdentity();
+ final long ident = clearCallingIdentity();
try {
synchronized (mLock) {
if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) {
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 7b677eea6b8f..8e53ff412f0a 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -124,6 +124,7 @@ class UsbSettingsManager {
if (mSettingsByProfileGroup.indexOfKey(userToRemove.getIdentifier()) >= 0) {
// The user to remove is the parent user of the group. The parent is the last user
// that gets removed. All state will be removed with the user
+ mSettingsByProfileGroup.get(userToRemove.getIdentifier()).unregisterReceivers();
mSettingsByProfileGroup.remove(userToRemove.getIdentifier());
} else {
// We cannot find the parent user of the user that is removed, hence try to remove
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 333edfd91b16..e20b1a4d6c89 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -505,22 +505,23 @@ class UsbUserPermissionManager {
int uid,
@NonNull Context userContext,
@NonNull PendingIntent pi) {
- long identity = Binder.clearCallingIdentity();
- Intent intent = new Intent();
- if (device != null) {
- intent.putExtra(UsbManager.EXTRA_DEVICE, device);
- } else {
- intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
- }
- intent.putExtra(Intent.EXTRA_INTENT, pi);
- intent.putExtra(Intent.EXTRA_UID, uid);
- intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
- intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
- intent.setComponent(ComponentName.unflattenFromString(userContext.getResources().getString(
- com.android.internal.R.string.config_usbPermissionActivity)));
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
+ final long identity = Binder.clearCallingIdentity();
try {
+ Intent intent = new Intent();
+ if (device != null) {
+ intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+ } else {
+ intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+ }
+ intent.putExtra(Intent.EXTRA_INTENT, pi);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ intent.putExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, canBeDefault);
+ intent.putExtra(UsbManager.EXTRA_PACKAGE, packageName);
+ intent.setComponent(
+ ComponentName.unflattenFromString(userContext.getResources().getString(
+ com.android.internal.R.string.config_usbPermissionActivity)));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
userContext.startActivityAsUser(intent, mUser);
} catch (ActivityNotFoundException e) {
Slog.e(TAG, "unable to start UsbPermissionActivity");
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index edbdd4e94ac8..64f8c585c80b 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -58,7 +58,9 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
+import java.util.function.BiFunction;
/**
* Helper for {@link SoundTrigger} APIs. Supports two types of models:
@@ -116,6 +118,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private PowerSaveModeListener mPowerSaveModeListener;
+ private final BiFunction<Integer, SoundTrigger.StatusListener, SoundTriggerModule>
+ mModuleProvider;
+
// Handler to process call state changes will delay to allow time for the audio
// and sound trigger HALs to process the end of call notifications
// before we re enable pending recognition requests.
@@ -123,15 +128,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private static final int MSG_CALL_STATE_CHANGED = 0;
private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
- SoundTriggerHelper(Context context) {
+ SoundTriggerHelper(Context context,
+ @NonNull BiFunction<Integer, SoundTrigger.StatusListener,
+ SoundTriggerModule> moduleProvider) {
ArrayList <ModuleProperties> modules = new ArrayList<>();
+ mModuleProvider = moduleProvider;
int status = SoundTrigger.listModules(modules);
mContext = context;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mModelDataMap = new HashMap<UUID, ModelData>();
mKeyphraseUuidMap = new HashMap<Integer, UUID>();
- mPhoneStateListener = new MyCallStateListener();
if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
mModuleProperties = null;
@@ -145,6 +152,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (looper == null) {
looper = Looper.getMainLooper();
}
+ mPhoneStateListener = new MyCallStateListener(looper);
if (looper != null) {
mHandler = new Handler(looper) {
@Override
@@ -264,7 +272,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private int prepareForRecognition(ModelData modelData) {
if (mModule == null) {
- mModule = SoundTrigger.attachModule(mModuleProperties.getId(), this, null);
+ mModule = mModuleProvider.apply(mModuleProperties.getId(), this);
if (mModule == null) {
Slog.w(TAG, "prepareForRecognition: cannot attach to sound trigger module");
return STATUS_ERROR;
@@ -1019,7 +1027,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
private void internalClearGlobalStateLocked() {
// Unregister from call state changes.
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
} finally {
@@ -1042,6 +1050,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
class MyCallStateListener extends PhoneStateListener {
+ MyCallStateListener(@NonNull Looper looper) {
+ super(Objects.requireNonNull(looper));
+ }
+
@Override
public void onCallStateChanged(int state, String arg1) {
if (DBG) Slog.d(TAG, "onCallStateChanged: " + state);
@@ -1088,7 +1100,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
if (mRecognitionRequested) {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
// Get the current call state synchronously for the first recognition.
mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index 54dffdc4c13a..f77d4907cf03 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.soundtrigger;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
@@ -25,6 +26,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.permission.Identity;
import com.android.server.voiceinteraction.VoiceInteractionManagerService;
@@ -35,97 +37,114 @@ import java.io.PrintWriter;
* Provides a local service for managing voice-related recoginition models. This is primarily used
* by the {@link VoiceInteractionManagerService}.
*/
-public abstract class SoundTriggerInternal {
+public interface SoundTriggerInternal {
/**
- * Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
+ * Return codes for {@link Session#startRecognition(int, KeyphraseSoundModel,
* IRecognitionStatusCallback, RecognitionConfig)},
- * {@link #stopRecognition(int, IRecognitionStatusCallback)}
+ * {@link Session#stopRecognition(int, IRecognitionStatusCallback)}
*/
- public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
- public static final int STATUS_OK = SoundTrigger.STATUS_OK;
+ int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
+ int STATUS_OK = SoundTrigger.STATUS_OK;
- /**
- * Starts recognition for the given keyphraseId.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * the recognition is to be started.
- * @param soundModel The sound model to use for recognition.
- * @param listener The listener for the recognition events related to the given keyphrase.
- * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
- */
- public abstract int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
- IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig);
+ Session attachAsOriginator(@NonNull Identity originatorIdentity);
+
+ Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
+ @NonNull Identity originatorIdentity);
/**
- * Stops recognition for the given {@link Keyphrase} if a recognition is
- * currently active.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * the recognition is to be stopped.
- * @param listener The listener for the recognition events related to the given keyphrase.
- *
- * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ * Dumps service-wide information.
*/
- public abstract int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener);
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
- public abstract ModuleProperties getModuleProperties();
+ interface Session {
+ /**
+ * Starts recognition for the given keyphraseId.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be started.
+ * @param soundModel The sound model to use for recognition.
+ * @param listener The listener for the recognition events related to the given
+ * keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig);
- /**
- * Set a model specific {@link ModelParams} with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
- * stopping recognition. Once the model is unloaded, the value will be lost.
- * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
- * method.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * to modify model parameters
- * @param modelParam {@link ModelParams}
- * @param value Value to set
- * @return - {@link SoundTrigger#STATUS_OK} in case of success
- * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
- * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
- * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
- * if API is not supported by HAL
- */
- public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+ /**
+ * Stops recognition for the given {@link Keyphrase} if a recognition is
+ * currently active.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * the recognition is to be stopped.
+ * @param listener The listener for the recognition events related to the given
+ * keyphrase.
+ * @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
+ */
+ int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener);
- /**
- * Get a model specific {@link ModelParams}. This parameter will keep its value
- * for the duration the model is loaded regardless of starting and stopping recognition.
- * Once the model is unloaded, the value will be lost. If the value is not set, a default
- * value is returned. See ModelParams for parameter default values.
- * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
- * method.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * to modify model parameters
- * @param modelParam {@link ModelParams}
- * @return value of parameter
- * @throws UnsupportedOperationException if hal or model do not support this API.
- * queryParameter should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * queryParameter should be checked first.
- */
- public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+ ModuleProperties getModuleProperties();
- /**
- * Determine if parameter control is supported for the given model handle.
- * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
- * or {@link SoundTriggerInternal#getParameter}.
- *
- * @param keyphraseId The identifier of the keyphrase for which
- * to modify model parameters
- * @param modelParam {@link ModelParams}
- * @return supported range of parameter, null if not supported
- */
- @Nullable
- public abstract ModelParamRange queryParameter(int keyphraseId,
- @ModelParams int modelParam);
+ /**
+ * Set a model specific {@link ModelParams} with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting
+ * and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * {@link #queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
- /**
- * Unloads (and stops if running) the given keyphraseId
- */
- public abstract int unloadKeyphraseModel(int keyphaseId);
+ /**
+ * Get a model specific {@link ModelParams}. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * {{@link #queryParameter}} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return value of parameter
+ * @throws UnsupportedOperationException if hal or model do not support this API.
+ * queryParameter should be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * queryParameter should be checked first.
+ */
+ int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link #setParameter}
+ * or {@link #getParameter}.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ ModelParamRange queryParameter(int keyphraseId,
+ @ModelParams int modelParam);
+
+ /**
+ * Unloads (and stops if running) the given keyphraseId
+ */
+ int unloadKeyphraseModel(int keyphaseId);
- public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+ /**
+ * Dumps session-wide information.
+ */
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 26d46dbd2ab7..5999044d9427 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -17,6 +17,7 @@
package com.android.server.soundtrigger;
import static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE;
+import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
@@ -25,7 +26,6 @@ import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
-import static android.hardware.soundtrigger.SoundTrigger.STATUS_NO_INIT;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
@@ -35,6 +35,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -54,6 +55,10 @@ import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.permission.PermissionUtil;
+import android.media.permission.SafeCloseable;
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
@@ -75,6 +80,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ISoundTriggerService;
+import com.android.internal.app.ISoundTriggerSession;
import com.android.server.SystemService;
import java.io.FileDescriptor;
@@ -104,10 +110,6 @@ public class SoundTriggerService extends SystemService {
private final SoundTriggerServiceStub mServiceStub;
private final LocalSoundTriggerService mLocalSoundTriggerService;
private SoundTriggerDbHelper mDbHelper;
- private SoundTriggerHelper mSoundTriggerHelper;
- private final TreeMap<UUID, SoundModel> mLoadedModels;
- private Object mCallbacksLock;
- private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
class SoundModelStatTracker {
private class SoundModelStat {
@@ -192,9 +194,6 @@ public class SoundTriggerService extends SystemService {
mContext = context;
mServiceStub = new SoundTriggerServiceStub();
mLocalSoundTriggerService = new LocalSoundTriggerService(context);
- mLoadedModels = new TreeMap<UUID, SoundModel>();
- mCallbacksLock = new Object();
- mCallbacks = new TreeMap<>();
mLock = new Object();
mSoundModelStatTracker = new SoundModelStatTracker();
}
@@ -208,34 +207,54 @@ public class SoundTriggerService extends SystemService {
@Override
public void onBootPhase(int phase) {
Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
- if (PHASE_DEVICE_SPECIFIC_SERVICES_READY == phase) {
- if (isSafeMode()) {
- Slog.w(TAG, "not enabling SoundTriggerService in safe mode");
- return;
- }
-
- initSoundTriggerHelper();
- mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
- } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
+ if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
mDbHelper = new SoundTriggerDbHelper(mContext);
}
}
- private synchronized void initSoundTriggerHelper() {
- if (mSoundTriggerHelper == null) {
- mSoundTriggerHelper = new SoundTriggerHelper(mContext);
- }
+ private SoundTriggerHelper newSoundTriggerHelper() {
+ Identity middlemanIdentity = new Identity();
+ middlemanIdentity.packageName = ActivityThread.currentOpPackageName();
+
+ Identity originatorIdentity = IdentityContext.getNonNull();
+
+ return new SoundTriggerHelper(mContext,
+ (moduleId, listener) -> SoundTrigger.attachModuleAsMiddleman(moduleId, listener,
+ null,
+ middlemanIdentity, originatorIdentity));
}
- private synchronized boolean isInitialized() {
- if (mSoundTriggerHelper == null ) {
- Slog.e(TAG, "SoundTriggerHelper not initialized.");
- return false;
+ class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
+ @Override
+ public ISoundTriggerSession attachAsOriginator(Identity originatorIdentity) {
+ try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
+ originatorIdentity)) {
+ return new SoundTriggerSessionStub(newSoundTriggerHelper());
+ }
+ }
+
+ @Override
+ public ISoundTriggerSession attachAsMiddleman(Identity originatorIdentity,
+ Identity middlemanIdentity) {
+ try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
+ SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity,
+ originatorIdentity)) {
+ return new SoundTriggerSessionStub(newSoundTriggerHelper());
+ }
}
- return true;
}
- class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
+ class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
+ private final SoundTriggerHelper mSoundTriggerHelper;
+ private final TreeMap<UUID, SoundModel> mLoadedModels = new TreeMap<>();
+ private final Object mCallbacksLock = new Object();
+ private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
+
+ SoundTriggerSessionStub(
+ SoundTriggerHelper soundTriggerHelper) {
+ mSoundTriggerHelper = soundTriggerHelper;
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -255,7 +274,6 @@ public class SoundTriggerService extends SystemService {
public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
RecognitionConfig config) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_ERROR;
if (DEBUG) {
Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
}
@@ -291,8 +309,6 @@ public class SoundTriggerService extends SystemService {
sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : "
+ parcelUuid));
- if (!isInitialized()) return STATUS_ERROR;
-
int ret = mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
if (ret == STATUS_OK) {
mSoundModelStatTracker.onStop(parcelUuid.getUuid());
@@ -338,13 +354,11 @@ public class SoundTriggerService extends SystemService {
sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
+ soundModelId));
- if (isInitialized()) {
- // Unload the model if it is loaded.
- mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
+ // Unload the model if it is loaded.
+ mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
- // Stop tracking recognition if it is started.
- mSoundModelStatTracker.onStop(soundModelId.getUuid());
- }
+ // Stop tracking recognition if it is started.
+ mSoundModelStatTracker.onStop(soundModelId.getUuid());
mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
}
@@ -352,7 +366,6 @@ public class SoundTriggerService extends SystemService {
@Override
public int loadGenericSoundModel(GenericSoundModel soundModel) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_ERROR;
if (soundModel == null || soundModel.getUuid() == null) {
Slog.e(TAG, "Invalid sound model");
@@ -387,7 +400,6 @@ public class SoundTriggerService extends SystemService {
@Override
public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_ERROR;
if (soundModel == null || soundModel.getUuid() == null) {
Slog.e(TAG, "Invalid sound model");
@@ -440,7 +452,6 @@ public class SoundTriggerService extends SystemService {
enforceDetectionPermissions(detectionService);
- if (!isInitialized()) return STATUS_ERROR;
if (DEBUG) {
Slog.i(TAG, "startRecognition(): id = " + soundModelId);
}
@@ -510,7 +521,6 @@ public class SoundTriggerService extends SystemService {
@Override
public int stopRecognitionForService(ParcelUuid soundModelId) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_ERROR;
if (DEBUG) {
Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
}
@@ -577,7 +587,6 @@ public class SoundTriggerService extends SystemService {
@Override
public int unloadSoundModel(ParcelUuid soundModelId) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_ERROR;
if (DEBUG) {
Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
}
@@ -628,7 +637,6 @@ public class SoundTriggerService extends SystemService {
@Override
public boolean isRecognitionActive(ParcelUuid parcelUuid) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return false;
synchronized (mCallbacksLock) {
IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
if (callback == null) {
@@ -642,7 +650,6 @@ public class SoundTriggerService extends SystemService {
public int getModelState(ParcelUuid soundModelId) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
int ret = STATUS_ERROR;
- if (!isInitialized()) return ret;
if (DEBUG) {
Slog.i(TAG, "getModelState(): id = " + soundModelId);
}
@@ -681,7 +688,6 @@ public class SoundTriggerService extends SystemService {
@Nullable
public ModuleProperties getModuleProperties() {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return null;
if (DEBUG) {
Slog.i(TAG, "getModuleProperties()");
}
@@ -698,7 +704,6 @@ public class SoundTriggerService extends SystemService {
public int setParameter(ParcelUuid soundModelId,
@ModelParams int modelParam, int value) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return STATUS_NO_INIT;
if (DEBUG) {
Slog.d(TAG, "setParameter(): id=" + soundModelId
+ ", param=" + modelParam
@@ -731,9 +736,6 @@ public class SoundTriggerService extends SystemService {
@ModelParams int modelParam)
throws UnsupportedOperationException, IllegalArgumentException {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) {
- throw new UnsupportedOperationException("SoundTriggerHelper not initialized");
- }
if (DEBUG) {
Slog.d(TAG, "getParameter(): id=" + soundModelId
+ ", param=" + modelParam);
@@ -763,7 +765,6 @@ public class SoundTriggerService extends SystemService {
public ModelParamRange queryParameter(@NonNull ParcelUuid soundModelId,
@ModelParams int modelParam) {
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
- if (!isInitialized()) return null;
if (DEBUG) {
Slog.d(TAG, "queryParameter(): id=" + soundModelId
+ ", param=" + modelParam);
@@ -788,712 +789,745 @@ public class SoundTriggerService extends SystemService {
return mSoundTriggerHelper.queryParameter(soundModel.getUuid(), modelParam);
}
}
- }
-
- /**
- * Counts the number of operations added in the last 24 hours.
- */
- private static class NumOps {
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private int[] mNumOps = new int[24];
- @GuardedBy("mLock")
- private long mLastOpsHourSinceBoot;
/**
- * Clear buckets of new hours that have elapsed since last operation.
+ * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and
+ * executed when the service connects.
*
- * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
- * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
+ * <p>If operations take too long they are forcefully aborted.
*
- * @param currentTime Current elapsed time since boot in ns
+ * <p>This also limits the amount of operations in 24 hours.
*/
- void clearOldOps(long currentTime) {
- synchronized (mLock) {
- long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
-
- // Clear buckets of new hours that have elapsed since last operation
- // I.e. when the last operation was triggered at 1:40 and the current
- // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
- if (mLastOpsHourSinceBoot != 0) {
- for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
- mNumOps[(int) (hour % 24)] = 0;
+ private class RemoteSoundTriggerDetectionService
+ extends IRecognitionStatusCallback.Stub implements ServiceConnection {
+ private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
+
+ private final Object mRemoteServiceLock = new Object();
+
+ /** UUID of the model the service is started for */
+ private final @NonNull
+ ParcelUuid mPuuid;
+ /** Params passed into the start method for the service */
+ private final @Nullable
+ Bundle mParams;
+ /** Component name passed when starting the service */
+ private final @NonNull
+ ComponentName mServiceName;
+ /** User that started the service */
+ private final @NonNull
+ UserHandle mUser;
+ /** Configuration of the recognition the service is handling */
+ private final @NonNull
+ RecognitionConfig mRecognitionConfig;
+ /** Wake lock keeping the remote service alive */
+ private final @NonNull
+ PowerManager.WakeLock mRemoteServiceWakeLock;
+
+ private final @NonNull
+ Handler mHandler;
+
+ /** Callbacks that are called by the service */
+ private final @NonNull
+ ISoundTriggerDetectionServiceClient mClient;
+
+ /** Operations that are pending because the service is not yet connected */
+ @GuardedBy("mRemoteServiceLock")
+ private final ArrayList<Operation> mPendingOps = new ArrayList<>();
+ /** Operations that have been send to the service but have no yet finished */
+ @GuardedBy("mRemoteServiceLock")
+ private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
+ /** The number of operations executed in each of the last 24 hours */
+ private final NumOps mNumOps;
+
+ /** The service binder if connected */
+ @GuardedBy("mRemoteServiceLock")
+ private @Nullable
+ ISoundTriggerDetectionService mService;
+ /** Whether the service has been bound */
+ @GuardedBy("mRemoteServiceLock")
+ private boolean mIsBound;
+ /** Whether the service has been destroyed */
+ @GuardedBy("mRemoteServiceLock")
+ private boolean mIsDestroyed;
+ /**
+ * Set once a final op is scheduled. No further ops can be added and the service is
+ * destroyed once the op finishes.
+ */
+ @GuardedBy("mRemoteServiceLock")
+ private boolean mDestroyOnceRunningOpsDone;
+
+ /** Total number of operations performed by this service */
+ @GuardedBy("mRemoteServiceLock")
+ private int mNumTotalOpsPerformed;
+
+ /**
+ * Create a new remote sound trigger detection service. This only binds to the service
+ * when operations are in flight. Each operation has a certain time it can run. Once no
+ * operations are allowed to run anymore, {@link #stopAllPendingOperations() all
+ * operations are aborted and stopped} and the service is disconnected.
+ *
+ * @param modelUuid The UUID of the model the recognition is for
+ * @param params The params passed to each method of the service
+ * @param serviceName The component name of the service
+ * @param user The user of the service
+ * @param config The configuration of the recognition
+ */
+ public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
+ @Nullable Bundle params, @NonNull ComponentName serviceName,
+ @NonNull UserHandle user, @NonNull RecognitionConfig config) {
+ mPuuid = new ParcelUuid(modelUuid);
+ mParams = params;
+ mServiceName = serviceName;
+ mUser = user;
+ mRecognitionConfig = config;
+ mHandler = new Handler(Looper.getMainLooper());
+
+ PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
+ mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
+ + mServiceName.getClassName());
+
+ synchronized (mLock) {
+ NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
+ if (numOps == null) {
+ numOps = new NumOps();
+ mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
}
+ mNumOps = numOps;
}
- }
- }
- /**
- * Add a new operation.
- *
- * @param currentTime Current elapsed time since boot in ns
- */
- void addOp(long currentTime) {
- synchronized (mLock) {
- long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
-
- mNumOps[(int) (numHoursSinceBoot % 24)]++;
- mLastOpsHourSinceBoot = numHoursSinceBoot;
- }
- }
-
- /**
- * Get the total operations added in the last 24 hours.
- *
- * @return The total number of operations added in the last 24 hours
- */
- int getOpsAdded() {
- synchronized (mLock) {
- int totalOperationsInLastDay = 0;
- for (int i = 0; i < 24; i++) {
- totalOperationsInLastDay += mNumOps[i];
- }
-
- return totalOperationsInLastDay;
- }
- }
- }
-
- /**
- * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
- *
- * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
- */
- private static class Operation {
- private interface ExecuteOp {
- void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
- }
-
- private final @Nullable Runnable mSetupOp;
- private final @NonNull ExecuteOp mExecuteOp;
- private final @Nullable Runnable mDropOp;
-
- private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
- @Nullable Runnable cancelOp) {
- mSetupOp = setupOp;
- mExecuteOp = executeOp;
- mDropOp = cancelOp;
- }
-
- private void setup() {
- if (mSetupOp != null) {
- mSetupOp.run();
- }
- }
-
- void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
- setup();
- mExecuteOp.run(opId, service);
- }
-
- void drop() {
- setup();
-
- if (mDropOp != null) {
- mDropOp.run();
- }
- }
- }
-
- /**
- * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
- * when the service connects.
- *
- * <p>If operations take too long they are forcefully aborted.
- *
- * <p>This also limits the amount of operations in 24 hours.
- */
- private class RemoteSoundTriggerDetectionService
- extends IRecognitionStatusCallback.Stub implements ServiceConnection {
- private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
-
- private final Object mRemoteServiceLock = new Object();
-
- /** UUID of the model the service is started for */
- private final @NonNull ParcelUuid mPuuid;
- /** Params passed into the start method for the service */
- private final @Nullable Bundle mParams;
- /** Component name passed when starting the service */
- private final @NonNull ComponentName mServiceName;
- /** User that started the service */
- private final @NonNull UserHandle mUser;
- /** Configuration of the recognition the service is handling */
- private final @NonNull RecognitionConfig mRecognitionConfig;
- /** Wake lock keeping the remote service alive */
- private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
-
- private final @NonNull Handler mHandler;
-
- /** Callbacks that are called by the service */
- private final @NonNull ISoundTriggerDetectionServiceClient mClient;
-
- /** Operations that are pending because the service is not yet connected */
- @GuardedBy("mRemoteServiceLock")
- private final ArrayList<Operation> mPendingOps = new ArrayList<>();
- /** Operations that have been send to the service but have no yet finished */
- @GuardedBy("mRemoteServiceLock")
- private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
- /** The number of operations executed in each of the last 24 hours */
- private final NumOps mNumOps;
-
- /** The service binder if connected */
- @GuardedBy("mRemoteServiceLock")
- private @Nullable ISoundTriggerDetectionService mService;
- /** Whether the service has been bound */
- @GuardedBy("mRemoteServiceLock")
- private boolean mIsBound;
- /** Whether the service has been destroyed */
- @GuardedBy("mRemoteServiceLock")
- private boolean mIsDestroyed;
- /**
- * Set once a final op is scheduled. No further ops can be added and the service is
- * destroyed once the op finishes.
- */
- @GuardedBy("mRemoteServiceLock")
- private boolean mDestroyOnceRunningOpsDone;
-
- /** Total number of operations performed by this service */
- @GuardedBy("mRemoteServiceLock")
- private int mNumTotalOpsPerformed;
-
- /**
- * Create a new remote sound trigger detection service. This only binds to the service when
- * operations are in flight. Each operation has a certain time it can run. Once no
- * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
- * are aborted and stopped} and the service is disconnected.
- *
- * @param modelUuid The UUID of the model the recognition is for
- * @param params The params passed to each method of the service
- * @param serviceName The component name of the service
- * @param user The user of the service
- * @param config The configuration of the recognition
- */
- public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
- @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
- @NonNull RecognitionConfig config) {
- mPuuid = new ParcelUuid(modelUuid);
- mParams = params;
- mServiceName = serviceName;
- mUser = user;
- mRecognitionConfig = config;
- mHandler = new Handler(Looper.getMainLooper());
-
- PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
- mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
- + mServiceName.getClassName());
-
- synchronized (mLock) {
- NumOps numOps = mNumOpsPerPackage.get(mServiceName.getPackageName());
- if (numOps == null) {
- numOps = new NumOps();
- mNumOpsPerPackage.put(mServiceName.getPackageName(), numOps);
- }
- mNumOps = numOps;
- }
-
- mClient = new ISoundTriggerDetectionServiceClient.Stub() {
- @Override
- public void onOpFinished(int opId) {
- long token = Binder.clearCallingIdentity();
- try {
- synchronized (mRemoteServiceLock) {
- mRunningOpIds.remove(opId);
-
- if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
- if (mDestroyOnceRunningOpsDone) {
- destroy();
- } else {
- disconnectLocked();
+ mClient = new ISoundTriggerDetectionServiceClient.Stub() {
+ @Override
+ public void onOpFinished(int opId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mRemoteServiceLock) {
+ mRunningOpIds.remove(opId);
+
+ if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
+ if (mDestroyOnceRunningOpsDone) {
+ destroy();
+ } else {
+ disconnectLocked();
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- } finally {
- Binder.restoreCallingIdentity(token);
}
- }
- };
- }
+ };
+ }
- @Override
- public boolean pingBinder() {
- return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
- }
+ @Override
+ public boolean pingBinder() {
+ return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
+ }
- /**
- * Disconnect from the service, but allow to re-connect when new operations are triggered.
- */
- @GuardedBy("mRemoteServiceLock")
- private void disconnectLocked() {
- if (mService != null) {
- try {
- mService.removeClient(mPuuid);
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Cannot remove client", e);
+ /**
+ * Disconnect from the service, but allow to re-connect when new operations are
+ * triggered.
+ */
+ @GuardedBy("mRemoteServiceLock")
+ private void disconnectLocked() {
+ if (mService != null) {
+ try {
+ mService.removeClient(mPuuid);
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Cannot remove client", e);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": Cannot remove client"));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": Cannot remove client"));
- }
+ }
- mService = null;
- }
+ mService = null;
+ }
- if (mIsBound) {
- mContext.unbindService(RemoteSoundTriggerDetectionService.this);
- mIsBound = false;
+ if (mIsBound) {
+ mContext.unbindService(RemoteSoundTriggerDetectionService.this);
+ mIsBound = false;
- synchronized (mCallbacksLock) {
- mRemoteServiceWakeLock.release();
+ synchronized (mCallbacksLock) {
+ mRemoteServiceWakeLock.release();
+ }
}
}
- }
- /**
- * Disconnect, do not allow to reconnect to the service. All further operations will be
- * dropped.
- */
- private void destroy() {
- if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
+ /**
+ * Disconnect, do not allow to reconnect to the service. All further operations will be
+ * dropped.
+ */
+ private void destroy() {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy"));
- synchronized (mRemoteServiceLock) {
- disconnectLocked();
+ synchronized (mRemoteServiceLock) {
+ disconnectLocked();
- mIsDestroyed = true;
- }
+ mIsDestroyed = true;
+ }
- // The callback is removed before the flag is set
- if (!mDestroyOnceRunningOpsDone) {
- synchronized (mCallbacksLock) {
- mCallbacks.remove(mPuuid.getUuid());
+ // The callback is removed before the flag is set
+ if (!mDestroyOnceRunningOpsDone) {
+ synchronized (mCallbacksLock) {
+ mCallbacks.remove(mPuuid.getUuid());
+ }
}
}
- }
- /**
- * Stop all pending operations and then disconnect for the service.
- */
- private void stopAllPendingOperations() {
- synchronized (mRemoteServiceLock) {
- if (mIsDestroyed) {
- return;
- }
+ /**
+ * Stop all pending operations and then disconnect for the service.
+ */
+ private void stopAllPendingOperations() {
+ synchronized (mRemoteServiceLock) {
+ if (mIsDestroyed) {
+ return;
+ }
- if (mService != null) {
- int numOps = mRunningOpIds.size();
- for (int i = 0; i < numOps; i++) {
- try {
- mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Could not stop operation "
- + mRunningOpIds.valueAt(i), e);
+ if (mService != null) {
+ int numOps = mRunningOpIds.size();
+ for (int i = 0; i < numOps; i++) {
+ try {
+ mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not stop operation "
+ + mRunningOpIds.valueAt(i), e);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": Could not stop operation " + mRunningOpIds.valueAt(i)));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": Could not stop operation " + mRunningOpIds.valueAt(
+ i)));
+ }
}
+
+ mRunningOpIds.clear();
}
- mRunningOpIds.clear();
+ disconnectLocked();
}
-
- disconnectLocked();
}
- }
- /**
- * Verify that the service has the expected properties and then bind to the service
- */
- private void bind() {
- long token = Binder.clearCallingIdentity();
- try {
- Intent i = new Intent();
- i.setComponent(mServiceName);
+ /**
+ * Verify that the service has the expected properties and then bind to the service
+ */
+ private void bind() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Intent i = new Intent();
+ i.setComponent(mServiceName);
- ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
- GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
- mUser.getIdentifier());
+ ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
+ GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
+ mUser.getIdentifier());
- if (ri == null) {
- Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
+ if (ri == null) {
+ Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": " + mServiceName + " not found"));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": " + mServiceName + " not found"));
- return;
- }
+ return;
+ }
- if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
- .equals(ri.serviceInfo.permission)) {
- Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
- + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
+ if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
+ .equals(ri.serviceInfo.permission)) {
+ Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
+ + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": " + mServiceName + " does not require "
- + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": " + mServiceName + " does not require "
+ + BIND_SOUND_TRIGGER_DETECTION_SERVICE));
- return;
- }
+ return;
+ }
- mIsBound = mContext.bindServiceAsUser(i, this,
- BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES,
- mUser);
+ mIsBound = mContext.bindServiceAsUser(i, this,
+ BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES,
+ mUser);
- if (mIsBound) {
- mRemoteServiceWakeLock.acquire();
- } else {
- Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
+ if (mIsBound) {
+ mRemoteServiceWakeLock.acquire();
+ } else {
+ Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": Could not bind to " + mServiceName));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": Could not bind to " + mServiceName));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- } finally {
- Binder.restoreCallingIdentity(token);
}
- }
- /**
- * Run an operation (i.e. send it do the service). If the service is not connected, this
- * binds the service and then runs the operation once connected.
- *
- * @param op The operation to run
- */
- private void runOrAddOperation(Operation op) {
- synchronized (mRemoteServiceLock) {
- if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
- Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
- + "destruction");
+ /**
+ * Run an operation (i.e. send it do the service). If the service is not connected, this
+ * binds the service and then runs the operation once connected.
+ *
+ * @param op The operation to run
+ */
+ private void runOrAddOperation(Operation op) {
+ synchronized (mRemoteServiceLock) {
+ if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
+ Slog.w(TAG,
+ mPuuid + ": Dropped operation as already destroyed or marked for "
+ + "destruction");
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ":Dropped operation as already destroyed or marked for "
+ + "destruction"));
+
+ op.drop();
+ return;
+ }
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ":Dropped operation as already destroyed or marked for destruction"));
+ if (mService == null) {
+ mPendingOps.add(op);
- op.drop();
- return;
- }
+ if (!mIsBound) {
+ bind();
+ }
+ } else {
+ long currentTime = System.nanoTime();
+ mNumOps.clearOldOps(currentTime);
- if (mService == null) {
- mPendingOps.add(op);
+ // Drop operation if too many were executed in the last 24 hours.
+ int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
+ MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
+ Integer.MAX_VALUE);
- if (!mIsBound) {
- bind();
- }
- } else {
- long currentTime = System.nanoTime();
- mNumOps.clearOldOps(currentTime);
-
- // Drop operation if too many were executed in the last 24 hours.
- int opsAllowed = Settings.Global.getInt(mContext.getContentResolver(),
- MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
- Integer.MAX_VALUE);
-
- // As we currently cannot dropping an op safely, disable throttling
- int opsAdded = mNumOps.getOpsAdded();
- if (false && mNumOps.getOpsAdded() >= opsAllowed) {
- try {
- if (DEBUG || opsAllowed + 10 > opsAdded) {
- Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
- + "were run in last 24 hours");
+ // As we currently cannot dropping an op safely, disable throttling
+ int opsAdded = mNumOps.getOpsAdded();
+ if (false && mNumOps.getOpsAdded() >= opsAllowed) {
+ try {
+ if (DEBUG || opsAllowed + 10 > opsAdded) {
+ Slog.w(TAG,
+ mPuuid + ": Dropped operation as too many operations "
+ + "were run in last 24 hours");
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": Dropped operation as too many operations "
- + "were run in last 24 hours"));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": Dropped operation as too many operations "
+ + "were run in last 24 hours"));
- }
+ }
- op.drop();
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Could not drop operation", e);
+ op.drop();
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not drop operation", e);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ ": Could not drop operation"));
- }
- } else {
- mNumOps.addOp(currentTime);
+ }
+ } else {
+ mNumOps.addOp(currentTime);
- // Find a free opID
- int opId = mNumTotalOpsPerformed;
- do {
- mNumTotalOpsPerformed++;
- } while (mRunningOpIds.contains(opId));
+ // Find a free opID
+ int opId = mNumTotalOpsPerformed;
+ do {
+ mNumTotalOpsPerformed++;
+ } while (mRunningOpIds.contains(opId));
- // Run OP
- try {
- if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
+ // Run OP
+ try {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ ": runOp " + opId));
- op.run(opId, mService);
- mRunningOpIds.add(opId);
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
+ op.run(opId, mService);
+ mRunningOpIds.add(opId);
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ ": Could not run operation " + opId));
+ }
}
- }
- // Unbind from service if no operations are left (i.e. if the operation failed)
- if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
- if (mDestroyOnceRunningOpsDone) {
- destroy();
+ // Unbind from service if no operations are left (i.e. if the operation
+ // failed)
+ if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
+ if (mDestroyOnceRunningOpsDone) {
+ destroy();
+ } else {
+ disconnectLocked();
+ }
} else {
- disconnectLocked();
+ mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
+ mHandler.sendMessageDelayed(obtainMessage(
+ RemoteSoundTriggerDetectionService::stopAllPendingOperations,
+ this)
+ .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
+ Settings.Global.getLong(mContext.getContentResolver(),
+ SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
+ Long.MAX_VALUE));
}
- } else {
- mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
- mHandler.sendMessageDelayed(obtainMessage(
- RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
- .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
- Settings.Global.getLong(mContext.getContentResolver(),
- SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
- Long.MAX_VALUE));
}
}
}
- }
-
- @Override
- public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
- Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
- + ")");
-
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
- + ": IGNORED onKeyphraseDetected(" + event + ")"));
- }
+ @Override
+ public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
+ Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
+ + ")");
- /**
- * Create an AudioRecord enough for starting and releasing the data buffered for the event.
- *
- * @param event The event that was received
- * @return The initialized AudioRecord
- */
- private @NonNull AudioRecord createAudioRecordForEvent(
- @NonNull SoundTrigger.GenericRecognitionEvent event)
- throws IllegalArgumentException, UnsupportedOperationException {
- AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
- attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
- AudioAttributes attributes = attributesBuilder.build();
-
- AudioFormat originalFormat = event.getCaptureFormat();
-
- sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
-
- return (new AudioRecord.Builder())
- .setAudioAttributes(attributes)
- .setAudioFormat((new AudioFormat.Builder())
- .setChannelMask(originalFormat.getChannelMask())
- .setEncoding(originalFormat.getEncoding())
- .setSampleRate(originalFormat.getSampleRate())
- .build())
- .setSessionId(event.getCaptureSession())
- .build();
- }
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName
+ + ": IGNORED onKeyphraseDetected(" + event + ")"));
+ }
- @Override
- public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
- if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
-
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": Generic sound trigger event: " + event));
-
- runOrAddOperation(new Operation(
- // always execute:
- () -> {
- if (!mRecognitionConfig.allowMultipleTriggers) {
- // Unregister this remoteService once op is done
- synchronized (mCallbacksLock) {
- mCallbacks.remove(mPuuid.getUuid());
- }
- mDestroyOnceRunningOpsDone = true;
- }
- },
- // execute if not throttled:
- (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
- // execute if throttled:
- () -> {
- if (event.isCaptureAvailable()) {
- try {
- AudioRecord capturedData = createAudioRecordForEvent(event);
- capturedData.startRecording();
- capturedData.release();
- } catch (IllegalArgumentException | UnsupportedOperationException e) {
- Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
- + "), failed to create AudioRecord");
- }
- }
- }));
- }
+ /**
+ * Create an AudioRecord enough for starting and releasing the data buffered for the event.
+ *
+ * @param event The event that was received
+ * @return The initialized AudioRecord
+ */
+ private @NonNull AudioRecord createAudioRecordForEvent(
+ @NonNull SoundTrigger.GenericRecognitionEvent event)
+ throws IllegalArgumentException, UnsupportedOperationException {
+ AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+ attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
+ AudioAttributes attributes = attributesBuilder.build();
+
+ AudioFormat originalFormat = event.getCaptureFormat();
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent"));
+
+ return (new AudioRecord.Builder())
+ .setAudioAttributes(attributes)
+ .setAudioFormat((new AudioFormat.Builder())
+ .setChannelMask(originalFormat.getChannelMask())
+ .setEncoding(originalFormat.getEncoding())
+ .setSampleRate(originalFormat.getSampleRate())
+ .build())
+ .setSessionId(event.getCaptureSession())
+ .build();
+ }
- @Override
- public void onError(int status) {
- if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
+ @Override
+ public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": onError: " + status));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": Generic sound trigger event: " + event));
- runOrAddOperation(
- new Operation(
- // always execute:
- () -> {
+ runOrAddOperation(new Operation(
+ // always execute:
+ () -> {
+ if (!mRecognitionConfig.allowMultipleTriggers) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
}
mDestroyOnceRunningOpsDone = true;
- },
- // execute if not throttled:
- (opId, service) -> service.onError(mPuuid, opId, status),
- // nothing to do if throttled
- null));
- }
+ }
+ },
+ // execute if not throttled:
+ (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
+ // execute if throttled:
+ () -> {
+ if (event.isCaptureAvailable()) {
+ try {
+ AudioRecord capturedData = createAudioRecordForEvent(event);
+ capturedData.startRecording();
+ capturedData.release();
+ } catch (IllegalArgumentException | UnsupportedOperationException e) {
+ Slog.w(TAG, mPuuid + ": createAudioRecordForEvent(" + event
+ + "), failed to create AudioRecord");
+ }
+ }
+ }));
+ }
- @Override
- public void onRecognitionPaused() {
- Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
+ @Override
+ public void onError(int status) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": onError: " + status));
+
+ runOrAddOperation(
+ new Operation(
+ // always execute:
+ () -> {
+ // Unregister this remoteService once op is done
+ synchronized (mCallbacksLock) {
+ mCallbacks.remove(mPuuid.getUuid());
+ }
+ mDestroyOnceRunningOpsDone = true;
+ },
+ // execute if not throttled:
+ (opId, service) -> service.onError(mPuuid, opId, status),
+ // nothing to do if throttled
+ null));
+ }
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
+ @Override
+ public void onRecognitionPaused() {
+ Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
- }
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + "->" + mServiceName + ": IGNORED onRecognitionPaused"));
- @Override
- public void onRecognitionResumed() {
- Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
+ }
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
+ @Override
+ public void onRecognitionResumed() {
+ Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
- }
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + "->" + mServiceName + ": IGNORED onRecognitionResumed"));
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
+ }
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": onServiceConnected(" + service + ")"));
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
- synchronized (mRemoteServiceLock) {
- mService = ISoundTriggerDetectionService.Stub.asInterface(service);
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": onServiceConnected(" + service + ")"));
- try {
- mService.setClient(mPuuid, mParams, mClient);
- } catch (Exception e) {
- Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
- return;
+ synchronized (mRemoteServiceLock) {
+ mService = ISoundTriggerDetectionService.Stub.asInterface(service);
+
+ try {
+ mService.setClient(mPuuid, mParams, mClient);
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
+ return;
+ }
+
+ while (!mPendingOps.isEmpty()) {
+ runOrAddOperation(mPendingOps.remove(0));
+ }
}
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
- while (!mPendingOps.isEmpty()) {
- runOrAddOperation(mPendingOps.remove(0));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": onServiceDisconnected"));
+
+ synchronized (mRemoteServiceLock) {
+ mService = null;
}
}
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
+ @Override
+ public void onBindingDied(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": onServiceDisconnected"));
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
+ + ": onBindingDied"));
- synchronized (mRemoteServiceLock) {
- mService = null;
+ synchronized (mRemoteServiceLock) {
+ destroy();
+ }
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
+
+ sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
+ + mPuuid + " returned a null binding"));
+
+ synchronized (mRemoteServiceLock) {
+ disconnectLocked();
+ }
}
}
+ }
- @Override
- public void onBindingDied(ComponentName name) {
- if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
+ /**
+ * Counts the number of operations added in the last 24 hours.
+ */
+ private static class NumOps {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private int[] mNumOps = new int[24];
+ @GuardedBy("mLock")
+ private long mLastOpsHourSinceBoot;
- sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid
- + ": onBindingDied"));
+ /**
+ * Clear buckets of new hours that have elapsed since last operation.
+ *
+ * <p>I.e. when the last operation was triggered at 1:40 and the current operation was
+ * triggered at 4:03, the buckets "2, 3, and 4" are cleared.
+ *
+ * @param currentTime Current elapsed time since boot in ns
+ */
+ void clearOldOps(long currentTime) {
+ synchronized (mLock) {
+ long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
- synchronized (mRemoteServiceLock) {
- destroy();
+ // Clear buckets of new hours that have elapsed since last operation
+ // I.e. when the last operation was triggered at 1:40 and the current
+ // operation was triggered at 4:03, the bucket "2, 3, and 4" is cleared
+ if (mLastOpsHourSinceBoot != 0) {
+ for (long hour = mLastOpsHourSinceBoot + 1; hour <= numHoursSinceBoot; hour++) {
+ mNumOps[(int) (hour % 24)] = 0;
+ }
+ }
}
}
- @Override
- public void onNullBinding(ComponentName name) {
- Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
+ /**
+ * Add a new operation.
+ *
+ * @param currentTime Current elapsed time since boot in ns
+ */
+ void addOp(long currentTime) {
+ synchronized (mLock) {
+ long numHoursSinceBoot = TimeUnit.HOURS.convert(currentTime, TimeUnit.NANOSECONDS);
- sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model "
- + mPuuid + " returned a null binding"));
+ mNumOps[(int) (numHoursSinceBoot % 24)]++;
+ mLastOpsHourSinceBoot = numHoursSinceBoot;
+ }
+ }
- synchronized (mRemoteServiceLock) {
- disconnectLocked();
+ /**
+ * Get the total operations added in the last 24 hours.
+ *
+ * @return The total number of operations added in the last 24 hours
+ */
+ int getOpsAdded() {
+ synchronized (mLock) {
+ int totalOperationsInLastDay = 0;
+ for (int i = 0; i < 24; i++) {
+ totalOperationsInLastDay += mNumOps[i];
+ }
+
+ return totalOperationsInLastDay;
}
}
}
- public final class LocalSoundTriggerService extends SoundTriggerInternal {
- private final Context mContext;
- private SoundTriggerHelper mSoundTriggerHelper;
-
- LocalSoundTriggerService(Context context) {
- mContext = context;
+ /**
+ * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
+ *
+ * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
+ */
+ private static class Operation {
+ private interface ExecuteOp {
+ void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
}
- synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
- mSoundTriggerHelper = helper;
+ private final @Nullable Runnable mSetupOp;
+ private final @NonNull ExecuteOp mExecuteOp;
+ private final @Nullable Runnable mDropOp;
+
+ private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
+ @Nullable Runnable cancelOp) {
+ mSetupOp = setupOp;
+ mExecuteOp = executeOp;
+ mDropOp = cancelOp;
}
- @Override
- public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
- IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
- recognitionConfig);
+ private void setup() {
+ if (mSetupOp != null) {
+ mSetupOp.run();
+ }
}
- @Override
- public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
+ void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
+ setup();
+ mExecuteOp.run(opId, service);
}
- @Override
- public ModuleProperties getModuleProperties() {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.getModuleProperties();
+ void drop() {
+ setup();
+
+ if (mDropOp != null) {
+ mDropOp.run();
+ }
}
+ }
- @Override
- public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+ public final class LocalSoundTriggerService implements SoundTriggerInternal {
+ private final Context mContext;
+ LocalSoundTriggerService(Context context) {
+ mContext = context;
}
- @Override
- public int getParameter(int keyphraseId, @ModelParams int modelParam) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+ private class SessionImpl implements Session {
+ private final @NonNull SoundTriggerHelper mSoundTriggerHelper;
+
+ private SessionImpl(
+ @NonNull SoundTriggerHelper soundTriggerHelper) {
+ mSoundTriggerHelper = soundTriggerHelper;
+ }
+
+ @Override
+ public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
+ return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel,
+ listener,
+ recognitionConfig);
+ }
+
+ @Override
+ public synchronized int stopRecognition(int keyphraseId,
+ IRecognitionStatusCallback listener) {
+ return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
+ }
+
+ @Override
+ public ModuleProperties getModuleProperties() {
+ return mSoundTriggerHelper.getModuleProperties();
+ }
+
+ @Override
+ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+ return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+ }
+
+ @Override
+ public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+ return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+ return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
+ public int unloadKeyphraseModel(int keyphraseId) {
+ return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mSoundTriggerHelper.dump(fd, pw, args);
+ }
}
@Override
- @Nullable
- public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+ public Session attachAsOriginator(@NonNull Identity originatorIdentity) {
+ try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
+ originatorIdentity)) {
+ return new SessionImpl(newSoundTriggerHelper());
+ }
}
@Override
- public int unloadKeyphraseModel(int keyphraseId) {
- if (!isInitialized()) throw new UnsupportedOperationException();
- return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
+ public Session attachAsMiddleman(@NonNull Identity middlemanIdentity,
+ @NonNull Identity originatorIdentity) {
+ try (SafeCloseable ignored = PermissionUtil.establishIdentityIndirect(mContext,
+ SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity, originatorIdentity)) {
+ return new SessionImpl(newSoundTriggerHelper());
+ }
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!isInitialized()) return;
- mSoundTriggerHelper.dump(fd, pw, args);
// log
sEventLogger.dump(pw);
@@ -1503,18 +1537,6 @@ public class SoundTriggerService extends SystemService {
// stats
mSoundModelStatTracker.dump(pw);
}
-
- private synchronized boolean isInitialized() {
- if (mSoundTriggerHelper == null ) {
- Slog.e(TAG, "SoundTriggerHelper not initialized.");
-
- sEventLogger.log(new SoundTriggerLogger.StringEvent(
- "SoundTriggerHelper not initialized."));
-
- return false;
- }
- return true;
- }
}
private void enforceCallingPermission(String permission) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index a2215ceed36a..917f65ab7c01 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
@@ -48,6 +49,11 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.Identity;
+import android.media.permission.IdentityContext;
+import android.media.permission.PermissionUtil;
+import android.media.permission.SafeCloseable;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -69,6 +75,7 @@ import android.service.voice.VoiceInteractionServiceInfo;
import android.service.voice.VoiceInteractionSession;
import android.speech.RecognitionService;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
@@ -78,6 +85,7 @@ import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
@@ -93,7 +101,10 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -111,7 +122,8 @@ public class VoiceInteractionManagerService extends SystemService {
final ActivityManagerInternal mAmInternal;
final ActivityTaskManagerInternal mAtmInternal;
final UserManagerInternal mUserManagerInternal;
- final ArraySet<Integer> mLoadedKeyphraseIds = new ArraySet<>();
+ final ArrayMap<Integer, VoiceInteractionManagerServiceStub.SoundTriggerSession>
+ mLoadedKeyphraseIds = new ArrayMap<>();
ShortcutServiceInternal mShortcutServiceInternal;
SoundTriggerInternal mSoundTriggerInternal;
@@ -236,12 +248,28 @@ public class VoiceInteractionManagerService extends SystemService {
private boolean mTemporarilyDisabled;
private final boolean mEnableService;
+ private final List<WeakReference<SoundTriggerSession>> mSessions = new LinkedList<>();
VoiceInteractionManagerServiceStub() {
mEnableService = shouldEnableService(mContext);
new RoleObserver(mContext.getMainExecutor());
}
+ @Override
+ public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator(
+ @NonNull Identity originatorIdentity) {
+ Objects.requireNonNull(originatorIdentity);
+ try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect(
+ originatorIdentity)) {
+ SoundTriggerSession session = new SoundTriggerSession(
+ mSoundTriggerInternal.attachAsOriginator(IdentityContext.getNonNull()));
+ synchronized (mSessions) {
+ mSessions.add(new WeakReference<>(session));
+ }
+ return session;
+ }
+ }
+
// TODO: VI Make sure the caller is the current user or profile
void startLocalVoiceInteraction(final IBinder token, Bundle options) {
if (mImpl == null) return;
@@ -1011,15 +1039,18 @@ public class VoiceInteractionManagerService extends SystemService {
}
final int callingUserId = UserHandle.getCallingUserId();
- final long caller = Binder.clearCallingIdentity();
boolean deleted = false;
+ final long caller = Binder.clearCallingIdentity();
try {
- int unloadStatus = mSoundTriggerInternal.unloadKeyphraseModel(keyphraseId);
- if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
- Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
+ SoundTriggerSession session = mLoadedKeyphraseIds.get(keyphraseId);
+ if (session != null) {
+ int unloadStatus = session.unloadKeyphraseModel(keyphraseId);
+ if (unloadStatus != SoundTriggerInternal.STATUS_OK) {
+ Slog.w(TAG, "Unable to unload keyphrase sound model:" + unloadStatus);
+ }
+ deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUserId,
+ bcp47Locale);
}
- deleted = mDbHelper.deleteKeyphraseSoundModel(
- keyphraseId, callingUserId, bcp47Locale);
return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
} finally {
if (deleted) {
@@ -1092,132 +1123,145 @@ public class VoiceInteractionManagerService extends SystemService {
return null;
}
- @Override
- public ModuleProperties getDspModuleProperties() {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ class SoundTriggerSession extends IVoiceInteractionSoundTriggerSession.Stub {
+ final SoundTriggerInternal.Session mSession;
- final long caller = Binder.clearCallingIdentity();
- try {
- return mSoundTriggerInternal.getModuleProperties();
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
+ SoundTriggerSession(
+ SoundTriggerInternal.Session session) {
+ mSession = session;
}
- }
- @Override
- public int startRecognition(int keyphraseId, String bcp47Locale,
- IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ @Override
+ public ModuleProperties getDspModuleProperties() {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
- if (callback == null || recognitionConfig == null || bcp47Locale == null) {
- throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSession.getModuleProperties();
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
}
- final int callingUserId = UserHandle.getCallingUserId();
- final long caller = Binder.clearCallingIdentity();
- try {
- KeyphraseSoundModel soundModel =
- mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
- if (soundModel == null
- || soundModel.getUuid() == null
- || soundModel.getKeyphrases() == null) {
- Slog.w(TAG, "No matching sound model found in startRecognition");
- return SoundTriggerInternal.STATUS_ERROR;
- } else {
- // Regardless of the status of the start recognition, we need to make sure
- // that we unload this model if needed later.
- synchronized (this) {
- mLoadedKeyphraseIds.add(keyphraseId);
+ @Override
+ public int startRecognition(int keyphraseId, String bcp47Locale,
+ IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
+
+ if (callback == null || recognitionConfig == null || bcp47Locale == null) {
+ throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
}
- return mSoundTriggerInternal.startRecognition(
- keyphraseId, soundModel, callback, recognitionConfig);
}
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
- }
- @Override
- public int stopRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ KeyphraseSoundModel soundModel =
+ mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+ if (soundModel == null
+ || soundModel.getUuid() == null
+ || soundModel.getKeyphrases() == null) {
+ Slog.w(TAG, "No matching sound model found in startRecognition");
+ return SoundTriggerInternal.STATUS_ERROR;
+ } else {
+ // Regardless of the status of the start recognition, we need to make sure
+ // that we unload this model if needed later.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ mLoadedKeyphraseIds.put(keyphraseId, this);
+ }
+ return mSession.startRecognition(
+ keyphraseId, soundModel, callback, recognitionConfig);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
- final long caller = Binder.clearCallingIdentity();
- try {
- return mSoundTriggerInternal.stopRecognition(keyphraseId, callback);
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
- }
+ @Override
+ public int stopRecognition(int keyphraseId, IRecognitionStatusCallback callback) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
+ }
- @Override
- public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSession.stopRecognition(keyphraseId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
- final long caller = Binder.clearCallingIdentity();
- try {
- return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
- }
+ @Override
+ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
+ }
- @Override
- public int getParameter(int keyphraseId, @ModelParams int modelParam) {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSession.setParameter(keyphraseId, modelParam, value);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
- final long caller = Binder.clearCallingIdentity();
- try {
- return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
- } finally {
- Binder.restoreCallingIdentity(caller);
- }
- }
+ @Override
+ public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
+ }
- @Override
- @Nullable
- public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
- // Allow the call if this is the current voice interaction service.
- synchronized (this) {
- enforceIsCurrentVoiceInteractionService();
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSession.getParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
- final long caller = Binder.clearCallingIdentity();
- try {
- return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
- } finally {
- Binder.restoreCallingIdentity(caller);
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ enforceIsCurrentVoiceInteractionService();
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSession.queryParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
}
- }
- private synchronized void unloadAllKeyphraseModels() {
- for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
+ private int unloadKeyphraseModel(int keyphraseId) {
final long caller = Binder.clearCallingIdentity();
try {
- int status = mSoundTriggerInternal.unloadKeyphraseModel(
- mLoadedKeyphraseIds.valueAt(i));
- if (status != SoundTriggerInternal.STATUS_OK) {
- Slog.w(TAG, "Failed to unload keyphrase " + mLoadedKeyphraseIds.valueAt(i)
- + ":" + status);
- }
+ return mSession.unloadKeyphraseModel(keyphraseId);
} finally {
Binder.restoreCallingIdentity(caller);
}
}
+ }
+
+ private synchronized void unloadAllKeyphraseModels() {
+ for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
+ int id = mLoadedKeyphraseIds.keyAt(i);
+ SoundTriggerSession session = mLoadedKeyphraseIds.valueAt(i);
+ int status = session.unloadKeyphraseModel(id);
+ if (status != SoundTriggerInternal.STATUS_OK) {
+ Slog.w(TAG, "Failed to unload keyphrase " + id + ":" + status);
+ }
+ }
mLoadedKeyphraseIds.clear();
}
@@ -1420,7 +1464,24 @@ public class VoiceInteractionManagerService extends SystemService {
mImpl.dumpLocked(fd, pw, args);
}
}
+
mSoundTriggerInternal.dump(fd, pw, args);
+
+ // Dump all sessions.
+ synchronized (mSessions) {
+ ListIterator<WeakReference<SoundTriggerSession>> iter =
+ mSessions.listIterator();
+ while (iter.hasNext()) {
+ SoundTriggerSession session = iter.next().get();
+ if (session != null) {
+ session.dump(fd, args);
+ } else {
+ // Session is obsolete, now is a good chance to remove it from the list.
+ iter.remove();
+ }
+ }
+ }
+
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 0ff92739f8b6..cfdc5682a117 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -309,7 +309,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
if (!"content".equals(uri.getScheme())) {
return;
}
- long ident = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
// This will throw SecurityException for us.
mUgmInternal.checkGrantUriPermission(srcUid, null,
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
index 8fa0cde0f9cc..150577a21f5a 100644
--- a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
@@ -124,7 +124,7 @@ class ParcelablesTest<T : Parcelable>(private val inputData: InputData<T>) {
data class InputData<T : Parcelable>(val valid: T, val validCopy: T, val validOther: T) {
val kls = valid.javaClass
init {
- assertThat(valid).isNotSameAs(validCopy)
+ assertThat(valid).isNotSameInstanceAs(validCopy)
// Don't use isInstanceOf because of phantom warnings in intellij about Class!
assertThat(validCopy.javaClass).isEqualTo(valid.javaClass)
assertThat(validOther.javaClass).isEqualTo(valid.javaClass)
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 62c889fcc7e0..288f50adf29e 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -976,6 +976,32 @@ public final class Call {
/**
* Gets the verification status for the phone number of an incoming call as identified in
* ATIS-1000082.
+ * <p>
+ * For incoming calls, the number verification status indicates whether the device was
+ * able to verify the authenticity of the calling number using the STIR process outlined
+ * in ATIS-1000082. {@link Connection#VERIFICATION_STATUS_NOT_VERIFIED} indicates that
+ * the network was not able to use STIR to verify the caller's number (i.e. nothing is
+ * known regarding the authenticity of the number.
+ * {@link Connection#VERIFICATION_STATUS_PASSED} indicates that the network was able to
+ * use STIR to verify the caller's number. This indicates that the network has a high
+ * degree of confidence that the incoming call actually originated from the indicated
+ * number. {@link Connection#VERIFICATION_STATUS_FAILED} indicates that the network's
+ * STIR verification did not pass. This indicates that the incoming call may not
+ * actually be from the indicated number. This could occur if, for example, the caller
+ * is using an impersonated phone number.
+ * <p>
+ * A {@link CallScreeningService} can use this information to help determine if an
+ * incoming call is potentially an unwanted call. A verification status of
+ * {@link Connection#VERIFICATION_STATUS_FAILED} indicates that an incoming call may not
+ * actually be from the number indicated on the call (i.e. impersonated number) and that it
+ * should potentially be blocked. Likewise,
+ * {@link Connection#VERIFICATION_STATUS_PASSED} can be used as a positive signal to
+ * help clarify that the incoming call is originating from the indicated number and it
+ * is less likely to be an undesirable call.
+ * <p>
+ * An {@link InCallService} can use this information to provide a visual indicator to the
+ * user regarding the verification status of a call and to help identify calls from
+ * potentially impersonated numbers.
* @return the verification status.
*/
public @Connection.VerificationStatus int getCallerNumberVerificationStatus() {
@@ -2121,7 +2147,13 @@ public final class Call {
/**
* Obtains a list of canned, pre-configured message responses to present to the user as
- * ways of rejecting this {@code Call} using via a text message.
+ * ways of rejecting an incoming {@code Call} using via a text message.
+ * <p>
+ * <em>Note:</em> Since canned responses may be loaded from the file system, they are not
+ * guaranteed to be present when this {@link Call} is first added to the {@link InCallService}
+ * via {@link InCallService#onCallAdded(Call)}. The callback
+ * {@link Call.Callback#onCannedTextResponsesLoaded(Call, List)} will be called when/if canned
+ * responses for the call become available.
*
* @see #reject(boolean, String)
*
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 4d9311c282f7..49f183151e27 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -64,7 +64,7 @@ import com.android.internal.telecom.ICallScreeningService;
* </li>
* </ol>
* <p>
- * <h2>Becoming the {@link CallScreeningService}</h2>
+ * <h2>Becoming the CallScreeningService</h2>
* Telecom will bind to a single app chosen by the user which implements the
* {@link CallScreeningService} API when there are new incoming and outgoing calls.
* <p>
@@ -90,7 +90,27 @@ import com.android.internal.telecom.ICallScreeningService;
* }
* }
* }
+ * }
* </pre>
+ *
+ * <h2>CallScreeningService Lifecycle</h2>
+ *
+ * The framework binds to the {@link CallScreeningService} implemented by the user-chosen app
+ * filling the {@link android.app.role.RoleManager#ROLE_CALL_SCREENING} role when incoming calls are
+ * received (prior to ringing) and when outgoing calls are placed. The platform calls the
+ * {@link #onScreenCall(Call.Details)} method to provide your service with details about the call.
+ * <p>
+ * For incoming calls, the {@link CallScreeningService} must call
+ * {@link #respondToCall(Call.Details, CallResponse)} within 5 seconds of being bound to indicate to
+ * the platform whether the call should be blocked or not. Your app must do this even if it is
+ * primarily performing caller ID operations and not screening calls. It is important to perform
+ * screening operations in a timely matter as the user's device will not begin ringing until the
+ * response is received (or the timeout is hit). A {@link CallScreeningService} may choose to
+ * perform local database lookups to help determine if a call should be screened or not; care should
+ * be taken to ensure the timeout is not repeatedly hit, causing delays in the incoming call flow.
+ * <p>
+ * If your app provides a caller ID experience, it should launch an activity to show the caller ID
+ * information from {@link #onScreenCall(Call.Details)}.
*/
public abstract class CallScreeningService extends Service {
/**
@@ -339,7 +359,7 @@ public abstract class CallScreeningService extends Service {
}
/**
- * Called when a new incoming or outgoing call is added which is not in the user's contact list.
+ * Called when a new incoming or outgoing call is added.
* <p>
* A {@link CallScreeningService} must indicate whether an incoming call is allowed or not by
* calling
@@ -347,21 +367,32 @@ public abstract class CallScreeningService extends Service {
* Your app can tell if a call is an incoming call by checking to see if
* {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
* <p>
- * Note: The {@link Call.Details} instance provided to a call screening service will only have
- * the following properties set. The rest of the {@link Call.Details} properties will be set to
- * their default value or {@code null}.
+ * <em>Note:</em> A {@link CallScreeningService} must respond to a call within 5 seconds. After
+ * this time, the framework will unbind from the {@link CallScreeningService} and ignore its
+ * response.
+ * <p>
+ * <em>Note:</em> The {@link Call.Details} instance provided to a call screening service will
+ * only have the following properties set. The rest of the {@link Call.Details} properties will
+ * be set to their default value or {@code null}.
* <ul>
* <li>{@link Call.Details#getCallDirection()}</li>
+ * <li>{@link Call.Details#getCallerNumberVerificationStatus()}</li>
* <li>{@link Call.Details#getConnectTimeMillis()}</li>
* <li>{@link Call.Details#getCreationTimeMillis()}</li>
* <li>{@link Call.Details#getHandle()}</li>
- * <li>{@link Call.Details#getHandlePresentation()}</li>
* </ul>
* <p>
* Only calls where the {@link Call.Details#getHandle() handle} {@link Uri#getScheme() scheme}
* is {@link PhoneAccount#SCHEME_TEL} are passed for call
* screening. Further, only calls which are not in the user's contacts are passed for
- * screening. For outgoing calls, no post-dial digits are passed.
+ * screening, unless the {@link CallScreeningService} has been granted
+ * {@link Manifest.permission#READ_CONTACTS} permission by the user. For outgoing calls, no
+ * post-dial digits are passed.
+ * <p>
+ * Calls with a {@link Call.Details#getHandlePresentation()} of
+ * {@link TelecomManager#PRESENTATION_RESTRICTED}, {@link TelecomManager#PRESENTATION_UNKNOWN}
+ * or {@link TelecomManager#PRESENTATION_PAYPHONE} presentation are not provided to the
+ * {@link CallScreeningService}.
*
* @param callDetails Information about a new call, see {@link Call.Details}.
*/
@@ -376,6 +407,13 @@ public abstract class CallScreeningService extends Service {
* <p>
* Calls to this method are ignored unless the {@link Call.Details#getCallDirection()} is
* {@link Call.Details#DIRECTION_INCOMING}.
+ * <p>
+ * For incoming calls, a {@link CallScreeningService} MUST call this method within 5 seconds of
+ * {@link #onScreenCall(Call.Details)} being invoked by the platform.
+ * <p>
+ * Calls which are blocked/rejected will be logged to the system call log with a call type of
+ * {@link android.provider.CallLog.Calls#BLOCKED_TYPE} and
+ * {@link android.provider.CallLog.Calls#BLOCK_REASON_CALL_SCREENING_SERVICE} block reason.
*
* @param callDetails The call to allow.
* <p>
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index 6ff24ace59aa..b332a97d4a25 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -407,7 +407,8 @@ public class CallerInfo {
// Change the callerInfo number ONLY if it is an emergency number
// or if it is the voicemail number. If it is either, take a
// shortcut and skip the query.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm.isEmergencyNumber(number)) {
return new CallerInfo().markAsEmergency(context);
} else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
return new CallerInfo().markAsVoiceMail(context, subId);
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 4a81a8eea5cf..a9e1a8fc1952 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -34,6 +34,7 @@ import android.os.UserManager;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -481,7 +482,8 @@ public class CallerInfoAsyncQuery {
cw.subId = subId;
// check to see if these are recognized numbers, and use shortcuts if we can.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm.isEmergencyNumber(number)) {
cw.event = EVENT_EMERGENCY_NUMBER;
} else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java
index 5b806a6e57da..1c070748400d 100644
--- a/telecomm/java/android/telecom/DefaultDialerManager.java
+++ b/telecomm/java/android/telecom/DefaultDialerManager.java
@@ -74,7 +74,7 @@ public class DefaultDialerManager {
* */
public static boolean setDefaultDialerApplication(Context context, String packageName,
int user) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
CompletableFuture<Void> future = new CompletableFuture<>();
Consumer<Boolean> callback = successful -> {
@@ -128,7 +128,7 @@ public class DefaultDialerManager {
* @hide
* */
public static String getDefaultDialerApplication(Context context, int user) {
- long identity = Binder.clearCallingIdentity();
+ final long identity = Binder.clearCallingIdentity();
try {
return CollectionUtils.firstOrNull(context.getSystemService(RoleManager.class)
.getRoleHoldersAsUser(RoleManager.ROLE_DIALER, UserHandle.of(user)));
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index f7fe1ba1f998..1472a4ac27bc 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,7 +16,6 @@
package android.telecom;
-import android.annotation.SystemApi;
import android.media.ToneGenerator;
import android.os.Parcel;
import android.os.Parcelable;
@@ -97,10 +96,7 @@ public final class DisconnectCause implements Parcelable {
*
* This reason code is only used for communication between a {@link ConnectionService} and
* Telecom and should not be surfaced to the user.
- *
- * @hide
*/
- @SystemApi
public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
/**
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index c20e5ad8ce7c..866e17148a1a 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -28,6 +28,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -48,12 +49,20 @@ import java.util.Objects;
public final class PhoneAccount implements Parcelable {
/**
- * String extra which determines the order in which {@link PhoneAccount}s are sorted
+ * Integer extra which determines the order in which {@link PhoneAccount}s are sorted
*
* This is an extras key set via {@link Builder#setExtras} which determines the order in which
* {@link PhoneAccount}s from the same {@link ConnectionService} are sorted. The accounts
- * are sorted by this key via standard lexicographical order, and this ordering is used to
+ * are sorted in ascending order by this key, and this ordering is used to
* determine priority when a call can be placed via multiple accounts.
+ *
+ * When multiple {@link PhoneAccount}s are supplied with the same sort order key, no ordering is
+ * guaranteed between those {@link PhoneAccount}s. Additionally, no ordering is guaranteed
+ * between {@link PhoneAccount}s that do not supply this extra, and all such accounts
+ * will be sorted after the accounts that do supply this extra.
+ *
+ * An example of a sort order key is slot index (see {@link TelephonyManager#getSlotIndex()}),
+ * which is the one used by the cell Telephony stack.
* @hide
*/
@SystemApi
diff --git a/telephony/api/system-current.txt b/telephony/api/system-current.txt
index 9269f8702d18..fc2ef2137c7b 100644
--- a/telephony/api/system-current.txt
+++ b/telephony/api/system-current.txt
@@ -27,6 +27,22 @@ package android.telephony {
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
+ public final class CallForwardingInfo implements android.os.Parcelable {
+ ctor public CallForwardingInfo(boolean, int, @Nullable String, int);
+ method public int describeContents();
+ method @Nullable public String getNumber();
+ method public int getReason();
+ method public int getTimeoutSeconds();
+ method public boolean isEnabled();
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;
+ field public static final int REASON_ALL = 4; // 0x4
+ field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5
+ field public static final int REASON_BUSY = 1; // 0x1
+ field public static final int REASON_NOT_REACHABLE = 3; // 0x3
+ field public static final int REASON_NO_REPLY = 2; // 0x2
+ field public static final int REASON_UNCONDITIONAL = 0; // 0x0
+ }
+
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
@@ -211,6 +227,25 @@ package android.telephony {
field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
}
+ public final class ModemActivityInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.ModemActivityInfo getDelta(@NonNull android.telephony.ModemActivityInfo);
+ method public long getIdleTimeMillis();
+ method public static int getNumTxPowerLevels();
+ method public long getReceiveTimeMillis();
+ method public long getSleepTimeMillis();
+ method public long getTimestampMillis();
+ method public long getTransmitDurationMillisAtPowerLevel(int);
+ method @NonNull public android.util.Range<java.lang.Integer> getTransmitPowerRange(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+ field public static final int TX_POWER_LEVEL_0 = 0; // 0x0
+ field public static final int TX_POWER_LEVEL_1 = 1; // 0x1
+ field public static final int TX_POWER_LEVEL_2 = 2; // 0x2
+ field public static final int TX_POWER_LEVEL_3 = 3; // 0x3
+ field public static final int TX_POWER_LEVEL_4 = 4; // 0x4
+ }
+
public final class NetworkRegistrationInfo implements android.os.Parcelable {
method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
method public int getRegistrationState();
@@ -569,7 +604,6 @@ package android.telephony {
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
@@ -656,6 +690,8 @@ package android.telephony {
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getAllowedNetworkTypes();
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int);
@@ -709,6 +745,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean isIccLockEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -732,11 +769,14 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallForwarding(@NonNull android.telephony.CallForwardingInfo, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
@@ -769,6 +809,10 @@ package android.telephony {
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
+ field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
+ field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
+ field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
+ field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -783,6 +827,8 @@ package android.telephony {
field public static final int INVALID_EMERGENCY_NUMBER_DB_VERSION = -1; // 0xffffffff
field public static final int KEY_TYPE_EPDG = 1; // 0x1
field public static final int KEY_TYPE_WLAN = 2; // 0x2
+ field public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1; // 0x1
+ field public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2; // 0x2
field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L
field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L
field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L
@@ -823,6 +869,15 @@ package android.telephony {
field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0
}
+ public static interface TelephonyManager.CallForwardingInfoCallback {
+ method public void onCallForwardingInfoAvailable(@NonNull android.telephony.CallForwardingInfo);
+ method public void onError(int);
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 2; // 0x2
+ field public static final int RESULT_ERROR_NOT_SUPPORTED = 3; // 0x3
+ field public static final int RESULT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
public final class UiccAccessRule implements android.os.Parcelable {
ctor public UiccAccessRule(byte[], @Nullable String, long);
method public int describeContents();
@@ -900,11 +955,11 @@ package android.telephony.data {
method public int getSuggestedRetryTime();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR;
- field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2; // 0x2
- field public static final int HANDOVER_FAILURE_MODE_LEGACY = 1; // 0x1
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3; // 0x3
- field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4; // 0x4
- field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1; // 0x1
+ field public static final int HANDOVER_FAILURE_MODE_LEGACY = 0; // 0x0
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2; // 0x2
+ field public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3; // 0x3
+ field public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1; // 0xffffffff
field public static final int LINK_STATUS_ACTIVE = 2; // 0x2
field public static final int LINK_STATUS_DORMANT = 1; // 0x1
field public static final int LINK_STATUS_INACTIVE = 0; // 0x0
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index 502bfa3749eb..85d59a216f25 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -377,7 +377,7 @@ public final class LocationAccessPolicy {
}
private static boolean isCurrentProfile(@NonNull Context context, int uid) {
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
if (UserHandle.getUserHandleForUid(uid).getIdentifier()
== ActivityManager.getCurrentUser()) {
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index b35b3236afc6..c3cd01738b96 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -524,7 +524,7 @@ public final class SmsApplication {
}
private static String getDefaultSmsPackage(Context context, int userId) {
- return context.getSystemService(RoleManager.class).getDefaultSmsPackage(userId);
+ return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId);
}
/**
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 7736473feafb..b8ca3267cf9e 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -96,7 +96,7 @@ public final class TelephonyUtils {
*/
public static void runWithCleanCallingIdentity(
@NonNull Runnable action) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
action.run();
} finally {
@@ -115,7 +115,7 @@ public final class TelephonyUtils {
*/
public static <T> T runWithCleanCallingIdentity(
@NonNull Supplier<T> action) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
return action.get();
} finally {
diff --git a/telephony/java/android/telephony/BinderCacheManager.java b/telephony/java/android/telephony/BinderCacheManager.java
new file mode 100644
index 000000000000..0d3e2fe7591c
--- /dev/null
+++ b/telephony/java/android/telephony/BinderCacheManager.java
@@ -0,0 +1,197 @@
+/*
+ * 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.telephony;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Keeps track of the connection to a Binder node, refreshes the cache if the node dies, and lets
+ * interested parties register listeners on the node to be notified when the node has died via the
+ * registered {@link Runnable}.
+ * @param <T> The IInterface representing the Binder type that this manager will be managing the
+ * cache of.
+ * @hide
+ */
+public class BinderCacheManager<T extends IInterface> {
+
+ /**
+ * Factory class for creating new IInterfaces in the case that {@link #getBinder()} is
+ * called and there is no active binder available.
+ * @param <T> The IInterface that should be cached and returned to the caller when
+ * {@link #getBinder()} is called until the Binder node dies.
+ */
+ public interface BinderInterfaceFactory<T> {
+ /**
+ * @return A new instance of the Binder node, which will be cached until it dies.
+ */
+ T create();
+ }
+
+ /**
+ * Tracks the cached Binder node as well as the listeners that were associated with that
+ * Binder node during its lifetime. If the Binder node dies, the listeners will be called and
+ * then this tracker will be unlinked and cleaned up.
+ */
+ private class BinderDeathTracker implements IBinder.DeathRecipient {
+
+ private final T mConnection;
+ private final HashMap<Object, Runnable> mListeners = new HashMap<>();
+
+ /**
+ * Create a tracker to cache the Binder node and add the ability to listen for the cached
+ * interface's death.
+ */
+ BinderDeathTracker(@NonNull T connection) {
+ mConnection = connection;
+ try {
+ mConnection.asBinder().linkToDeath(this, 0 /*flags*/);
+ } catch (RemoteException e) {
+ // isAlive will return false.
+ }
+ }
+
+ public boolean addListener(Object key, Runnable r) {
+ synchronized (mListeners) {
+ if (!isAlive()) return false;
+ mListeners.put(key, r);
+ return true;
+ }
+ }
+
+ public void removeListener(Object runnableKey) {
+ synchronized (mListeners) {
+ mListeners.remove(runnableKey);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ ArrayList<Runnable> listeners;
+ synchronized (mListeners) {
+ listeners = new ArrayList<>(mListeners.values());
+ mListeners.clear();
+ try {
+ mConnection.asBinder().unlinkToDeath(this, 0 /*flags*/);
+ } catch (NoSuchElementException e) {
+ // No need to worry about this, this means the death recipient was never linked.
+ }
+ }
+ listeners.forEach(Runnable::run);
+ }
+
+ /**
+ * @return The cached Binder.
+ */
+ public T getConnection() {
+ return mConnection;
+ }
+
+ /**
+ * @return true if the cached Binder is alive at the time of calling, false otherwise.
+ */
+ public boolean isAlive() {
+ return mConnection.asBinder().isBinderAlive();
+ }
+ }
+
+ private final BinderInterfaceFactory<T> mBinderInterfaceFactory;
+ private final AtomicReference<BinderDeathTracker> mCachedConnection;
+
+ /**
+ * Create a new instance, which manages a cached IInterface and creates new ones using the
+ * provided factory when the cached IInterface dies.
+ * @param factory The factory used to create new Instances of the cached IInterface when it
+ * dies.
+ */
+ public BinderCacheManager(BinderInterfaceFactory<T> factory) {
+ mBinderInterfaceFactory = factory;
+ mCachedConnection = new AtomicReference<>();
+ }
+
+ /**
+ * Get the binder node connection and add a Runnable to be run if this Binder dies. Once this
+ * Runnable is run, the Runnable itself is discarded and must be added again.
+ * <p>
+ * Note: There should be no assumptions here as to which Thread this Runnable is called on. If
+ * the Runnable should be called on a specific thread, it should be up to the caller to handle
+ * that in the runnable implementation.
+ * @param runnableKey The Key associated with this runnable so that it can be removed later
+ * using {@link #removeRunnable(Object)} if needed.
+ * @param deadRunnable The runnable that will be run if the cached Binder node dies.
+ * @return T if the runnable was added or {@code null} if the connection is not alive right now
+ * and the associated runnable was never added.
+ */
+ public T listenOnBinder(Object runnableKey, Runnable deadRunnable) {
+ if (runnableKey == null || deadRunnable == null) return null;
+ BinderDeathTracker tracker = getTracker();
+ if (tracker == null) return null;
+
+ boolean addSucceeded = tracker.addListener(runnableKey, deadRunnable);
+ return addSucceeded ? tracker.getConnection() : null;
+ }
+
+ /**
+ * @return The cached Binder node. May return null if the requested Binder node is not currently
+ * available.
+ */
+ public T getBinder() {
+ BinderDeathTracker tracker = getTracker();
+ return (tracker != null) ? tracker.getConnection() : null;
+ }
+
+ /**
+ * Removes a previously registered runnable associated with the returned cached Binder node
+ * using the key it was registered with in {@link #listenOnBinder} if the runnable still exists.
+ * @param runnableKey The key that was used to register the Runnable earlier.
+ * @return The cached Binder node that the runnable used to registered to or null if the cached
+ * Binder node is not alive anymore.
+ */
+ public T removeRunnable(Object runnableKey) {
+ if (runnableKey == null) return null;
+ BinderDeathTracker tracker = getTracker();
+ if (tracker == null) return null;
+ tracker.removeListener(runnableKey);
+ return tracker.getConnection();
+ }
+
+ /**
+ * @return The BinderDeathTracker container, which contains the cached IInterface instance or
+ * null if it is not available right now.
+ */
+ private BinderDeathTracker getTracker() {
+ return mCachedConnection.updateAndGet((oldVal) -> {
+ BinderDeathTracker tracker = oldVal;
+ // Update cache if no longer alive. BinderDied will eventually be called on the tracker,
+ // which will call listeners & clean up.
+ if (tracker == null || !tracker.isAlive()) {
+ T binder = mBinderInterfaceFactory.create();
+ tracker = (binder != null) ? new BinderDeathTracker(binder) : null;
+
+ }
+ return (tracker != null && tracker.isAlive()) ? tracker : null;
+ });
+ }
+
+}
diff --git a/telephony/java/android/telephony/CallForwardingInfo.java b/telephony/java/android/telephony/CallForwardingInfo.java
index 7e777fae46eb..6ae6d002d990 100644
--- a/telephony/java/android/telephony/CallForwardingInfo.java
+++ b/telephony/java/android/telephony/CallForwardingInfo.java
@@ -20,6 +20,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,50 +34,14 @@ import java.util.Objects;
* Defines the call forwarding information.
* @hide
*/
+@SystemApi
public final class CallForwardingInfo implements Parcelable {
private static final String TAG = "CallForwardingInfo";
/**
- * Indicates the call forwarding status is inactive.
- *
- * @hide
- */
- public static final int STATUS_INACTIVE = 0;
-
- /**
- * Indicates the call forwarding status is active.
- *
- * @hide
- */
- public static final int STATUS_ACTIVE = 1;
-
- /**
- * Indicates the call forwarding could not be enabled because the recipient is not on
- * Fixed Dialing Number (FDN) list.
- *
- * @hide
- */
- public static final int STATUS_FDN_CHECK_FAILURE = 2;
-
- /**
- * Indicates the call forwarding status is with an unknown error.
- *
- * @hide
- */
- public static final int STATUS_UNKNOWN_ERROR = 3;
-
- /**
- * Indicates the call forwarding is not supported (e.g. called via CDMA).
- *
- * @hide
- */
- public static final int STATUS_NOT_SUPPORTED = 4;
-
- /**
- * Indicates the call forwarding reason is "unconditional".
+ * Indicates that call forwarding reason is "unconditional".
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_UNCONDITIONAL = 0;
@@ -84,7 +49,6 @@ public final class CallForwardingInfo implements Parcelable {
* Indicates the call forwarding status is "busy".
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_BUSY = 1;
@@ -92,7 +56,6 @@ public final class CallForwardingInfo implements Parcelable {
* Indicates the call forwarding reason is "no reply".
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_NO_REPLY = 2;
@@ -100,7 +63,6 @@ public final class CallForwardingInfo implements Parcelable {
* Indicates the call forwarding reason is "not reachable".
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_NOT_REACHABLE = 3;
@@ -109,7 +71,6 @@ public final class CallForwardingInfo implements Parcelable {
* simultaneously (unconditional, busy, no reply, and not reachable).
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_ALL = 4;
@@ -118,26 +79,12 @@ public final class CallForwardingInfo implements Parcelable {
* forwarding reasons simultaneously (busy, no reply, and not reachable).
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10 - 7.11 Call forwarding number
* and conditions +CCFC
- * @hide
*/
public static final int REASON_ALL_CONDITIONAL = 5;
/**
- * Call forwarding function status
- */
- @IntDef(prefix = { "STATUS_" }, value = {
- STATUS_ACTIVE,
- STATUS_INACTIVE,
- STATUS_UNKNOWN_ERROR,
- STATUS_NOT_SUPPORTED,
- STATUS_FDN_CHECK_FAILURE
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CallForwardingStatus {
- }
-
- /**
* Call forwarding reason types
+ * @hide
*/
@IntDef(flag = true, prefix = { "REASON_" }, value = {
REASON_UNCONDITIONAL,
@@ -152,9 +99,9 @@ public final class CallForwardingInfo implements Parcelable {
}
/**
- * The call forwarding status.
+ * Whether call forwarding is enabled for this reason.
*/
- private int mStatus;
+ private boolean mEnabled;
/**
* The call forwarding reason indicates the condition under which calls will be forwarded.
@@ -178,39 +125,35 @@ public final class CallForwardingInfo implements Parcelable {
/**
* Construct a CallForwardingInfo.
*
- * @param status the call forwarding status
+ * @param enabled Whether to enable call forwarding for the reason specified
+ * in {@link #getReason()}.
* @param reason the call forwarding reason
* @param number the phone number to which calls will be forwarded
* @param timeSeconds the timeout (in seconds) before the forwarding is attempted
- * @hide
*/
- public CallForwardingInfo(@CallForwardingStatus int status, @CallForwardingReason int reason,
+ public CallForwardingInfo(boolean enabled, @CallForwardingReason int reason,
@Nullable String number, int timeSeconds) {
- mStatus = status;
+ mEnabled = enabled;
mReason = reason;
mNumber = number;
mTimeSeconds = timeSeconds;
}
/**
- * Returns the call forwarding status.
+ * Whether call forwarding is enabled for the reason from {@link #getReason()}.
*
- * @return the call forwarding status.
- *
- * @hide
+ * @return {@code true} if enabled, {@code false} otherwise.
*/
- public @CallForwardingStatus int getStatus() {
- return mStatus;
+ public boolean isEnabled() {
+ return mEnabled;
}
/**
* Returns the call forwarding reason. The call forwarding reason indicates the condition
- * under which calls will be forwarded. For example, {@link #REASON_NO_REPLY} indicates
- * that calls will be forward to {@link #getNumber()} when the user fails to answer the call.
+ * under which calls will be forwarded. For example, {@link #REASON_NO_REPLY} indicates
+ * that calls will be forwarded when the user fails to answer the call.
*
* @return the call forwarding reason.
- *
- * @hide
*/
public @CallForwardingReason int getReason() {
return mReason;
@@ -220,9 +163,7 @@ public final class CallForwardingInfo implements Parcelable {
* Returns the phone number to which calls will be forwarded.
*
* @return the number calls will be forwarded to, or {@code null} if call forwarding
- * is being disabled.
- *
- * @hide
+ * is disabled.
*/
@Nullable
public String getNumber() {
@@ -230,16 +171,14 @@ public final class CallForwardingInfo implements Parcelable {
}
/**
- * Gets the timeout (in seconds) before the forwarding is attempted. For example,
+ * Gets the timeout (in seconds) before forwarding is attempted. For example,
* if {@link #REASON_NO_REPLY} is the call forwarding reason, the device will wait this
- * duration of time before forwarding the call to {@link #getNumber()}.
+ * duration of time before forwarding the call to the number returned by {@link #getNumber()}.
*
* Reference: 3GPP TS 27.007 version 10.3.0 Release 10
* 7.11 Call forwarding number and conditions +CCFC
*
* @return the timeout (in seconds) before the forwarding is attempted.
- *
- * @hide
*/
@SuppressLint("MethodNameUnits")
public int getTimeoutSeconds() {
@@ -257,14 +196,14 @@ public final class CallForwardingInfo implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mNumber);
- out.writeInt(mStatus);
+ out.writeBoolean(mEnabled);
out.writeInt(mReason);
out.writeInt(mTimeSeconds);
}
private CallForwardingInfo(Parcel in) {
mNumber = in.readString();
- mStatus = in.readInt();
+ mEnabled = in.readBoolean();
mReason = in.readInt();
mTimeSeconds = in.readInt();
}
@@ -281,7 +220,7 @@ public final class CallForwardingInfo implements Parcelable {
}
CallForwardingInfo other = (CallForwardingInfo) o;
- return mStatus == other.mStatus
+ return mEnabled == other.mEnabled
&& mNumber == other.mNumber
&& mReason == other.mReason
&& mTimeSeconds == other.mTimeSeconds;
@@ -292,7 +231,7 @@ public final class CallForwardingInfo implements Parcelable {
*/
@Override
public int hashCode() {
- return Objects.hash(mStatus, mNumber, mReason, mTimeSeconds);
+ return Objects.hash(mEnabled, mNumber, mReason, mTimeSeconds);
}
public static final @NonNull Parcelable.Creator<CallForwardingInfo> CREATOR =
@@ -313,7 +252,7 @@ public final class CallForwardingInfo implements Parcelable {
*/
@Override
public String toString() {
- return "[CallForwardingInfo: status=" + mStatus
+ return "[CallForwardingInfo: enabled=" + mEnabled
+ ", reason= " + mReason
+ ", timeSec= " + mTimeSeconds + " seconds"
+ ", number=" + Rlog.pii(TAG, mNumber) + "]";
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 496c0f199c7e..ae882850692a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -68,7 +68,7 @@ public class CarrierConfigManager {
SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX;
/**
- * Service class flag if not specify a service class.
+ * Service class flag if no specific service class is specified.
* Reference: 3GPP TS 27.007 Section 7.4 Facility lock +CLCK
*/
public static final int SERVICE_CLASS_NONE = ImsSsData.SERVICE_CLASS_NONE;
@@ -79,6 +79,30 @@ public class CarrierConfigManager {
*/
public static final int SERVICE_CLASS_VOICE = ImsSsData.SERVICE_CLASS_VOICE;
+ /**
+ * Only send USSD over IMS while CS is out of service, otherwise send USSD over CS.
+ * {@link #KEY_CARRIER_USSD_METHOD_INT}
+ */
+ public static final int USSD_OVER_CS_PREFERRED = 0;
+
+ /**
+ * Send USSD over IMS or CS while IMS is out of service or silent redial over CS if needed.
+ * {@link #KEY_CARRIER_USSD_METHOD_INT}
+ */
+ public static final int USSD_OVER_IMS_PREFERRED = 1;
+
+ /**
+ * Only send USSD over CS.
+ * {@link #KEY_CARRIER_USSD_METHOD_INT}
+ */
+ public static final int USSD_OVER_CS_ONLY = 2;
+
+ /**
+ * Only send USSD over IMS and disallow silent redial over CS.
+ * {@link #KEY_CARRIER_USSD_METHOD_INT}
+ */
+ public static final int USSD_OVER_IMS_ONLY = 3;
+
private final Context mContext;
/**
@@ -584,6 +608,20 @@ public class CarrierConfigManager {
public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
/**
+ * Specify the method of selection for UE sending USSD requests. The default value is
+ * {@link #USSD_OVER_CS_PREFERRED}.
+ * <p> Available options:
+ * <ul>
+ * <li>0: {@link #USSD_OVER_CS_PREFERRED} </li>
+ * <li>1: {@link #USSD_OVER_IMS_PREFERRED} </li>
+ * <li>2: {@link #USSD_OVER_CS_ONLY} </li>
+ * <li>3: {@link #USSD_OVER_IMS_ONLY} </li>
+ * </ul>
+ */
+ public static final String KEY_CARRIER_USSD_METHOD_INT =
+ "carrier_ussd_method_int";
+
+ /**
* Flag specifying whether to show an alert dialog for 5G disable when the user disables VoLTE.
* By default this value is {@code false}.
*
@@ -3974,6 +4012,16 @@ public class CarrierConfigManager {
public static final String KEY_DEFAULT_PREFERRED_APN_NAME_STRING =
"default_preferred_apn_name_string";
+ /**
+ * For Android 11, provide a temporary solution for OEMs to use the lower of the two MTU values
+ * for IPv4 and IPv6 if both are sent.
+ * TODO: remove in later release
+ *
+ * @hide
+ */
+ public static final String KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED =
+ "use_lower_mtu_value_if_both_received";
+
/**
* Flag indicating whether carrier supports multianchor conference.
* In multianchor conference, a participant of a conference can add
@@ -4013,6 +4061,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
+ sDefaults.putInt(KEY_CARRIER_USSD_METHOD_INT, USSD_OVER_CS_PREFERRED);
sDefaults.putBoolean(KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL, false);
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
sDefaults.putBoolean(KEY_ALLOW_MERGING_RTT_CALLS_BOOL, false);
@@ -4538,6 +4587,7 @@ public class CarrierConfigManager {
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
+ sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false);
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_MULTIANCHOR_CONFERENCE, false);
sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0);
}
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index 905f90800305..3923c756033f 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -138,7 +138,11 @@ public final class CellIdentityNr extends CellIdentity {
@NonNull
@Override
public CellLocation asCellLocation() {
- return new GsmCellLocation();
+ GsmCellLocation cl = new GsmCellLocation();
+ int tac = mTac != CellInfo.UNAVAILABLE ? mTac : -1;
+ cl.setLacAndCid(tac, -1);
+ cl.setPsc(0);
+ return cl;
}
@Override
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index cdf735195d61..8f5ec365e65c 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -29,7 +29,10 @@ import com.android.internal.telephony.PhoneConstants;
/**
* Abstract class that represents the location of the device. {@more}
+ *
+ * @deprecated use {@link android.telephony.CellIdentity CellIdentity}.
*/
+@Deprecated
public abstract class CellLocation {
/**
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index debb119c94bc..881d85c73b5d 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -16,8 +16,12 @@
package android.telephony;
+import android.annotation.DurationMillisLong;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -25,46 +29,50 @@ import android.util.Range;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Objects;
/**
- * Reports modem activity information.
+ * Contains information about the modem's activity. May be useful for power stats reporting.
* @hide
*/
+@SystemApi
+@TestApi
public final class ModemActivityInfo implements Parcelable {
+ private static final int TX_POWER_LEVELS = 5;
+
/**
- * Tx(transmit) power level. see power index below
- * <ul>
- * <li> index 0 = tx_power < 0dBm. </li>
- * <li> index 1 = 0dBm < tx_power < 5dBm. </li>
- * <li> index 2 = 5dBm < tx_power < 15dBm. </li>
- * <li> index 3 = 15dBm < tx_power < 20dBm. </li>
- * <li> index 4 = tx_power > 20dBm. </li>
- * </ul>
- */
- public static final int TX_POWER_LEVELS = 5;
- /**
- * Tx(transmit) power level 0: tx_power < 0dBm
+ * Corresponds to transmit power of less than 0dBm.
*/
public static final int TX_POWER_LEVEL_0 = 0;
+
/**
- * Tx(transmit) power level 1: 0dBm < tx_power < 5dBm
+ * Corresponds to transmit power between 0dBm and 5dBm.
*/
public static final int TX_POWER_LEVEL_1 = 1;
+
/**
- * Tx(transmit) power level 2: 5dBm < tx_power < 15dBm
+ * Corresponds to transmit power between 5dBm and 15dBm.
*/
public static final int TX_POWER_LEVEL_2 = 2;
+
/**
- * Tx(transmit) power level 3: 15dBm < tx_power < 20dBm.
+ * Corresponds to transmit power between 15dBm and 20dBm.
*/
public static final int TX_POWER_LEVEL_3 = 3;
+
/**
- * Tx(transmit) power level 4: tx_power > 20dBm
+ * Corresponds to transmit power above 20dBm.
*/
public static final int TX_POWER_LEVEL_4 = 4;
+ /**
+ * The number of transmit power levels. Fixed by HAL definition.
+ */
+ public static int getNumTxPowerLevels() {
+ return TX_POWER_LEVELS;
+ }
+
/** @hide */
@IntDef(prefix = {"TX_POWER_LEVEL_"}, value = {
TX_POWER_LEVEL_0,
@@ -82,34 +90,39 @@ public final class ModemActivityInfo implements Parcelable {
new Range<>(5, 15),
new Range<>(15, 20),
new Range<>(20, Integer.MAX_VALUE)
-
};
private long mTimestamp;
private int mSleepTimeMs;
private int mIdleTimeMs;
- private List<TransmitPower> mTransmitPowerInfo = new ArrayList<>(TX_POWER_LEVELS);
+ private int[] mTxTimeMs;
private int mRxTimeMs;
+ /**
+ * @hide
+ */
+ @TestApi
public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs,
@NonNull int[] txTimeMs, int rxTimeMs) {
+ Objects.requireNonNull(txTimeMs);
+ if (txTimeMs.length != TX_POWER_LEVELS) {
+ throw new IllegalArgumentException("txTimeMs must have length == TX_POWER_LEVELS");
+ }
mTimestamp = timestamp;
mSleepTimeMs = sleepTimeMs;
mIdleTimeMs = idleTimeMs;
- populateTransmitPowerRange(txTimeMs);
+ mTxTimeMs = txTimeMs;
mRxTimeMs = rxTimeMs;
}
- /** helper API to populate tx power range for each bucket **/
- private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) {
- int i = 0;
- for ( ; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
- mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i]));
- }
- // Make sure that mTransmitPowerInfo is fully initialized.
- for ( ; i < TX_POWER_LEVELS; i++) {
- mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], 0));
- }
+ /**
+ * Provided for convenience in manipulation since the API exposes long values but internal
+ * representations are ints.
+ * @hide
+ */
+ public ModemActivityInfo(long timestamp, long sleepTimeMs, long idleTimeMs,
+ @NonNull int[] txTimeMs, long rxTimeMs) {
+ this(timestamp, (int) sleepTimeMs, (int) idleTimeMs, txTimeMs, (int) rxTimeMs);
}
@Override
@@ -118,7 +131,7 @@ public final class ModemActivityInfo implements Parcelable {
+ " mTimestamp=" + mTimestamp
+ " mSleepTimeMs=" + mSleepTimeMs
+ " mIdleTimeMs=" + mIdleTimeMs
- + " mTransmitPowerInfo[]=" + mTransmitPowerInfo.toString()
+ + " mTxTimeMs[]=" + mTxTimeMs
+ " mRxTimeMs=" + mRxTimeMs
+ "}";
}
@@ -129,14 +142,12 @@ public final class ModemActivityInfo implements Parcelable {
public static final @android.annotation.NonNull Parcelable.Creator<ModemActivityInfo> CREATOR =
new Parcelable.Creator<ModemActivityInfo>() {
- public ModemActivityInfo createFromParcel(Parcel in) {
+ public ModemActivityInfo createFromParcel(@NonNull Parcel in) {
long timestamp = in.readLong();
int sleepTimeMs = in.readInt();
int idleTimeMs = in.readInt();
int[] txTimeMs = new int[TX_POWER_LEVELS];
- for (int i = 0; i < TX_POWER_LEVELS; i++) {
- txTimeMs[i] = in.readInt();
- }
+ in.readIntArray(txTimeMs);
int rxTimeMs = in.readInt();
return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs,
txTimeMs, rxTimeMs);
@@ -147,21 +158,25 @@ public final class ModemActivityInfo implements Parcelable {
}
};
- public void writeToParcel(Parcel dest, int flags) {
+ /**
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mTimestamp);
dest.writeInt(mSleepTimeMs);
dest.writeInt(mIdleTimeMs);
- for (int i = 0; i < TX_POWER_LEVELS; i++) {
- dest.writeInt(mTransmitPowerInfo.get(i).getTimeInMillis());
- }
+ dest.writeIntArray(mTxTimeMs);
dest.writeInt(mRxTimeMs);
}
/**
- * @return milliseconds since boot, including mTimeInMillis spent in sleep.
- * @see SystemClock#elapsedRealtime()
+ * Gets the timestamp at which this modem activity info was recorded.
+ *
+ * @return The timestamp, as returned by {@link SystemClock#elapsedRealtime()}, when this
+ * {@link ModemActivityInfo} was recorded.
*/
- public long getTimestamp() {
+ public @ElapsedRealtimeLong long getTimestampMillis() {
return mTimestamp;
}
@@ -171,35 +186,48 @@ public final class ModemActivityInfo implements Parcelable {
}
/**
- * @return an arrayList of {@link TransmitPower} with each element representing the total time where
- * transmitter is awake time (in ms) for a given power range (in dbm).
+ * Gets the amount of time the modem spent transmitting at a certain power level.
*
- * @see #TX_POWER_LEVELS
+ * @param powerLevel The power level to query.
+ * @return The amount of time, in milliseconds, that the modem spent transmitting at the
+ * given power level.
*/
- @NonNull
- public List<TransmitPower> getTransmitPowerInfo() {
- return mTransmitPowerInfo;
+ public @DurationMillisLong long getTransmitDurationMillisAtPowerLevel(
+ @TxPowerLevel int powerLevel) {
+ return mTxTimeMs[powerLevel];
+ }
+
+ /**
+ * Gets the range of transmit powers corresponding to a certain power level.
+ *
+ * @param powerLevel The power level to query
+ * @return A {@link Range} object representing the range of intensities (in dBm) to which this
+ * power level corresponds.
+ */
+ public @NonNull Range<Integer> getTransmitPowerRange(@TxPowerLevel int powerLevel) {
+ return TX_POWER_RANGES[powerLevel];
}
/** @hide */
public void setTransmitTimeMillis(int[] txTimeMs) {
- populateTransmitPowerRange(txTimeMs);
+ mTxTimeMs = Arrays.copyOf(txTimeMs, TX_POWER_LEVELS);
}
- /** @hide */
+ /**
+ * @return The raw array of transmit power durations
+ * @hide
+ */
@NonNull
public int[] getTransmitTimeMillis() {
- int[] transmitTimeMillis = new int[TX_POWER_LEVELS];
- for (int i = 0; i < transmitTimeMillis.length; i++) {
- transmitTimeMillis[i] = mTransmitPowerInfo.get(i).getTimeInMillis();
- }
- return transmitTimeMillis;
+ return mTxTimeMs;
}
/**
- * @return total mTimeInMillis (in ms) when modem is in a low power or sleep state.
+ * Gets the amount of time (in milliseconds) when the modem is in a low power or sleep state.
+ *
+ * @return Time in milliseconds.
*/
- public int getSleepTimeMillis() {
+ public @DurationMillisLong long getSleepTimeMillis() {
return mSleepTimeMs;
}
@@ -209,10 +237,44 @@ public final class ModemActivityInfo implements Parcelable {
}
/**
- * @return total mTimeInMillis (in ms) when modem is awake but neither the transmitter nor receiver are
- * active.
+ * Provided for convenience, since the API surface needs to return longs but internal
+ * representations are ints.
+ * @hide
*/
- public int getIdleTimeMillis() {
+ public void setSleepTimeMillis(long sleepTimeMillis) {
+ mSleepTimeMs = (int) sleepTimeMillis;
+ }
+
+ /**
+ * Computes the difference between this instance of {@link ModemActivityInfo} and another
+ * instance.
+ *
+ * This method should be used to compute the amount of activity that has happened between two
+ * samples of modem activity taken at separate times. The sample passed in as an argument to
+ * this method should be the one that's taken later in time (and therefore has more activity).
+ * @param other The other instance of {@link ModemActivityInfo} to diff against.
+ * @return An instance of {@link ModemActivityInfo} representing the difference in modem
+ * activity.
+ */
+ public @NonNull ModemActivityInfo getDelta(@NonNull ModemActivityInfo other) {
+ int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
+ for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
+ txTimeMs[i] = other.mTxTimeMs[i] - mTxTimeMs[i];
+ }
+ return new ModemActivityInfo(other.getTimestampMillis(),
+ other.getSleepTimeMillis() - getSleepTimeMillis(),
+ other.getIdleTimeMillis() - getIdleTimeMillis(),
+ txTimeMs,
+ other.getReceiveTimeMillis() - getReceiveTimeMillis());
+ }
+
+ /**
+ * Gets the amount of time (in milliseconds) when the modem is awake but neither transmitting
+ * nor receiving.
+ *
+ * @return Time in milliseconds.
+ */
+ public @DurationMillisLong long getIdleTimeMillis() {
return mIdleTimeMs;
}
@@ -222,9 +284,20 @@ public final class ModemActivityInfo implements Parcelable {
}
/**
- * @return rx(receive) mTimeInMillis in ms.
+ * Provided for convenience, since the API surface needs to return longs but internal
+ * representations are ints.
+ * @hide
+ */
+ public void setIdleTimeMillis(long idleTimeMillis) {
+ mIdleTimeMs = (int) idleTimeMillis;
+ }
+
+ /**
+ * Gets the amount of time (in milliseconds) when the modem is awake and receiving data.
+ *
+ * @return Time in milliseconds.
*/
- public int getReceiveTimeMillis() {
+ public @DurationMillisLong long getReceiveTimeMillis() {
return mRxTimeMs;
}
@@ -234,71 +307,56 @@ public final class ModemActivityInfo implements Parcelable {
}
/**
+ * Provided for convenience, since the API surface needs to return longs but internal
+ * representations are ints.
+ * @hide
+ */
+ public void setReceiveTimeMillis(long receiveTimeMillis) {
+ mRxTimeMs = (int) receiveTimeMillis;
+ }
+
+ /**
* Indicates if the modem has reported valid {@link ModemActivityInfo}.
*
* @return {@code true} if this {@link ModemActivityInfo} record is valid,
* {@code false} otherwise.
+ * TODO: remove usages of this outside Telephony by always returning a valid (or null) result
+ * from telephony.
+ * @hide
*/
+ @TestApi
public boolean isValid() {
- for (TransmitPower powerInfo : getTransmitPowerInfo()) {
- if(powerInfo.getTimeInMillis() < 0) {
- return false;
- }
- }
+ boolean isTxPowerValid = Arrays.stream(mTxTimeMs).allMatch((i) -> i >= 0);
- return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
+ return isTxPowerValid && ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0)
&& (getReceiveTimeMillis() >= 0) && !isEmpty());
}
private boolean isEmpty() {
- for (TransmitPower txVal : getTransmitPowerInfo()) {
- if(txVal.getTimeInMillis() != 0) {
- return false;
- }
- }
+ boolean isTxPowerEmpty = mTxTimeMs == null || mTxTimeMs.length == 0
+ || Arrays.stream(mTxTimeMs).allMatch((i) -> i == 0);
- return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
+ return isTxPowerEmpty && ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0)
&& (getReceiveTimeMillis() == 0));
}
- /**
- * Transmit power Information, including the power range in dbm and the total time (in ms) where
- * the transmitter is active/awake for this power range.
- * e.g, range: 0dbm(lower) ~ 5dbm(upper)
- * time: 5ms
- */
- public class TransmitPower {
- private int mTimeInMillis;
- private Range<Integer> mPowerRangeInDbm;
- /** @hide */
- public TransmitPower(@NonNull Range<Integer> range, int time) {
- this.mTimeInMillis = time;
- this.mPowerRangeInDbm = range;
- }
-
- /**
- * @return the total time in ms where the transmitter is active/wake for this power range
- * {@link #getPowerRangeInDbm()}.
- */
- public int getTimeInMillis() {
- return mTimeInMillis;
- }
- /**
- * @return the power range in dbm. e.g, range: 0dbm(lower) ~ 5dbm(upper)
- */
- @NonNull
- public Range<Integer> getPowerRangeInDbm() {
- return mPowerRangeInDbm;
- }
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ModemActivityInfo that = (ModemActivityInfo) o;
+ return mTimestamp == that.mTimestamp
+ && mSleepTimeMs == that.mSleepTimeMs
+ && mIdleTimeMs == that.mIdleTimeMs
+ && mRxTimeMs == that.mRxTimeMs
+ && Arrays.equals(mTxTimeMs, that.mTxTimeMs);
+ }
- @Override
- public String toString() {
- return "TransmitPower{"
- + " mTimeInMillis=" + mTimeInMillis
- + " mPowerRangeInDbm={" + mPowerRangeInDbm.getLower()
- + "," + mPowerRangeInDbm.getUpper()
- + "}}";
- }
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(mTimestamp, mSleepTimeMs, mIdleTimeMs, mRxTimeMs);
+ result = 31 * result + Arrays.hashCode(mTxTimeMs);
+ return result;
}
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 56523136e40b..9f244fd7cd98 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2186,17 +2186,25 @@ public final class SmsManager {
}
/**
- * Gets the total capacity of SMS storage on RUIM and SIM cards
- * <p>
- * This is the number of 176 byte EF-SMS records which can be stored on the RUIM or SIM card.
+ * Gets the total capacity of SMS storage on the SIM card.
+ *
* <p>
- * See 3GPP TS 31.102 - 4.2.25 - EF-SMS for more information
+ * This is the number of 176 byte EF-SMS records which can be stored on the SIM card.
+ * See 3GPP TS 31.102 - 4.2.25 - EF-SMS for more information.
+ * </p>
*
- * @return the total number of SMS records which can be stored on the RUIM or SIM cards.
- * @hide
+ * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
+ * dialog. If this method is called on a device that has multiple active subscriptions, this
+ * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
+ * default subscription is defined, the subscription ID associated with this method will be
+ * INVALID, which will result in the operation being completed on the subscription associated
+ * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the operation
+ * is performed on the correct subscription.
+ * </p>
+ *
+ * @return the total number of SMS records which can be stored on the SIM card.
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public int getSmsCapacityOnIcc() {
int ret = 0;
try {
@@ -2205,7 +2213,7 @@ public final class SmsManager {
ret = iccISms.getSmsCapacityOnIccForSubscriber(getSubscriptionId());
}
} catch (RemoteException ex) {
- //ignore it
+ throw new RuntimeException(ex);
}
return ret;
}
@@ -2601,13 +2609,12 @@ public final class SmsManager {
/**
* Send an MMS message
*
- * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
- * dialog. If this method is called on a device that has multiple active subscriptions, this
- * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
- * default subscription is defined, the subscription ID associated with this message will be
- * INVALID, which will result in the operation being completed on the subscription associated
- * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
- * operation is performed on the correct subscription.
+ * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+ * manager on a multi-SIM device, this operation may fail sending the MMS message because no
+ * suitable default subscription could be found. In this case, if {@code sentIntent} is
+ * non-null, then the {@link PendingIntent} will be sent with an error code
+ * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+ * conditions where this operation may fail.
* </p>
*
* @param context application context
@@ -2626,21 +2633,30 @@ public final class SmsManager {
}
MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
if (m != null) {
- m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
- sentIntent, 0L /* messageId */);
+ resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+ @Override
+ public void onSuccess(int subId) {
+ m.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides,
+ sentIntent, 0L /* messageId */);
+ }
+
+ @Override
+ public void onFailure() {
+ notifySmsError(sentIntent, RESULT_NO_DEFAULT_SMS_APP);
+ }
+ });
}
}
/**
* Download an MMS message from carrier by a given location URL
*
- * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
- * dialog. If this method is called on a device that has multiple active subscriptions, this
- * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
- * default subscription is defined, the subscription ID associated with this message will be
- * INVALID, which will result in the operation being completed on the subscription associated
- * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
- * operation is performed on the correct subscription.
+ * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+ * manager on a multi-SIM device, this operation may fail downloading the MMS message because no
+ * suitable default subscription could be found. In this case, if {@code downloadedIntent} is
+ * non-null, then the {@link PendingIntent} will be sent with an error code
+ * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+ * conditions where this operation may fail.
* </p>
*
* @param context application context
@@ -2663,8 +2679,18 @@ public final class SmsManager {
}
MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
if (m != null) {
- m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
- configOverrides, downloadedIntent, 0L /* messageId */);
+ resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+ @Override
+ public void onSuccess(int subId) {
+ m.downloadMultimediaMessage(subId, locationUrl, contentUri, configOverrides,
+ downloadedIntent, 0L /* messageId */);
+ }
+
+ @Override
+ public void onFailure() {
+ notifySmsError(downloadedIntent, RESULT_NO_DEFAULT_SMS_APP);
+ }
+ });
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 01fe533a453e..50a3d838241d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -85,8 +85,6 @@ import android.telephony.emergency.EmergencyNumber;
import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.aidl.IImsConfig;
-import android.telephony.ims.aidl.IImsMmTelFeature;
-import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -94,11 +92,12 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
+import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.IOns;
import com.android.internal.telephony.IPhoneSubInfo;
@@ -1886,8 +1885,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -1939,8 +1938,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2009,8 +2008,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2087,8 +2086,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2125,8 +2124,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2209,8 +2208,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2246,8 +2245,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -2808,7 +2807,11 @@ public class TelephonyManager {
/** Current network is LTE_CA {@hide} */
@UnsupportedAppUsage
public static final int NETWORK_TYPE_LTE_CA = TelephonyProtoEnums.NETWORK_TYPE_LTE_CA; // = 19.
- /** Current network is NR(New Radio) 5G. */
+ /**
+ * Current network is NR (New Radio) 5G.
+ * This will only be returned for 5G SA.
+ * For 5G NSA, the network type will be {@link #NETWORK_TYPE_LTE}.
+ */
public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20.
private static final @NetworkType int[] NETWORK_TYPES = {
@@ -3724,8 +3727,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -3762,8 +3765,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -4017,8 +4020,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -4056,8 +4059,8 @@ public class TelephonyManager {
*
* <p>Starting with API level 29, persistent device identifiers are guarded behind additional
* restrictions, and apps are recommended to use resettable identifiers (see <a
- * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of
- * the following requirements is met:
+ * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This
+ * method can be invoked if one of the following requirements is met:
* <ul>
* <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
* is a privileged permission that can only be granted to apps preloaded on the device.
@@ -7384,80 +7387,6 @@ public class TelephonyManager {
}
/**
- * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id and MMTel
- * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
- * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
- * @param slotIndex The SIM slot that we are requesting the {@link IImsMmTelFeature} for.
- * @param callback Listener that will send updates to ImsManager when there are updates to
- * ImsServiceController.
- * @return {@link IImsMmTelFeature} interface for the feature specified or {@code null} if
- * it is unavailable.
- * @hide
- */
- public @Nullable IImsMmTelFeature getImsMmTelFeatureAndListen(int slotIndex,
- IImsServiceFeatureCallback callback) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null) {
- return telephony.getMmTelFeatureAndListen(slotIndex, callback);
- }
- } catch (RemoteException e) {
- Rlog.e(TAG, "getImsMmTelFeatureAndListen, RemoteException: "
- + e.getMessage());
- }
- return null;
- }
-
- /**
- * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
- * feature for emergency calling or {@link null} if the service is not available. If an
- * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
- * listener for feature updates.
- * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
- * @param callback Listener that will send updates to ImsManager when there are updates to
- * ImsServiceController.
- * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
- * it is unavailable.
- * @hide
- */
- public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
- IImsServiceFeatureCallback callback) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null) {
- return telephony.getRcsFeatureAndListen(slotIndex, callback);
- }
- } catch (RemoteException e) {
- Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
- + e.getMessage());
- }
- return null;
- }
-
- /**
- * Unregister a IImsServiceFeatureCallback previously associated with an ImsFeature through
- * {@link #getImsMmTelFeatureAndListen(int, IImsServiceFeatureCallback)} or
- * {@link #getImsRcsFeatureAndListen(int, IImsServiceFeatureCallback)}.
- * @param slotIndex The SIM slot associated with the callback.
- * @param featureType The {@link android.telephony.ims.feature.ImsFeature.FeatureType}
- * associated with the callback.
- * @param callback The callback to be unregistered.
- * @hide
- */
- public void unregisterImsFeatureCallback(int slotIndex, int featureType,
- IImsServiceFeatureCallback callback) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null) {
- telephony.unregisterImsFeatureCallback(slotIndex, featureType, callback);
- }
- } catch (RemoteException e) {
- Rlog.e(TAG, "unregisterImsFeatureCallback, RemoteException: "
- + e.getMessage());
- }
- }
-
- /**
* @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
* @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
* @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
@@ -10474,19 +10403,25 @@ public class TelephonyManager {
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
* and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+ *
+ * May return {@code null} when the subscription is inactive or when there was an error
+ * communicating with the phone process.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(allOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_COARSE_LOCATION
})
- public ServiceState getServiceState() {
+ public @Nullable ServiceState getServiceState() {
return getServiceStateForSubscriber(getSubId());
}
/**
* Returns the service state information on specified subscription. Callers require
* either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+ *
+ * May return {@code null} when the subscription is inactive or when there was an error
+ * communicating with the phone process.
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -10513,9 +10448,9 @@ public class TelephonyManager {
* @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the
* voicemail ringtone.
* @return The URI for the ringtone to play when receiving a voicemail from a specific
- * PhoneAccount.
+ * PhoneAccount. May be {@code null} if no ringtone is set.
*/
- public Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
+ public @Nullable Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
try {
ITelephony service = getITelephony();
if (service != null) {
@@ -12885,7 +12820,7 @@ public class TelephonyManager {
* 1) User data is turned on, or
* 2) APN is un-metered for this subscription, or
* 3) APN type is whitelisted. E.g. MMS is whitelisted if
- * {@link #setAlwaysAllowMmsData(boolean)} is turned on.
+ * {@link #MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED} is enabled.
*
* @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}.
* @return whether data is enabled for a apn type.
@@ -13026,246 +12961,402 @@ public class TelephonyManager {
}
/**
- * Gets the voice call forwarding info {@link CallForwardingInfo}, given the call forward
- * reason.
+ * Callback to be used with {@link #getCallForwarding}
+ * @hide
+ */
+ @SystemApi
+ public interface CallForwardingInfoCallback {
+ /**
+ * Indicates that the operation was successful.
+ */
+ int RESULT_SUCCESS = 0;
+
+ /**
+ * Indicates that setting or retrieving the call forwarding info failed with an unknown
+ * error.
+ */
+ int RESULT_ERROR_UNKNOWN = 1;
+
+ /**
+ * Indicates that call forwarding is not enabled because the recipient is not on a
+ * Fixed Dialing Number (FDN) list.
+ */
+ int RESULT_ERROR_FDN_CHECK_FAILURE = 2;
+
+ /**
+ * Indicates that call forwarding is not supported on the network at this time.
+ */
+ int RESULT_ERROR_NOT_SUPPORTED = 3;
+
+ /**
+ * Call forwarding errors
+ * @hide
+ */
+ @IntDef(prefix = { "RESULT_ERROR_" }, value = {
+ RESULT_ERROR_UNKNOWN,
+ RESULT_ERROR_NOT_SUPPORTED,
+ RESULT_ERROR_FDN_CHECK_FAILURE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CallForwardingError{
+ }
+ /**
+ * Called when the call forwarding info is successfully retrieved from the network.
+ * @param info information about how calls are forwarded
+ */
+ void onCallForwardingInfoAvailable(@NonNull CallForwardingInfo info);
+
+ /**
+ * Called when there was an error retrieving the call forwarding information.
+ * @param error
+ */
+ void onError(@CallForwardingError int error);
+ }
+
+ /**
+ * Gets the voice call forwarding info for a given call forwarding reason.
*
- * @param callForwardingReason the call forwarding reasons
+ * This method queries the network for the currently set call forwarding configuration for the
+ * provided call forwarding reason. When the network has provided its response, the result will
+ * be supplied via the provided {@link Executor} on the provided
+ * {@link CallForwardingInfoCallback}.
*
- * @throws IllegalArgumentException if callForwardingReason is not any of
- * {@link CallForwardingInfo.REASON_UNCONDITIONAL}, {@link CallForwardingInfo.REASON_BUSY},
- * {@link CallForwardingInfo.REASON_NO_REPLY}, {@link CallForwardingInfo.REASON_NOT_REACHABLE},
- * {@link CallForwardingInfo.REASON_ALL}, {@link CallForwardingInfo.REASON_ALL_CONDITIONAL}
+ * @param callForwardingReason the call forwarding reason to query.
+ * @param executor The executor on which to execute the callback once the result is ready.
+ * @param callback The callback the results should be delivered on.
*
- * @return {@link CallForwardingInfo} with the status {@link CallForwardingInfo#STATUS_ACTIVE}
- * or {@link CallForwardingInfo#STATUS_INACTIVE} and the target phone number to forward calls
- * to, if it's available. Otherwise, it will return a {@link CallForwardingInfo} with status
- * {@link CallForwardingInfo#STATUS_UNKNOWN_ERROR},
- * {@link CallForwardingInfo#STATUS_NOT_SUPPORTED},
- * or {@link CallForwardingInfo#STATUS_FDN_CHECK_FAILURE} depending on the situation.
+ * @throws IllegalArgumentException if callForwardingReason is not any of
+ * {@link CallForwardingInfo#REASON_UNCONDITIONAL}, {@link CallForwardingInfo#REASON_BUSY},
+ * {@link CallForwardingInfo#REASON_NO_REPLY}, {@link CallForwardingInfo#REASON_NOT_REACHABLE},
+ * {@link CallForwardingInfo#REASON_ALL}, or {@link CallForwardingInfo#REASON_ALL_CONDITIONAL}
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- @NonNull
- public CallForwardingInfo getCallForwarding(@CallForwardingReason int callForwardingReason) {
+ @SystemApi
+ public void getCallForwarding(@CallForwardingReason int callForwardingReason,
+ @NonNull Executor executor, @NonNull CallForwardingInfoCallback callback) {
if (callForwardingReason < CallForwardingInfo.REASON_UNCONDITIONAL
|| callForwardingReason > CallForwardingInfo.REASON_ALL_CONDITIONAL) {
throw new IllegalArgumentException("callForwardingReason is out of range");
}
+
+ ICallForwardingInfoCallback internalCallback = new ICallForwardingInfoCallback.Stub() {
+ @Override
+ public void onCallForwardingInfoAvailable(CallForwardingInfo info) {
+ executor.execute(() ->
+ Binder.withCleanCallingIdentity(() ->
+ callback.onCallForwardingInfoAvailable(info)));
+ }
+
+ @Override
+ public void onError(int error) {
+ executor.execute(() ->
+ Binder.withCleanCallingIdentity(() ->
+ callback.onError(error)));
+ }
+ };
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getCallForwarding(getSubId(), callForwardingReason);
+ telephony.getCallForwarding(getSubId(), callForwardingReason, internalCallback);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "getCallForwarding RemoteException", ex);
- } catch (NullPointerException ex) {
- Rlog.e(TAG, "getCallForwarding NPE", ex);
+ ex.rethrowAsRuntimeException();
}
- return new CallForwardingInfo(
- CallForwardingInfo.STATUS_UNKNOWN_ERROR, 0 /* reason */, null /* number */,
- 0 /* timeout */);
}
/**
- * Sets the voice call forwarding info including status (enable/disable), call forwarding
- * reason, the number to forward, and the timeout before the forwarding is attempted.
+ * Sets voice call forwarding behavior as described by the provided {@link CallForwardingInfo}.
*
- * @param callForwardingInfo {@link CallForwardingInfo} to setup the call forwarding.
- * Enabling if {@link CallForwardingInfo#getStatus()} returns
- * {@link CallForwardingInfo#STATUS_ACTIVE}; Disabling if
- * {@link CallForwardingInfo#getStatus()} returns {@link CallForwardingInfo#STATUS_INACTIVE}.
+ * This method will enable call forwarding if the provided {@link CallForwardingInfo} returns
+ * {@code true} from its {@link CallForwardingInfo#isEnabled()} method, and disables call
+ * forwarding otherwise.
*
- * @throws IllegalArgumentException if any of the following for parameter callForwardingInfo:
- * 0) it is {@code null}.
- * 1) {@link CallForwardingInfo#getStatus()} returns neither
- * {@link CallForwardingInfo#STATUS_ACTIVE} nor {@link CallForwardingInfo#STATUS_INACTIVE}.
- * 2) {@link CallForwardingInfo#getReason()} is not any of
- * {@link CallForwardingInfo.REASON_UNCONDITIONAL}, {@link CallForwardingInfo.REASON_BUSY},
- * {@link CallForwardingInfo.REASON_NO_REPLY}, {@link CallForwardingInfo.REASON_NOT_REACHABLE},
- * {@link CallForwardingInfo.REASON_ALL}, {@link CallForwardingInfo.REASON_ALL_CONDITIONAL}
- * 3) {@link CallForwardingInfo#getNumber()} returns {@code null}.
- * 4) {@link CallForwardingInfo#getTimeoutSeconds()} doesn't return a positive value.
+ * If you wish to be notified about the results of this operation, provide an {@link Executor}
+ * and {@link Consumer<Integer>} to be notified asynchronously when the operation completes.
*
- * @return {@code true} to indicate it was set successfully; {@code false} otherwise.
+ * @param callForwardingInfo Info about whether calls should be forwarded and where they
+ * should be forwarded to.
+ * @param executor The executor on which the listener will be called. Must be non-null if
+ * {@code listener} is non-null.
+ * @param resultListener Asynchronous listener that'll be called when the operation completes.
+ * Called with {@link CallForwardingInfoCallback#RESULT_SUCCESS} if the
+ * operation succeeded and an error code from
+ * {@link CallForwardingInfoCallback} it failed.
*
+ * @throws IllegalArgumentException if any of the following are true for the parameter
+ * callForwardingInfo:
+ * <ul>
+ * <li>it is {@code null}.</li>
+ * <li>{@link CallForwardingInfo#getReason()} is not any of:
+ * <ul>
+ * <li>{@link CallForwardingInfo#REASON_UNCONDITIONAL}</li>
+ * <li>{@link CallForwardingInfo#REASON_BUSY}</li>
+ * <li>{@link CallForwardingInfo#REASON_NO_REPLY}</li>
+ * <li>{@link CallForwardingInfo#REASON_NOT_REACHABLE}</li>
+ * <li>{@link CallForwardingInfo#REASON_ALL}</li>
+ * <li>{@link CallForwardingInfo#REASON_ALL_CONDITIONAL}</li>
+ * </ul>
+ * <li>{@link CallForwardingInfo#getNumber()} returns {@code null} when enabling call
+ * forwarding</li>
+ * <li>{@link CallForwardingInfo#getTimeoutSeconds()} returns a non-positive value when
+ * enabling call forwarding</li>
+ * </ul>
* @hide
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCallForwarding(@NonNull CallForwardingInfo callForwardingInfo) {
+ @SystemApi
+ public void setCallForwarding(@NonNull CallForwardingInfo callForwardingInfo,
+ @Nullable @CallbackExecutor Executor executor,
+ @Nullable @CallForwardingInfoCallback.CallForwardingError
+ Consumer<Integer> resultListener) {
if (callForwardingInfo == null) {
throw new IllegalArgumentException("callForwardingInfo is null");
}
- int callForwardingStatus = callForwardingInfo.getStatus();
- if (callForwardingStatus != CallForwardingInfo.STATUS_ACTIVE
- && callForwardingStatus != CallForwardingInfo.STATUS_INACTIVE) {
- throw new IllegalArgumentException(
- "callForwardingStatus is neither active nor inactive");
- }
int callForwardingReason = callForwardingInfo.getReason();
if (callForwardingReason < CallForwardingInfo.REASON_UNCONDITIONAL
|| callForwardingReason > CallForwardingInfo.REASON_ALL_CONDITIONAL) {
throw new IllegalArgumentException("callForwardingReason is out of range");
}
- if (callForwardingInfo.getNumber() == null) {
- throw new IllegalArgumentException("callForwarding number is null");
+ if (callForwardingInfo.isEnabled()) {
+ if (callForwardingInfo.getNumber() == null) {
+ throw new IllegalArgumentException("callForwarding number is null");
+ }
+ if (callForwardingInfo.getTimeoutSeconds() <= 0) {
+ throw new IllegalArgumentException("callForwarding timeout isn't positive");
+ }
}
- if (callForwardingInfo.getTimeoutSeconds() <= 0) {
- throw new IllegalArgumentException("callForwarding timeout isn't positive");
+ if (resultListener != null) {
+ Objects.requireNonNull(executor);
}
+
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() ->
+ Binder.withCleanCallingIdentity(() -> resultListener.accept(result)));
+ }
+ };
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.setCallForwarding(getSubId(), callForwardingInfo);
+ telephony.setCallForwarding(getSubId(), callForwardingInfo, internalCallback);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "setCallForwarding RemoteException", ex);
+ ex.rethrowAsRuntimeException();
} catch (NullPointerException ex) {
Rlog.e(TAG, "setCallForwarding NPE", ex);
+ throw ex;
}
- return false;
}
/**
- * Indicates the call waiting status is active.
+ * Indicates that call waiting is enabled.
*
* @hide
*/
- public static final int CALL_WAITING_STATUS_ACTIVE = 1;
+ @SystemApi
+ public static final int CALL_WAITING_STATUS_ENABLED = 1;
/**
- * Indicates the call waiting status is inactive.
+ * Indicates that call waiting is disabled.
*
* @hide
*/
- public static final int CALL_WAITING_STATUS_INACTIVE = 2;
+ @SystemApi
+ public static final int CALL_WAITING_STATUS_DISABLED = 2;
/**
- * Indicates the call waiting status is with an unknown error.
+ * Indicates there was an unknown error retrieving the call waiting status.
*
* @hide
*/
+ @SystemApi
public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3;
/**
- * Indicates the call waiting is not supported (e.g. called via CDMA).
+ * Indicates the call waiting is not supported on the current network.
*
* @hide
*/
+ @SystemApi
public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4;
/**
- * Call waiting function status
- *
* @hide
*/
@IntDef(prefix = { "CALL_WAITING_STATUS_" }, value = {
- CALL_WAITING_STATUS_ACTIVE,
- CALL_WAITING_STATUS_INACTIVE,
- CALL_WAITING_STATUS_NOT_SUPPORTED,
- CALL_WAITING_STATUS_UNKNOWN_ERROR
+ CALL_WAITING_STATUS_ENABLED,
+ CALL_WAITING_STATUS_DISABLED,
+ CALL_WAITING_STATUS_UNKNOWN_ERROR,
+ CALL_WAITING_STATUS_NOT_SUPPORTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CallWaitingStatus {
}
/**
- * Gets the status of voice call waiting function. Call waiting function enables the waiting
- * for the incoming call when it reaches the user who is busy to make another call and allows
- * users to decide whether to switch to the incoming call.
+ * Retrieves the call waiting status of this device from the network.
*
- * @return the status of call waiting function.
+ * When call waiting is enabled, an incoming call that arrives when the user is already on
+ * an active call will be held in a waiting state while the user is notified instead of being
+ * rejected with a busy signal.
+ *
+ * @param executor The executor on which the result listener will be called.
+ * @param resultListener A {@link Consumer} that will be called with the result fetched
+ * from the network. The result will be one of:
+ * <ul>
+ * <li>{@link #CALL_WAITING_STATUS_ENABLED}}</li>
+ * <li>{@link #CALL_WAITING_STATUS_DISABLED}}</li>
+ * <li>{@link #CALL_WAITING_STATUS_UNKNOWN_ERROR}}</li>
+ * <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li>
+ * </ul>
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public @CallWaitingStatus int getCallWaitingStatus() {
+ public void getCallWaitingStatus(@NonNull Executor executor,
+ @NonNull @CallWaitingStatus Consumer<Integer> resultListener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(resultListener);
+
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(result)));
+ }
+ };
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getCallWaitingStatus(getSubId());
+ telephony.getCallWaitingStatus(getSubId(), internalCallback);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "getCallWaitingStatus RemoteException", ex);
+ ex.rethrowAsRuntimeException();
} catch (NullPointerException ex) {
Rlog.e(TAG, "getCallWaitingStatus NPE", ex);
+ throw ex;
}
- return CALL_WAITING_STATUS_UNKNOWN_ERROR;
}
/**
- * Sets the status for voice call waiting function. Call waiting function enables the waiting
- * for the incoming call when it reaches the user who is busy to make another call and allows
- * users to decide whether to switch to the incoming call.
+ * Sets the call waiting status of this device with the network.
+ *
+ * If you wish to be notified about the results of this operation, provide an {@link Executor}
+ * and {@link Consumer<Integer>} to be notified asynchronously when the operation completes.
*
- * @param isEnable {@code true} to enable; {@code false} to disable.
- * @return {@code true} to indicate it was set successfully; {@code false} otherwise.
+ * @see #getCallWaitingStatus for a description of the call waiting functionality.
*
+ * @param enabled {@code true} to enable; {@code false} to disable.
+ * @param executor The executor on which the listener will be called. Must be non-null if
+ * {@code listener} is non-null.
+ * @param resultListener Asynchronous listener that'll be called when the operation completes.
+ * Called with the new call waiting status (either
+ * {@link #CALL_WAITING_STATUS_ENABLED} or
+ * {@link #CALL_WAITING_STATUS_DISABLED} if the operation succeeded and
+ * {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or
+ * {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} if it failed.
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCallWaitingStatus(boolean isEnable) {
+ public void setCallWaitingEnabled(boolean enabled, @Nullable Executor executor,
+ @Nullable Consumer<Integer> resultListener) {
+ if (resultListener != null) {
+ Objects.requireNonNull(executor);
+ }
+
+ IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() ->
+ Binder.withCleanCallingIdentity(() -> resultListener.accept(result)));
+ }
+ };
+
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.setCallWaitingStatus(getSubId(), isEnable);
+ telephony.setCallWaitingStatus(getSubId(), enabled, internalCallback);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "setCallWaitingStatus RemoteException", ex);
+ ex.rethrowAsRuntimeException();
} catch (NullPointerException ex) {
Rlog.e(TAG, "setCallWaitingStatus NPE", ex);
+ throw ex;
}
- return false;
}
/**
- * Set allowing mobile data during voice call. This is used for allowing data on the non-default
- * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
- * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
- * non-default data SIM during the life cycle of the voice call.
+ * Controls whether mobile data on the non-default SIM is allowed during a voice call.
*
- * @param allow {@code true} if allowing using data during voice call, {@code false} if
- * disallowed.
+ * This is used for allowing data on the non-default data SIM when a voice call is placed on
+ * the non-default data SIM on DSDS devices. If this policy is disabled, users will not be able
+ * to use mobile data via the non-default data SIM during the call, which may mean no mobile
+ * data at all since some modem implementations disallow mobile data via the default data SIM
+ * during voice calls.
+ * If this policy is enabled, data will be temporarily enabled on the non-default data SIM
+ * during any voice calls.
*
- * @return {@code true} if operation is successful. otherwise {@code false}.
+ * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL = 1;
+
+ /**
+ * Controls whether MMS messages bypass the user-specified "mobile data" toggle.
*
- * @throws SecurityException if the caller doesn't have the permission.
+ * When enabled, requests for connections to the MMS APN will be accepted by telephony even if
+ * the user has turned "mobile data" off on this specific sim card. {@link #isDataEnabledForApn}
+ * will also return true for {@link ApnSetting#TYPE_MMS}.
+ * When disabled, the MMS APN will be governed by the same rules as all other APNs.
*
+ * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setDataAllowedDuringVoiceCall(boolean allow) {
- try {
- ITelephony service = getITelephony();
- if (service != null) {
- return service.setDataAllowedDuringVoiceCall(getSubId(), allow);
- }
- } catch (RemoteException ex) {
- // This could happen if binder process crashes.
- if (!isSystemProcess()) {
- ex.rethrowAsRuntimeException();
- }
- }
- return false;
- }
+ @SystemApi
+ @TestApi
+ public static final int MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED = 2;
/**
- * Check whether data is allowed during voice call. This is used for allowing data on the
- * non-default data SIM. When a voice call is placed on the non-default data SIM on DSDS
- * devices, users will not be able to use mobile data. By calling this API, data will be
- * temporarily enabled on the non-default data SIM during the life cycle of the voice call.
- *
- * @return {@code true} if data is allowed during voice call.
+ * @hide
+ */
+ @IntDef(prefix = { "MOBILE_DATA_POLICY_" }, value = {
+ MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
+ MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MobileDataPolicy { }
+
+ /**
+ * Enables or disables a piece of mobile data policy.
*
- * @throws SecurityException if the caller doesn't have the permission.
+ * Enables or disables the mobile data policy specified in {@code policy}. See the detailed
+ * description of each policy constant for what they do.
*
+ * @param policy The data policy to enable.
+ * @param enabled Whether to enable or disable the policy.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- public boolean isDataAllowedInVoiceCall() {
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setMobileDataPolicyEnabledStatus(@MobileDataPolicy int policy, boolean enabled) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.isDataAllowedInVoiceCall(getSubId());
+ service.setMobileDataPolicyEnabledStatus(getSubId(), policy, enabled);
}
} catch (RemoteException ex) {
// This could happen if binder process crashes.
@@ -13273,27 +13364,26 @@ public class TelephonyManager {
ex.rethrowAsRuntimeException();
}
}
- return false;
}
/**
- * Set whether the specific sim card always allows MMS connection. If true, MMS network
- * request will be accepted by telephony even if user turns "mobile data" off
- * on this specific sim card.
- *
- * @param alwaysAllow whether Mms data is always allowed.
- * @return whether operation is successful.
+ * Fetches the status of a piece of mobile data policy.
*
+ * @param policy The data policy that you want the status for.
+ * @return {@code true} if enabled, {@code false} otherwise.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setAlwaysAllowMmsData(boolean alwaysAllow) {
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isMobileDataPolicyEnabled(@MobileDataPolicy int policy) {
try {
ITelephony service = getITelephony();
if (service != null) {
- return service.setAlwaysAllowMmsData(getSubId(), alwaysAllow);
+ return service.isMobileDataPolicyEnabled(getSubId(), policy);
}
} catch (RemoteException ex) {
+ // This could happen if binder process crashes.
if (!isSystemProcess()) {
ex.rethrowAsRuntimeException();
}
@@ -13591,4 +13681,36 @@ public class TelephonyManager {
return true;
}
}
+
+ /**
+ * Returns a list of the equivalent home PLMNs (EF_EHPLMN) from the USIM app.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @return A list of equivalent home PLMNs. Returns an empty list if EF_EHPLMN is empty or
+ * does not exist on the SIM card.
+ *
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @throws SecurityException if the caller doesn't have the permission.
+ *
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public @NonNull List<String> getEquivalentHomePlmns() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getEquivalentHomePlmns(getSubId(), mContext.getOpPackageName(),
+ getAttributionTag());
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ if (!isSystemProcess()) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ return Collections.emptyList();
+ }
}
diff --git a/telephony/java/android/telephony/cdma/CdmaCellLocation.java b/telephony/java/android/telephony/cdma/CdmaCellLocation.java
index 9bc39a0c6ced..d808cabaaa92 100644
--- a/telephony/java/android/telephony/cdma/CdmaCellLocation.java
+++ b/telephony/java/android/telephony/cdma/CdmaCellLocation.java
@@ -23,7 +23,10 @@ import android.telephony.CellLocation;
/**
* Represents the cell location on a CDMA phone.
+ *
+ * @deprecated use {@link android.telephony.CellIdentity CellIdentity}.
*/
+@Deprecated
public class CdmaCellLocation extends CellLocation {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mBaseStationId = -1;
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 5ead8decdb63..579200e15cca 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -69,6 +69,7 @@ public final class DataCallResponse implements Parcelable {
/** {@hide} */
@IntDef(prefix = "HANDOVER_FAILURE_MODE_", value = {
+ HANDOVER_FAILURE_MODE_UNKNOWN,
HANDOVER_FAILURE_MODE_LEGACY,
HANDOVER_FAILURE_MODE_DO_FALLBACK,
HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER,
@@ -80,33 +81,33 @@ public final class DataCallResponse implements Parcelable {
/**
* Data handover failure mode is unknown.
*/
- public static final int HANDOVER_FAILURE_MODE_UNKNOWN = 0;
+ public static final int HANDOVER_FAILURE_MODE_UNKNOWN = -1;
/**
* Perform fallback to the source data transport on data handover failure using
* the legacy logic, which is fallback if the fail cause is
* {@link DataFailCause#HANDOFF_PREFERENCE_CHANGED}.
*/
- public static final int HANDOVER_FAILURE_MODE_LEGACY = 1;
+ public static final int HANDOVER_FAILURE_MODE_LEGACY = 0;
/**
* Perform fallback to the source data transport on data handover failure.
*/
- public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 2;
+ public static final int HANDOVER_FAILURE_MODE_DO_FALLBACK = 1;
/**
* Do not perform fallback to the source data transport on data handover failure.
- * Frameworks should keep retrying handover by sending
+ * Framework will retry setting up a new data connection by sending
* {@link DataService#REQUEST_REASON_HANDOVER} request to the underlying {@link DataService}.
*/
- public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 3;
+ public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER = 2;
/**
* Do not perform fallback to the source transport on data handover failure.
- * Frameworks should retry setup a new data connection by sending
+ * Framework will retry setting up a new data connection by sending
* {@link DataService#REQUEST_REASON_NORMAL} request to the underlying {@link DataService}.
*/
- public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 4;
+ public static final int HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL = 3;
private final @DataFailureCause int mCause;
private final int mSuggestedRetryTime;
@@ -332,6 +333,7 @@ public final class DataCallResponse implements Parcelable {
.append(" mtu=").append(getMtu())
.append(" mtuV4=").append(getMtuV4())
.append(" mtuV6=").append(getMtuV6())
+ .append(" handoverFailureMode=").append(getHandoverFailureMode())
.append("}");
return sb.toString();
}
@@ -361,14 +363,15 @@ public final class DataCallResponse implements Parcelable {
&& mPcscfAddresses.containsAll(other.mPcscfAddresses)
&& mMtu == other.mMtu
&& mMtuV4 == other.mMtuV4
- && mMtuV6 == other.mMtuV6;
+ && mMtuV6 == other.mMtuV6
+ && mHandoverFailureMode == other.mHandoverFailureMode;
}
@Override
public int hashCode() {
return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
- mMtu, mMtuV4, mMtuV6);
+ mMtu, mMtuV4, mMtuV6, mHandoverFailureMode);
}
@Override
diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java
index 30cea0e6dd59..2eee4ce371a0 100644
--- a/telephony/java/android/telephony/gsm/GsmCellLocation.java
+++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java
@@ -23,7 +23,10 @@ import android.telephony.CellLocation;
/**
* Represents the cell location on a GSM phone.
+ *
+ * @deprecated use {@link android.telephony.CellIdentity CellIdentity}.
*/
+@Deprecated
public class GsmCellLocation extends CellLocation {
private int mLac;
private int mCid;
@@ -55,7 +58,7 @@ public class GsmCellLocation extends CellLocation {
}
/**
- * @return gsm cell id, -1 if unknown, 0xffff max legal value
+ * @return gsm cell id, -1 if unknown or invalid, 0xffff max legal value
*/
public int getCid() {
return mCid;
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index f6c14e67306b..3a0e49e204cb 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -59,6 +59,7 @@ import java.util.function.Consumer;
* manager.
*/
public class ImsMmTelManager implements RegistrationManager {
+ private static final String TAG = "ImsMmTelManager";
/**
* @hide
@@ -160,7 +161,7 @@ public class ImsMmTelManager implements RegistrationManager {
public void onCapabilitiesStatusChanged(int config) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onCapabilitiesStatusChanged(
new MmTelFeature.MmTelCapabilities(config)));
@@ -809,7 +810,7 @@ public class ImsMmTelManager implements RegistrationManager {
}
try {
- getITelephony().isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
+ iTelephony.isMmTelCapabilitySupported(mSubId, new IIntegerConsumer.Stub() {
@Override
public void accept(int result) {
executor.execute(() -> callback.accept(result == 1));
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 94407f1dcd3a..a7586ec4ec18 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -99,7 +99,7 @@ public class ImsRcsManager {
public void onCapabilitiesStatusChanged(int config) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onAvailabilityChanged(
new RcsFeature.RcsImsCapabilities(config)));
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index da7311c08307..8a05bdfc8401 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.LongDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Service;
@@ -41,6 +42,11 @@ import android.util.SparseArray;
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
* ImsService must register the service in their AndroidManifest to be detected by the framework.
@@ -98,6 +104,32 @@ public class ImsService extends Service {
private static final String LOG_TAG = "ImsService";
/**
+ * This ImsService supports the capability to place emergency calls over MMTEL.
+ * @hide This is encoded into the {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, but we will be
+ * adding other capabilities in a central location, so track this capability here as well.
+ */
+ public static final long CAPABILITY_EMERGENCY_OVER_MMTEL = 1 << 0;
+
+ /**
+ * @hide
+ */
+ @LongDef(flag = true,
+ prefix = "CAPABILITY_",
+ value = {
+ CAPABILITY_EMERGENCY_OVER_MMTEL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ImsServiceCapability {}
+
+ /**
+ * Used for logging purposes, see {@link #getCapabilitiesString(long)}
+ * @hide
+ */
+ private static final Map<Long, String> CAPABILITIES_LOG_MAP = new HashMap<Long, String>() {{
+ put(CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL");
+ }};
+
+ /**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
* @hide
*/
@@ -409,4 +441,30 @@ public class ImsService extends Service {
public ImsRegistrationImplBase getRegistration(int slotId) {
return new ImsRegistrationImplBase();
}
+
+ /**
+ * @return A string representation of the ImsService capabilties for logging.
+ * @hide
+ */
+ public static String getCapabilitiesString(@ImsServiceCapability long caps) {
+ StringBuffer result = new StringBuffer();
+ result.append("capabilities={ ");
+ // filter incrementally fills 0s from left to right. This is used to keep filtering out
+ // more bits in the long until the remaining leftmost bits are all zero.
+ long filter = 0xFFFFFFFFFFFFFFFFL;
+ // position of iterator to potentially print capability.
+ long i = 0;
+ while ((caps & filter) != 0 && i <= 63) {
+ long bitToCheck = (1L << i);
+ if ((caps & bitToCheck) != 0) {
+ result.append(CAPABILITIES_LOG_MAP.getOrDefault(bitToCheck, bitToCheck + "?"));
+ result.append(" ");
+ }
+ // shift left by one and fill in another 1 on the leftmost bit.
+ filter <<= 1;
+ i++;
+ }
+ result.append("}");
+ return result.toString();
+ }
} \ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 2a073a1f1d81..3affdf64aae7 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -879,7 +879,7 @@ public class ProvisioningManager {
@Override
public final void onIntConfigChanged(int item, int value) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() ->
mLocalConfigurationCallback.onProvisioningIntChanged(item, value));
@@ -890,7 +890,7 @@ public class ProvisioningManager {
@Override
public final void onStringConfigChanged(int item, String value) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() ->
mLocalConfigurationCallback.onProvisioningStringChanged(item, value));
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index a427d056f915..4606f7d625aa 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -206,7 +206,7 @@ public class RcsUceAdapter {
public void onPublishStateChanged(int publishState) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onChanged(publishState));
} finally {
@@ -322,7 +322,7 @@ public class RcsUceAdapter {
IRcsUceControllerCallback internalCallback = new IRcsUceControllerCallback.Stub() {
@Override
public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
executor.execute(() ->
c.onCapabilitiesReceived(contactCapabilities));
@@ -332,7 +332,7 @@ public class RcsUceAdapter {
}
@Override
public void onError(int errorCode) {
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
executor.execute(() -> c.onError(errorCode));
} finally {
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index e085dec10546..1a78e166932a 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -105,7 +105,7 @@ public interface RegistrationManager {
public void onRegistered(int imsRadioTech) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() ->
mLocalCallback.onRegistered(getAccessType(imsRadioTech)));
@@ -118,7 +118,7 @@ public interface RegistrationManager {
public void onRegistering(int imsRadioTech) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() ->
mLocalCallback.onRegistering(getAccessType(imsRadioTech)));
@@ -131,7 +131,7 @@ public interface RegistrationManager {
public void onDeregistered(ImsReasonInfo info) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onUnregistered(info));
} finally {
@@ -143,7 +143,7 @@ public interface RegistrationManager {
public void onTechnologyChangeFailed(int imsRadioTech, ImsReasonInfo info) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onTechnologyChangeFailed(
getAccessType(imsRadioTech), info));
@@ -155,7 +155,7 @@ public interface RegistrationManager {
public void onSubscriberAssociatedUriChanged(Uri[] uris) {
if (mLocalCallback == null) return;
- long callingIdentity = Binder.clearCallingIdentity();
+ final long callingIdentity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mLocalCallback.onSubscriberAssociatedUriChanged(uris));
} finally {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 7bbe30a444b9..52464703c608 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -27,8 +27,11 @@ import com.android.ims.internal.IImsCallSession;
* See MmTelFeature#Listener for more information.
* {@hide}
*/
-oneway interface IImsMmTelListener {
+ // This interface is not considered oneway because we need to ensure that these operations are
+ // processed by telephony before the control flow returns to the ImsService to perform
+ // operations on the IImsCallSession.
+interface IImsMmTelListener {
void onIncomingCall(IImsCallSession c, in Bundle extras);
void onRejectedCall(in ImsCallProfile callProfile, in ImsReasonInfo reason);
- void onVoiceMessageCountUpdate(int count);
+ oneway void onVoiceMessageCountUpdate(int count);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index 9e461420e126..e01ea9179452 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -22,6 +22,8 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
import android.telephony.ims.aidl.IImsRegistrationCallback;
+import com.android.ims.ImsFeatureContainer;
+import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.IIntegerConsumer;
/**
@@ -50,4 +52,8 @@ interface IImsRcsController {
void setUceSettingEnabled(int subId, boolean isEnabled);
void registerUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
void unregisterUcePublishStateCallback(int subId, IRcsUcePublishStateCallback c);
+
+ // Internal commands that should not be made public
+ void registerRcsFeatureCallback(int slotId, in IImsServiceFeatureCallback callback);
+ void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
}
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadProgressListener.java b/telephony/java/android/telephony/mbms/InternalDownloadProgressListener.java
index 6a135694869c..a413ef8a8738 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadProgressListener.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadProgressListener.java
@@ -43,7 +43,7 @@ public class InternalDownloadProgressListener extends IDownloadProgressListener.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
index ce32477b443b..67539a0ad7ee 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
@@ -40,7 +40,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -59,7 +59,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -78,7 +78,7 @@ public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallbac
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadStatusListener.java b/telephony/java/android/telephony/mbms/InternalDownloadStatusListener.java
index 87163ff8b32c..ce96a8faeb49 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadStatusListener.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadStatusListener.java
@@ -42,7 +42,7 @@ public class InternalDownloadStatusListener extends IDownloadStatusListener.Stub
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java
index c7600b6c7843..5e1f1f170f60 100644
--- a/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalGroupCallCallback.java
@@ -38,7 +38,7 @@ public class InternalGroupCallCallback extends IGroupCallCallback.Stub {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -57,7 +57,7 @@ public class InternalGroupCallCallback extends IGroupCallCallback.Stub {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -76,7 +76,7 @@ public class InternalGroupCallCallback extends IGroupCallCallback.Stub {
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java
index 0b7667ec525c..ca4190c6b4f1 100644
--- a/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalGroupCallSessionCallback.java
@@ -39,7 +39,7 @@ public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -58,7 +58,7 @@ public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -77,7 +77,7 @@ public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -96,7 +96,7 @@ public class InternalGroupCallSessionCallback extends IMbmsGroupCallSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
index 3a4ed08ed954..d62add193e77 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -39,7 +39,7 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -58,7 +58,7 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -77,7 +77,7 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -96,7 +96,7 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -115,7 +115,7 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback.
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
index 2eb280e74106..f4ee4dc069a9 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -40,7 +40,7 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -60,7 +60,7 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
@@ -79,7 +79,7 @@ public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallb
return;
}
- long token = Binder.clearCallingIdentity();
+ final long token = Binder.clearCallingIdentity();
try {
mExecutor.execute(new Runnable() {
@Override
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.aidl b/telephony/java/com/android/ims/ImsFeatureContainer.aidl
new file mode 100644
index 000000000000..9706f20c59ca
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsFeatureContainer.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.ims;
+
+parcelable ImsFeatureContainer; \ No newline at end of file
diff --git a/telephony/java/com/android/ims/ImsFeatureContainer.java b/telephony/java/com/android/ims/ImsFeatureContainer.java
new file mode 100644
index 000000000000..b259679ea1bf
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsFeatureContainer.java
@@ -0,0 +1,172 @@
+/*
+ * 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.ims;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.aidl.IImsConfig;
+import android.telephony.ims.aidl.IImsRegistration;
+import android.telephony.ims.feature.ImsFeature;
+
+import java.util.Objects;
+
+/**
+ * Contains an IBinder linking to the appropriate ImsFeature as well as the associated
+ * interfaces.
+ * @hide
+ */
+public final class ImsFeatureContainer implements Parcelable {
+ /**
+ * ImsFeature that is being tracked.
+ */
+ public final IBinder imsFeature;
+
+ /**
+ * IImsConfig interface that should be associated with the ImsFeature.
+ */
+ public final android.telephony.ims.aidl.IImsConfig imsConfig;
+
+ /**
+ * IImsRegistration interface that should be associated with this ImsFeature.
+ */
+ public final IImsRegistration imsRegistration;
+
+ /**
+ * State of the feature that is being tracked.
+ */
+ private @ImsFeature.ImsState int mState = ImsFeature.STATE_UNAVAILABLE;
+
+ /**
+ * Capabilities of this ImsService.
+ */
+ private @ImsService.ImsServiceCapability long mCapabilities;
+ /**
+ * Contains the ImsFeature IBinder as well as the ImsService interfaces associated with
+ * that feature.
+ * @param iFace IBinder connection to the ImsFeature.
+ * @param iConfig IImsConfig interface associated with the ImsFeature.
+ * @param iReg IImsRegistration interface associated with the ImsFeature
+ * @param initialCaps The initial capabilities that the ImsService supports.
+ */
+ public ImsFeatureContainer(@NonNull IBinder iFace, @NonNull IImsConfig iConfig,
+ @NonNull IImsRegistration iReg, long initialCaps) {
+ imsFeature = iFace;
+ imsConfig = iConfig;
+ imsRegistration = iReg;
+ mCapabilities = initialCaps;
+ }
+
+ /**
+ * Create an ImsFeatureContainer from a Parcel.
+ */
+ private ImsFeatureContainer(Parcel in) {
+ imsFeature = in.readStrongBinder();
+ imsConfig = IImsConfig.Stub.asInterface(in.readStrongBinder());
+ imsRegistration = IImsRegistration.Stub.asInterface(in.readStrongBinder());
+ mState = in.readInt();
+ mCapabilities = in.readLong();
+ }
+
+ /**
+ * @return the capabilties that are associated with the ImsService that this ImsFeature
+ * belongs to.
+ */
+ public @ImsService.ImsServiceCapability long getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Update the capabilities that are associated with the ImsService that this ImsFeature
+ * belongs to.
+ */
+ public void setCapabilities(@ImsService.ImsServiceCapability long caps) {
+ mCapabilities = caps;
+ }
+
+ /**
+ * @return The state of the ImsFeature.
+ */
+ public @ImsFeature.ImsState int getState() {
+ return mState;
+ }
+
+ /**
+ * Set the state that is associated with the ImsService that this ImsFeature
+ * belongs to.
+ */
+ public void setState(@ImsFeature.ImsState int state) {
+ mState = state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ImsFeatureContainer that = (ImsFeatureContainer) o;
+ return imsFeature.equals(that.imsFeature) &&
+ imsConfig.equals(that.imsConfig) &&
+ imsRegistration.equals(that.imsRegistration) &&
+ mState == that.getState() &&
+ mCapabilities == that.getCapabilities();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(imsFeature, imsConfig, imsRegistration, mState, mCapabilities);
+ }
+
+ @Override
+ public String toString() {
+ return "FeatureContainer{" +
+ "imsFeature=" + imsFeature +
+ ", imsConfig=" + imsConfig +
+ ", imsRegistration=" + imsRegistration +
+ ", state=" + ImsFeature.STATE_LOG_MAP.get(mState) +
+ ", capabilities = " + ImsService.getCapabilitiesString(mCapabilities) +
+ '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(imsFeature);
+ dest.writeStrongInterface(imsConfig);
+ dest.writeStrongInterface(imsRegistration);
+ dest.writeInt(mState);
+ dest.writeLong(mCapabilities);
+ }
+
+
+ public static final Creator<ImsFeatureContainer> CREATOR = new Creator<ImsFeatureContainer>() {
+ @Override
+ public ImsFeatureContainer createFromParcel(Parcel source) {
+ return new ImsFeatureContainer(source);
+ }
+
+ @Override
+ public ImsFeatureContainer[] newArray(int size) {
+ return new ImsFeatureContainer[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
index 9a9cf5325310..f5f67bd36ec3 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
@@ -16,13 +16,18 @@
package com.android.ims.internal;
+import com.android.ims.ImsFeatureContainer;
/**
- * Interface from ImsResolver to ImsServiceProxy in ImsManager.
- * Callback to ImsManager when a feature changes in the ImsServiceController.
+ * Interface from ImsResolver to FeatureConnections.
+ * Callback to FeatureConnections when a feature's status changes.
* {@hide}
*/
oneway interface IImsServiceFeatureCallback {
- void imsFeatureCreated(int slotId, int feature);
- void imsFeatureRemoved(int slotId, int feature);
- void imsStatusChanged(int slotId, int feature, int status);
+ void imsFeatureCreated(in ImsFeatureContainer feature);
+ // Reason defined in FeatureConnector.UnavailableReason
+ void imsFeatureRemoved(int reason);
+ // Status defined in ImsFeature.ImsState.
+ void imsStatusChanged(int status);
+ //Capabilities defined in ImsService.ImsServiceCapability
+ void updateCapabilities(long capabilities);
} \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ICallForwardingInfoCallback.aidl b/telephony/java/com/android/internal/telephony/ICallForwardingInfoCallback.aidl
new file mode 100644
index 000000000000..4d3b9f4636df
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ICallForwardingInfoCallback.aidl
@@ -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.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.CallForwardingInfo;
+
+// Callback interface for the getCallForwarding API in TelephonyManager.
+oneway interface ICallForwardingInfoCallback {
+ void onCallForwardingInfoAvailable(in CallForwardingInfo info);
+ void onError(int error);
+} \ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 4021d0a2888f..0d8351d1ff12 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -58,6 +58,7 @@ import android.telephony.ims.aidl.IImsRegistrationCallback;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
+import com.android.internal.telephony.ICallForwardingInfoCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
import com.android.internal.telephony.OperatorInfo;
@@ -828,22 +829,14 @@ interface ITelephony {
* as well as registering the MmTelFeature for callbacks using the IImsServiceFeatureCallback
* interface.
*/
- IImsMmTelFeature getMmTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
-
- /**
- * Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature
- * as well as registering the RcsFeature for callbacks using the IImsServiceFeatureCallback
- * interface.
- */
- IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
+ void registerMmTelFeatureCallback(int slotId, in IImsServiceFeatureCallback callback);
/**
* Unregister a callback that was previously registered through
- * {@link #getMmTelFeatureAndListen} or {@link #getRcsFeatureAndListen}. This should always be
- * called when the callback is no longer being used.
+ * {@link #registerMmTelFeatureCallback}. This should always be called when the callback is no
+ * longer being used.
*/
- void unregisterImsFeatureCallback(int slotId, int featureType,
- in IImsServiceFeatureCallback callback);
+ void unregisterImsFeatureCallback(in IImsServiceFeatureCallback callback);
/**
* Returns the IImsRegistration associated with the slot and feature specified.
@@ -1648,78 +1641,15 @@ interface ITelephony {
*/
void carrierActionResetAll(int subId);
- /**
- * Gets the voice call forwarding info {@link CallForwardingInfo}, given the call forward
- * reason.
- *
- * @param callForwardingReason the call forwarding reasons which are the bitwise-OR combination
- * of the following constants:
- * <ol>
- * <li>{@link CallForwardingInfo#REASON_BUSY} </li>
- * <li>{@link CallForwardingInfo#REASON_NO_REPLY} </li>
- * <li>{@link CallForwardingInfo#REASON_NOT_REACHABLE} </li>
- * </ol>
- *
- * @throws IllegalArgumentException if callForwardingReason is not a bitwise-OR combination
- * of {@link CallForwardingInfo.REASON_BUSY}, {@link CallForwardingInfo.REASON_BUSY},
- * {@link CallForwardingInfo.REASON_NOT_REACHABLE}
- *
- * @return {@link CallForwardingInfo} with the status {@link CallForwardingInfo#STATUS_ACTIVE}
- * or {@link CallForwardingInfo#STATUS_INACTIVE} and the target phone number to forward calls
- * to, if it's available. Otherwise, it will return a {@link CallForwardingInfo} with status
- * {@link CallForwardingInfo#STATUS_NOT_SUPPORTED} or
- * {@link CallForwardingInfo#STATUS_FDN_CHECK_FAILURE} depending on the situation.
- *
- * @hide
- */
- CallForwardingInfo getCallForwarding(int subId, int callForwardingReason);
+ void getCallForwarding(int subId, int callForwardingReason,
+ ICallForwardingInfoCallback callback);
- /**
- * Sets the voice call forwarding info including status (enable/disable), call forwarding
- * reason, the number to forward, and the timeout before the forwarding is attempted.
- *
- * @param callForwardingInfo {@link CallForwardingInfo} to setup the call forwarding.
- * Enabling if {@link CallForwardingInfo#getStatus()} returns
- * {@link CallForwardingInfo#STATUS_ACTIVE}; Disabling if
- * {@link CallForwardingInfo#getStatus()} returns {@link CallForwardingInfo#STATUS_INACTIVE}.
- *
- * @throws IllegalArgumentException if any of the following:
- * 0) callForwardingInfo is null.
- * 1) {@link CallForwardingInfo#getStatus()} for callForwardingInfo returns neither
- * {@link CallForwardingInfo#STATUS_ACTIVE} nor {@link CallForwardingInfo#STATUS_INACTIVE}.
- * 2) {@link CallForwardingInfo#getReason()} for callForwardingInfo doesn't return the
- * bitwise-OR combination of {@link CallForwardingInfo.REASON_BUSY},
- * {@link CallForwardingInfo.REASON_BUSY}, {@link CallForwardingInfo.REASON_NOT_REACHABLE}
- * 3) {@link CallForwardingInfo#getNumber()} for callForwardingInfo returns null.
- * 4) {@link CallForwardingInfo#getTimeout()} for callForwardingInfo returns nagetive value.
- *
- * @return {@code true} to indicate it was set successfully; {@code false} otherwise.
- *
- * @hide
- */
- boolean setCallForwarding(int subId, in CallForwardingInfo callForwardingInfo);
+ void setCallForwarding(int subId, in CallForwardingInfo callForwardingInfo,
+ IIntegerConsumer callback);
- /**
- * Gets the status of voice call waiting function. Call waiting function enables the waiting
- * for the incoming call when it reaches the user who is busy to make another call and allows
- * users to decide whether to switch to the incoming call.
- *
- * @return the status of call waiting function.
- * @hide
- */
- int getCallWaitingStatus(int subId);
+ void getCallWaitingStatus(int subId, IIntegerConsumer callback);
- /**
- * Sets the status for voice call waiting function. Call waiting function enables the waiting
- * for the incoming call when it reaches the user who is busy to make another call and allows
- * users to decide whether to switch to the incoming call.
- *
- * @param isEnable {@code true} to enable; {@code false} to disable.
- * @return {@code true} to indicate it was set successfully; {@code false} otherwise.
- *
- * @hide
- */
- boolean setCallWaitingStatus(int subId, boolean isEnable);
+ void setCallWaitingStatus(int subId, boolean enabled, IIntegerConsumer callback);
/**
* Get Client request stats which will contain statistical information
@@ -2234,21 +2164,9 @@ interface ITelephony {
*/
String getMmsUAProfUrl(int subId);
- /**
- * Set allowing mobile data during voice call.
- */
- boolean setDataAllowedDuringVoiceCall(int subId, boolean allow);
+ void setMobileDataPolicyEnabledStatus(int subscriptionId, int policy, boolean enabled);
- /**
- * Check whether data is allowed during voice call. Note this is for dual sim device that
- * data might be disabled on non-default data subscription but explicitly turned on by settings.
- */
- boolean isDataAllowedInVoiceCall(int subId);
-
- /**
- * Set whether a subscription always allows MMS connection.
- */
- boolean setAlwaysAllowMmsData(int subId, boolean allow);
+ boolean isMobileDataPolicyEnabled(int subscriptionId, int policy);
/**
* Command line command to enable or disable handling of CEP data for test purposes.
@@ -2290,4 +2208,12 @@ interface ITelephony {
* Whether device can connect to 5G network when two SIMs are active.
*/
boolean canConnectTo5GInDsdsMode();
+
+ /**
+ * Returns a list of the equivalent home PLMNs (EF_EHPLMN) from the USIM app.
+ *
+ * @return A list of equivalent home PLMNs. Returns an empty list if EF_EHPLMN is empty or
+ * does not exist on the SIM card.
+ */
+ List<String> getEquivalentHomePlmns(int subId, String callingPackage, String callingFeatureId);
}
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 475305eb83a2..66e44e123c7e 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@ public class HierrarchicalDataClassBase implements Parcelable {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -98,8 +98,8 @@ public class HierrarchicalDataClassBase implements Parcelable {
};
@DataClass.Generated(
- time = 1582685650576L,
- codegenVersion = "1.0.15",
+ time = 1601950882280L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index 150b324d1a30..643abd8650d0 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -120,8 +120,8 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase {
};
@DataClass.Generated(
- time = 1582685651560L,
- codegenVersion = "1.0.15",
+ time = 1601950883222L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 30871566c269..5e33a338f098 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -54,7 +54,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -412,8 +412,8 @@ public class ParcelAllTheThingsDataClass implements Parcelable {
}
@DataClass.Generated(
- time = 1582685649678L,
- codegenVersion = "1.0.15",
+ time = 1601950881327L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 8d421bfa492a..9e5b1a974030 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,7 +342,7 @@ public final class SampleDataClass implements Parcelable {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -1872,8 +1872,8 @@ public final class SampleDataClass implements Parcelable {
}
@DataClass.Generated(
- time = 1582685647656L,
- codegenVersion = "1.0.15",
+ time = 1601950879293L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index d9fe1fd5a465..735f7047af9f 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@ public class SampleWithCustomBuilder implements Parcelable {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -253,10 +253,10 @@ public class SampleWithCustomBuilder implements Parcelable {
}
@DataClass.Generated(
- time = 1582685648622L,
- codegenVersion = "1.0.15",
+ time = 1601950880290L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
- inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
index f98d7b05c2d3..51601213acce 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
@@ -36,7 +36,7 @@ public class SampleWithNestedDataClasses {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -135,8 +135,8 @@ public class SampleWithNestedDataClasses {
};
@DataClass.Generated(
- time = 1582685653406L,
- codegenVersion = "1.0.15",
+ time = 1601950885147L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -160,7 +160,7 @@ public class SampleWithNestedDataClasses {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -259,8 +259,8 @@ public class SampleWithNestedDataClasses {
};
@DataClass.Generated(
- time = 1582685653415L,
- codegenVersion = "1.0.15",
+ time = 1601950885156L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -274,7 +274,7 @@ public class SampleWithNestedDataClasses {
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -373,8 +373,8 @@ public class SampleWithNestedDataClasses {
};
@DataClass.Generated(
- time = 1582685653420L,
- codegenVersion = "1.0.15",
+ time = 1601950885161L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index 6b4fc2fa157f..5417c11c8fca 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -16,9 +16,13 @@
package com.android.codegentest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import com.android.internal.util.DataClass;
+import java.util.List;
+import java.util.Set;
+
/**
* Test for some false positive pitfalls for
* {@link android.processor.staledataclass.StaleDataclassProcessor}
@@ -46,12 +50,16 @@ public class StaleDataclassDetectorFalsePositivesTest {
/* Initializers should be ignored */
{}
+ /* Wildcard type argument should work correctly */
+ @Nullable
+ private List<Set<?>> mUsesWildcards = null;
+
/** Unrelated methods should be noted, without triggering staleness false positives */
public @NonNull String someMethod(int param) { return null; }
- // Code below generated by codegen v1.0.15.
+ // Code below generated by codegen v1.0.16.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -64,11 +72,22 @@ public class StaleDataclassDetectorFalsePositivesTest {
//@formatter:off
+ @DataClass.Generated.Member
+ public @Nullable List<Set<?>> getUsesWildcards() {
+ return mUsesWildcards;
+ }
+
+ @DataClass.Generated.Member
+ public StaleDataclassDetectorFalsePositivesTest setUsesWildcards(@NonNull List<Set<?>> value) {
+ mUsesWildcards = value;
+ return this;
+ }
+
@DataClass.Generated(
- time = 1582685652436L,
- codegenVersion = "1.0.15",
+ time = 1601950884160L,
+ codegenVersion = "1.0.16",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
- inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index d430db525f4c..3b9bec9ad0a7 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -54,4 +54,18 @@ android_test {
"launcher-aosp-tapl",
"platform-test-annotations",
],
+}
+
+java_library {
+ name: "wm-flicker-common-assertions",
+ platform_apis: true,
+ srcs: ["src/**/*Assertions.java", "src/**/*Assertions.kt"],
+ exclude_srcs: [
+ "**/helpers/*",
+ ],
+ static_libs: [
+ "flickerlib",
+ "truth-prebuilt",
+ "app-helpers-core"
+ ],
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 69b11872e123..8457039e0399 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,13 +21,17 @@ import com.android.server.wm.flicker.dsl.LayersAssertion
import com.android.server.wm.flicker.dsl.WmAssertion
import com.android.server.wm.flicker.helpers.WindowUtils
+const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
+const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
+
@JvmOverloads
fun WmAssertion.statusBarWindowIsAlwaysVisible(
bugId: Int = 0,
enabled: Boolean = bugId == 0
) {
all("statusBarWindowIsAlwaysVisible", enabled, bugId) {
- this.showsAboveAppWindow(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
}
}
@@ -37,7 +41,7 @@ fun WmAssertion.navBarWindowIsAlwaysVisible(
enabled: Boolean = bugId == 0
) {
all("navBarWindowIsAlwaysVisible", enabled, bugId) {
- this.showsAboveAppWindow(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
}
}
@@ -77,7 +81,7 @@ fun LayersAssertion.navBarLayerIsAlwaysVisible(
enabled: Boolean = bugId == 0
) {
all("navBarLayerIsAlwaysVisible", enabled, bugId) {
- this.showsLayer(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE)
+ this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
}
}
@@ -87,7 +91,7 @@ fun LayersAssertion.statusBarLayerIsAlwaysVisible(
enabled: Boolean = bugId == 0
) {
all("statusBarLayerIsAlwaysVisible", enabled, bugId) {
- this.showsLayer(FlickerTestBase.STATUS_BAR_WINDOW_TITLE)
+ this.showsLayer(STATUS_BAR_WINDOW_TITLE)
}
}
@@ -102,15 +106,15 @@ fun LayersAssertion.navBarLayerRotatesAndScales(
val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
start("navBarLayerRotatesAndScales_StartingPos", enabled, bugId) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
}
end("navBarLayerRotatesAndScales_EndingPost", enabled, bugId) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
}
if (startingPos == endingPos) {
all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
- this.hasVisibleRegion(FlickerTestBase.NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
}
}
}
@@ -126,10 +130,10 @@ fun LayersAssertion.statusBarLayerRotatesScales(
val endingPos = WindowUtils.getStatusBarPosition(endRotation)
start("statusBarLayerRotatesScales_StartingPos", enabled, bugId) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, startingPos)
+ this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
}
end("statusBarLayerRotatesScales_EndingPos", enabled, bugId) {
- this.hasVisibleRegion(FlickerTestBase.STATUS_BAR_WINDOW_TITLE, endingPos)
+ this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
deleted file mode 100644
index abe7dbc57002..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import android.os.RemoteException
-import android.os.SystemClock
-import android.platform.helpers.IAppHelper
-import android.view.Surface
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-
-/**
- * Base class of all Flicker test that performs common functions for all flicker tests:
- *
- *
- * - Caches transitions so that a transition is run once and the transition results are used by
- * tests multiple times. This is needed for parameterized tests which call the BeforeClass methods
- * multiple times.
- * - Keeps track of all test artifacts and deletes ones which do not need to be reviewed.
- * - Fails tests if results are not available for any test due to jank.
- */
-abstract class FlickerTestBase {
- val instrumentation by lazy {
- InstrumentationRegistry.getInstrumentation()
- }
- val uiDevice by lazy {
- UiDevice.getInstance(instrumentation)
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param rotation Initial screen rotation
- *
- * @return test tag with pattern <NAME>__<APP>__<ROTATION>
- </ROTATION></APP></NAME> */
- protected fun buildTestTag(testName: String, app: IAppHelper, rotation: Int): String {
- return buildTestTag(
- testName, app, rotation, rotation, app2 = null, extraInfo = "")
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param beginRotation Initial screen rotation
- * @param endRotation End screen rotation (if any, otherwise use same as initial)
- *
- * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
- </END_ROTATION></BEGIN_ROTATION></APP></NAME> */
- protected fun buildTestTag(
- testName: String,
- app: IAppHelper,
- beginRotation: Int,
- endRotation: Int
- ): String {
- return buildTestTag(
- testName, app, beginRotation, endRotation, app2 = null, extraInfo = "")
- }
-
- /**
- * Build a test tag for the test
- * @param testName Name of the transition(s) being tested
- * @param app App being launcher
- * @param app2 Second app being launched (if any)
- * @param beginRotation Initial screen rotation
- * @param endRotation End screen rotation (if any, otherwise use same as initial)
- * @param extraInfo Additional information to append to the tag
- *
- * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
- </EXTRA></NAME> */
- protected fun buildTestTag(
- testName: String,
- app: IAppHelper,
- beginRotation: Int,
- endRotation: Int,
- app2: IAppHelper?,
- extraInfo: String
- ): String {
- var testTag = "${testName}__${app.launcherName}"
- if (app2 != null) {
- testTag += "-${app2.launcherName}"
- }
- testTag += "__${Surface.rotationToString(beginRotation)}"
- if (endRotation != beginRotation) {
- testTag += "-${Surface.rotationToString(endRotation)}"
- }
- if (extraInfo.isNotEmpty()) {
- testTag += "__$extraInfo"
- }
- return testTag
- }
-
- protected fun Flicker.setRotation(rotation: Int) {
- try {
- when (rotation) {
- Surface.ROTATION_270 -> device.setOrientationLeft()
- Surface.ROTATION_90 -> device.setOrientationRight()
- Surface.ROTATION_0 -> device.setOrientationNatural()
- else -> device.setOrientationNatural()
- }
- // Wait for animation to complete
- SystemClock.sleep(1000)
- } catch (e: RemoteException) {
- throw RuntimeException(e)
- }
- }
-
- companion object {
- const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
- const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
- const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
deleted file mode 100644
index e7d1f8e94dba..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import android.view.Surface
-import org.junit.runners.Parameterized
-
-abstract class NonRotationTestBase(
- protected val rotationName: String,
- protected val rotation: Int
-) : FlickerTestBase() {
- companion object {
- const val SCREENSHOT_LAYER = "RotationLayer"
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
- }
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
deleted file mode 100644
index 3b6772779029..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/RotationTestBase.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker
-
-import android.view.Surface
-import org.junit.runners.Parameterized
-
-abstract class RotationTestBase(
- beginRotationName: String,
- endRotationName: String,
- protected val beginRotation: Int,
- protected val endRotation: Int
-) : FlickerTestBase() {
- companion object {
- @Parameterized.Parameters(name = "{0}-{1}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- val params: MutableCollection<Array<Any>> = mutableListOf()
- for (begin in supportedRotations) {
- for (end in supportedRotations) {
- if (begin != end) {
- params.add(arrayOf(
- Surface.rotationToString(begin),
- Surface.rotationToString(end),
- begin,
- end
- ))
- }
- }
- }
- return params
- }
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
new file mode 100644
index 000000000000..742003a9438e
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers
+
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.SystemClock
+import android.platform.helpers.IAppHelper
+import android.view.Surface
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.startRotation
+
+fun Flicker.setRotation(rotation: Int) {
+ try {
+ when (rotation) {
+ Surface.ROTATION_270 -> device.setOrientationLeft()
+ Surface.ROTATION_90 -> device.setOrientationRight()
+ Surface.ROTATION_0 -> device.setOrientationNatural()
+ else -> device.setOrientationNatural()
+ }
+ // Wait for animation to complete
+ SystemClock.sleep(1000)
+ } catch (e: RemoteException) {
+ throw RuntimeException(e)
+ }
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ *
+ * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+</END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+fun buildTestTag(
+ testName: String,
+ app: IAppHelper,
+ beginRotation: Int,
+ endRotation: Int
+): String {
+ return buildTestTag(
+ testName, app.launcherName, beginRotation, endRotation, app2 = null, extraInfo = "")
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param rotation Screen rotation configuration for the test
+ *
+ * @return test tag with pattern <NAME>__<APP>__<BEGIN_ROTATION>-<END_ROTATION>
+</END_ROTATION></BEGIN_ROTATION></APP></NAME> */
+fun buildTestTag(
+ testName: String,
+ app: IAppHelper?,
+ configuration: Bundle
+): String {
+ return buildTestTag(testName, app?.launcherName ?: "", configuration.startRotation,
+ configuration.endRotation, app2 = null, extraInfo = "")
+}
+
+/**
+ * Build a test tag for the test
+ * @param testName Name of the transition(s) being tested
+ * @param app App being launcher
+ * @param app2 Second app being launched (if any)
+ * @param beginRotation Initial screen rotation
+ * @param endRotation End screen rotation (if any, otherwise use same as initial)
+ * @param extraInfo Additional information to append to the tag
+ *
+ * @return test tag with pattern <NAME>__<APP></APP>(S)>__<ROTATION></ROTATION>(S)>[__<EXTRA>]
+</EXTRA></NAME> */
+fun buildTestTag(
+ testName: String,
+ app: String,
+ beginRotation: Int,
+ endRotation: Int,
+ app2: String?,
+ extraInfo: String
+): String {
+ var testTag = "${testName}__$app"
+ if (app2 != null) {
+ testTag += "-$app2"
+ }
+ testTag += "__${Surface.rotationToString(beginRotation)}"
+ if (endRotation != beginRotation) {
+ testTag += "-${Surface.rotationToString(endRotation)}"
+ }
+ if (extraInfo.isNotEmpty()) {
+ testTag += "__$extraInfo"
+ }
+ return testTag
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 404c7891bcad..a73264d304f8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -16,21 +16,27 @@
package com.android.server.wm.flicker.ime
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,56 +45,62 @@ import org.junit.runners.Parameterized
* Test IME window closing back to app window transitions.
* To run this test: `atest FlickerTests:CloseImeWindowToAppTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class CloseImeAutoOpenWindowToAppTest(
- rotationName: String,
- rotation: Int
-) : CloseImeWindowToAppTest(rotationName, rotation) {
- override val testApp: ImeAppHelper
- get() = ImeAppAutoFocusHelper(instrumentation)
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
- @Test
- override fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("imeToAppAutoOpen", testApp, rotation) }
- repeat { 1 }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- testApp.open()
- testApp.openIME(device)
- }
- }
- teardown {
- eachRun {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- transitions {
- device.pressBack()
- device.waitForIdle()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- imeAppWindowIsAlwaysVisible(testApp, bugId = 141458352)
- }
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = ImeAppAutoFocusHelper(instrumentation)
+
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTag { buildTestTag("imeToAppAutoOpen", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ testApp.open()
+ testApp.openIME(device)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ transitions {
+ device.pressBack()
+ device.waitForIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ imeAppWindowIsAlwaysVisible(testApp, bugId = 141458352)
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
- imeLayerBecomesInvisible(bugId = 141458352)
- imeAppLayerIsAlwaysVisible(testApp, bugId = 141458352)
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation)
+ navBarLayerRotatesAndScales(configuration.startRotation)
+ statusBarLayerRotatesScales(configuration.startRotation)
+ imeLayerBecomesInvisible(bugId = 141458352)
+ imeAppLayerIsAlwaysVisible(testApp, bugId = 141458352)
+ }
+ }
}
- }
}
}
-}
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index c1ba21a2977a..76478025ee63 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -19,19 +19,24 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
-import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -45,53 +50,63 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class CloseImeAutoOpenWindowToHomeTest(
- rotationName: String,
- rotation: Int
-) : CloseImeWindowToHomeTest(rotationName, rotation) {
- override val testApp: ImeAppHelper
- get() = ImeAppAutoFocusHelper(instrumentation)
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
- @Test
- override fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("imeToHomeAutoOpen", testApp, rotation) }
- repeat { 1 }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- testApp.open()
- testApp.openIME(device)
- }
- }
- teardown {
- eachRun {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- transitions {
- device.pressHome()
- device.waitForIdle()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- imeWindowBecomesInvisible(bugId = 141458352)
- imeAppWindowBecomesInvisible(testApp, bugId = 157449248)
- }
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = ImeAppAutoFocusHelper(instrumentation)
+
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("imeToHomeAutoOpen", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ testApp.open()
+ testApp.openIME(device)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ transitions {
+ device.pressHome()
+ device.waitForIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ imeWindowBecomesInvisible(bugId = 141458352)
+ imeAppWindowBecomesInvisible(testApp, bugId = 157449248)
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
- navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
- statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
- imeLayerBecomesInvisible(bugId = 141458352)
- imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ allStates = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+ imeLayerBecomesInvisible(bugId = 141458352)
+ imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+ }
+ }
}
- }
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 2c0072270378..136cf86037a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -19,19 +19,24 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -44,52 +49,57 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToAppTest(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- open val testApp = ImeAppHelper(instrumentation)
+class CloseImeWindowToAppTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
- @Test
- open fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("imeToApp", testApp, rotation) }
- repeat { 1 }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- testApp.open()
- testApp.openIME(device)
- }
- }
- teardown {
- eachRun {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- transitions {
- device.pressBack()
- device.waitForIdle()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- imeAppWindowIsAlwaysVisible(testApp)
- }
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = ImeAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTestName { buildTestTag("imeToApp", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ testApp.open()
+ testApp.openIME(device)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ transitions {
+ device.pressBack()
+ device.waitForIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ imeAppWindowIsAlwaysVisible(testApp)
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
- imeLayerBecomesInvisible(enabled = false)
- imeAppLayerIsAlwaysVisible(testApp)
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation)
+ navBarLayerRotatesAndScales(configuration.startRotation)
+ statusBarLayerRotatesScales(configuration.startRotation)
+ imeLayerBecomesInvisible(enabled = false)
+ imeAppLayerIsAlwaysVisible(testApp)
+ }
+ }
}
- }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 4697adcbce80..6cfb28202b39 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -19,21 +19,26 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.openQuickstep
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -45,61 +50,69 @@ import org.junit.runners.Parameterized
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class CloseImeWindowToHomeTest(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- open val testApp = ImeAppHelper(instrumentation)
+class CloseImeWindowToHomeTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
- @Test
- open fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("imeToHome", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- testApp.open()
- }
- eachRun {
- device.openQuickstep()
- device.reopenAppFromOverview()
- this.setRotation(rotation)
- testApp.openIME(device)
- }
- }
- transitions {
- device.pressHome()
- device.waitForIdle()
- }
- teardown {
- eachRun {
- device.pressHome()
- }
- test {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- imeWindowBecomesInvisible()
- imeAppWindowBecomesInvisible(testApp)
- }
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = ImeAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTestName { buildTestTag("imeToHome", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ testApp.open()
+ }
+ eachRun {
+ device.openQuickstep()
+ device.reopenAppFromOverview()
+ this.setRotation(configuration.startRotation)
+ testApp.openIME(device)
+ }
+ }
+ transitions {
+ device.pressHome()
+ device.waitForIdle()
+ }
+ teardown {
+ eachRun {
+ device.pressHome()
+ }
+ test {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ imeWindowBecomesInvisible()
+ imeAppWindowBecomesInvisible(testApp)
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
- navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0, bugId = 140855415)
- statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
- imeLayerBecomesInvisible(bugId = 153739621)
- imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation,
+ Surface.ROTATION_0, allStates = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+ imeLayerBecomesInvisible(bugId = 153739621)
+ imeAppLayerBecomesInvisible(testApp, bugId = 153739621)
+ }
+ }
}
- }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ImeAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index b2be54fe068a..b2be54fe068a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ImeAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 2caa8f399be7..5767a94025e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -19,19 +19,24 @@ package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -45,62 +50,65 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenImeWindowTest(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- @Test
- fun test() {
- val testApp = ImeAppHelper(instrumentation)
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ private const val IME_WINDOW_TITLE = "InputMethod"
- flicker(instrumentation) {
- withTag { buildTestTag("openIme", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- testApp.open()
- }
- }
- transitions {
- testApp.openIME(device)
- }
- teardown {
- eachRun {
- testApp.closeIME(device)
- }
- test {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = ImeAppHelper(instrumentation)
- all("imeWindowBecomesVisible") {
- this.skipUntilFirstAssertion()
- .hidesNonAppWindow(IME_WINDOW_TITLE)
- .then()
- .showsNonAppWindow(IME_WINDOW_TITLE)
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTestName { buildTestTag("openIme", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ testApp.open()
+ }
}
- }
+ transitions {
+ testApp.openIME(device)
+ }
+ teardown {
+ eachRun {
+ testApp.closeIME(device)
+ }
+ test {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+
+ all("imeWindowBecomesVisible") {
+ this.skipUntilFirstAssertion()
+ .hidesNonAppWindow(IME_WINDOW_TITLE)
+ .then()
+ .showsNonAppWindow(IME_WINDOW_TITLE)
+ }
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation)
+ navBarLayerRotatesAndScales(configuration.startRotation)
+ statusBarLayerRotatesScales(configuration.startRotation)
- imeLayerBecomesVisible()
+ imeLayerBecomesVisible()
+ }
+ }
}
- }
}
}
-
- companion object {
- private const val IME_WINDOW_TITLE = "InputMethod"
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
new file mode 100644
index 000000000000..7e857f3b56c0
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.helpers.IAppHelper
+import com.android.server.wm.flicker.dsl.LayersAssertion
+import com.android.server.wm.flicker.dsl.WmAssertion
+
+fun WmAssertion.wallpaperWindowBecomesInvisible(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("wallpaperWindowBecomesInvisible", enabled, bugId) {
+ this.showsBelowAppWindow("Wallpaper")
+ .then()
+ .hidesBelowAppWindow("Wallpaper")
+ }
+}
+
+fun WmAssertion.appWindowReplacesLauncherAsTopWindow(
+ testApp: IAppHelper,
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
+ this.showsAppWindowOnTop("Launcher")
+ .then()
+ .showsAppWindowOnTop("Snapshot", testApp.getPackage())
+ }
+}
+
+fun LayersAssertion.wallpaperLayerBecomesInvisible(
+ testApp: IAppHelper,
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("wallpaperLayerBecomesInvisible", enabled, bugId) {
+ this.showsLayer("Wallpaper")
+ .then()
+ .replaceVisibleLayer("Wallpaper", testApp.getPackage())
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 2c9c8ba75a10..1081414e9b6b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -19,18 +19,26 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -44,53 +52,64 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenAppColdTest(
- rotationName: String,
- rotation: Int
-) : OpenAppTestBase(rotationName, rotation) {
- @Test
- fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("openAppCold", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- this.setRotation(rotation)
- }
- }
- transitions {
- testApp.open()
- }
- teardown {
- eachRun {
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- appWindowReplacesLauncherAsTopWindow()
- wallpaperWindowBecomesInvisible()
- }
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTag { buildTestTag("openAppCold", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ testApp.open()
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowReplacesLauncherAsTopWindow(testApp)
+ wallpaperWindowBecomesInvisible()
+ }
- layersTrace {
- // During testing the launcher is always in portrait mode
- noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
- navBarLayerIsAlwaysVisible(enabled = rotation == Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(enabled = false)
- wallpaperLayerBecomesInvisible()
- }
+ layersTrace {
+ // During testing the launcher is always in portrait mode
+ noUncoveredRegions(Surface.ROTATION_0, configuration.endRotation,
+ bugId = 141361128)
+ navBarLayerRotatesAndScales(Surface.ROTATION_0,
+ configuration.endRotation)
+ statusBarLayerRotatesScales(Surface.ROTATION_0,
+ configuration.endRotation)
+ navBarLayerIsAlwaysVisible(
+ enabled = configuration.endRotation == Surface.ROTATION_0)
+ statusBarLayerIsAlwaysVisible(enabled = false)
+ wallpaperLayerBecomesInvisible(testApp)
+ }
- eventLog {
- focusChanges("NexusLauncherActivity", testApp.`package`)
+ eventLog {
+ focusChanges("NexusLauncherActivity", testApp.`package`)
+ }
+ }
}
- }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt
deleted file mode 100644
index 98e05d526bed..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTestBase.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import com.android.server.wm.flicker.NonRotationTestBase
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.LayersAssertion
-import com.android.server.wm.flicker.dsl.WmAssertion
-
-abstract class OpenAppTestBase(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- protected val testApp = StandardAppHelper(instrumentation,
- "com.android.server.wm.flicker.testapp", "SimpleApp")
-
- protected fun WmAssertion.wallpaperWindowBecomesInvisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
- ) {
- all("wallpaperWindowBecomesInvisible", enabled, bugId) {
- this.showsBelowAppWindow("Wallpaper")
- .then()
- .hidesBelowAppWindow("Wallpaper")
- }
- }
-
- protected fun WmAssertion.appWindowReplacesLauncherAsTopWindow(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
- ) {
- all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
- this.showsAppWindowOnTop("Launcher")
- .then()
- .showsAppWindowOnTop("Snapshot", testApp.getPackage())
- }
- }
-
- protected fun LayersAssertion.wallpaperLayerBecomesInvisible(
- bugId: Int = 0,
- enabled: Boolean = bugId == 0
- ) {
- all("appWindowReplacesLauncherAsTopWindow", enabled, bugId) {
- this.showsLayer("Wallpaper")
- .then()
- .replaceVisibleLayer("Wallpaper", testApp.getPackage())
- }
- }
-} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index acd141a8d74f..2061994b57b1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,21 +16,29 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -39,64 +47,73 @@ import org.junit.runners.Parameterized
* Test warm launch app.
* To run this test: `atest FlickerTests:OpenAppWarmTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenAppWarmTest(
- rotationName: String,
- rotation: Int
-) : OpenAppTestBase(rotationName, rotation) {
- @Test
- fun test() {
- val testApp = StandardAppHelper(instrumentation,
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
"com.android.server.wm.flicker.testapp", "SimpleApp")
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTag { buildTestTag("openAppWarm", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ }
+ eachRun {
+ device.pressHome()
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ transitions {
+ testApp.open()
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ }
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ appWindowReplacesLauncherAsTopWindow(testApp)
+ wallpaperWindowBecomesInvisible(enabled = false)
+ }
- flicker(instrumentation) {
- withTag { buildTestTag("openAppWarm", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- testApp.open()
- }
- eachRun {
- device.pressHome()
- this.setRotation(rotation)
- }
- }
- transitions {
- testApp.open()
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- testApp.exit()
- }
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- appWindowReplacesLauncherAsTopWindow()
- wallpaperWindowBecomesInvisible(enabled = false)
- }
-
- layersTrace {
- // During testing the launcher is always in portrait mode
- noUncoveredRegions(Surface.ROTATION_0, rotation, bugId = 141361128)
- navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation)
- statusBarLayerRotatesScales(Surface.ROTATION_0, rotation)
- navBarLayerIsAlwaysVisible(enabled = rotation == Surface.ROTATION_0)
- statusBarLayerIsAlwaysVisible(enabled = false)
- wallpaperLayerBecomesInvisible()
- }
+ layersTrace {
+ // During testing the launcher is always in portrait mode
+ noUncoveredRegions(Surface.ROTATION_0, configuration.endRotation,
+ bugId = 141361128)
+ navBarLayerRotatesAndScales(Surface.ROTATION_0,
+ configuration.endRotation)
+ statusBarLayerRotatesScales(Surface.ROTATION_0,
+ configuration.endRotation)
+ navBarLayerIsAlwaysVisible(
+ enabled = configuration.endRotation == Surface.ROTATION_0)
+ statusBarLayerIsAlwaysVisible(enabled = false)
+ wallpaperLayerBecomesInvisible(testApp)
+ }
- eventLog {
- focusChanges("NexusLauncherActivity", testApp.`package`)
+ eventLog {
+ focusChanges("NexusLauncherActivity", testApp.`package`)
+ }
+ }
}
- }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt
new file mode 100644
index 000000000000..6bc9dcb5d96c
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/CommonAssertions.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.pip
+
+internal const val PIP_WINDOW_TITLE = "PipMenuActivity"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
index 9cfc03304fe7..89539fd1a9ec 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/EnterPipTest.kt
@@ -16,23 +16,31 @@
package com.android.server.wm.flicker.pip
+import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.expandPipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -41,81 +49,85 @@ import org.junit.runners.Parameterized
* Test Pip launch.
* To run this test: `atest FlickerTests:PipToAppTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class EnterPipTest(
- rotationName: String,
- rotation: Int
-) : PipTestBase(rotationName, rotation) {
- @Test
- fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("enterPip", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- device.pressHome()
- testApp.open()
- this.setRotation(rotation)
- }
- }
- teardown {
- eachRun {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("enterPip", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ device.pressHome()
+ testApp.open()
+ this.setRotation(configuration.startRotation)
+ }
}
- testApp.exit()
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ teardown {
+ eachRun {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ }
}
- }
- }
- transitions {
- testApp.clickEnterPipButton(device)
- device.expandPipWindow()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- all("pipWindowBecomesVisible") {
- this.showsAppWindow(testApp.`package`)
- .then()
- .showsAppWindow(sPipWindowTitle)
+ transitions {
+ testApp.clickEnterPipButton(device)
+ device.expandPipWindow()
}
- }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
- navBarLayerRotatesAndScales(rotation, Surface.ROTATION_0)
- statusBarLayerRotatesScales(rotation, Surface.ROTATION_0)
+ all("pipWindowBecomesVisible") {
+ this.showsAppWindow(testApp.`package`)
+ .then()
+ .showsAppWindow(PIP_WINDOW_TITLE)
+ }
+ }
- all("pipLayerBecomesVisible") {
- this.showsLayer(testApp.launcherName)
- .then()
- .showsLayer(sPipWindowTitle)
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
+ }
+
+ layersTrace {
+ all("pipLayerBecomesVisible") {
+ this.showsLayer(testApp.launcherName)
+ .then()
+ .showsLayer(PIP_WINDOW_TITLE)
+ }
+ }
}
}
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
index deccc908961d..ac54a0a29350 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToAppTest.kt
@@ -19,21 +19,28 @@ package com.android.server.wm.flicker.pip
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.expandPipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -47,73 +54,82 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class PipToAppTest(
- rotationName: String,
- rotation: Int
-) : PipTestBase(rotationName, rotation) {
- @Test
- fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("exitPipModeToApp", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.pressHome()
- testApp.open()
- }
- eachRun {
- this.setRotation(rotation)
- testApp.clickEnterPipButton(device)
- device.hasPipWindow()
- }
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.pressHome()
+ testApp.open()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ testApp.clickEnterPipButton(device)
+ device.hasPipWindow()
+ }
}
- testApp.exit()
- }
- }
- transitions {
- device.expandPipWindow()
- device.waitForIdle()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
-
- all("appReplacesPipWindow") {
- this.showsAppWindow(sPipWindowTitle)
- .then()
- .showsAppWindowOnTop(testApp.launcherName)
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ }
}
- }
+ transitions {
+ device.expandPipWindow()
+ device.waitForIdle()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
+ all("appReplacesPipWindow") {
+ this.showsAppWindow(PIP_WINDOW_TITLE)
+ .then()
+ .showsAppWindowOnTop(testApp.launcherName)
+ }
+ }
- all("appReplacesPipLayer") {
- this.showsLayer(sPipWindowTitle)
- .then()
- .showsLayer(testApp.launcherName)
- }
- }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
- eventLog {
- focusChanges(
- "NexusLauncherActivity", testApp.launcherName, "NexusLauncherActivity",
- bugId = 151179149)
+ all("appReplacesPipLayer") {
+ this.showsLayer(PIP_WINDOW_TITLE)
+ .then()
+ .showsLayer(testApp.launcherName)
+ }
+ }
+
+ eventLog {
+ focusChanges(
+ "NexusLauncherActivity", testApp.launcherName,
+ "NexusLauncherActivity", bugId = 151179149)
+ }
+ }
}
- }
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
index f40869c88d63..f14a27d31ad1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/pip/PipToHomeTest.kt
@@ -19,20 +19,27 @@ package com.android.server.wm.flicker.pip
import android.view.Surface
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.closePipWindow
import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -46,83 +53,83 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 152738416)
class PipToHomeTest(
- rotationName: String,
- rotation: Int
-) : PipTestBase(rotationName, rotation) {
- @Test
- fun test() {
- flicker(instrumentation) {
- withTag { buildTestTag("exitPipModeToApp", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.pressHome()
- }
- eachRun {
- testApp.open()
- this.setRotation(rotation)
- testApp.clickEnterPipButton(device)
- device.hasPipWindow()
- }
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = PipAppHelper(instrumentation)
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ device.pressHome()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.startRotation)
+ testApp.clickEnterPipButton(device)
+ device.hasPipWindow()
+ }
}
- }
- test {
- if (device.hasPipWindow()) {
- device.closePipWindow()
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ }
+ test {
+ if (device.hasPipWindow()) {
+ device.closePipWindow()
+ }
+ testApp.exit()
+ }
}
- testApp.exit()
- }
- }
- transitions {
- testApp.closePipWindow(device)
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
-
- all("pipWindowBecomesInvisible") {
- this.showsAppWindow(sPipWindowTitle)
- .then()
- .hidesAppWindow(sPipWindowTitle)
+ transitions {
+ testApp.closePipWindow(device)
}
- }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- // The final state is the launcher, so always in portrait mode
- noUncoveredRegions(rotation, Surface.ROTATION_0, allStates = false)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
+ all("pipWindowBecomesInvisible") {
+ this.showsAppWindow(PIP_WINDOW_TITLE)
+ .then()
+ .hidesAppWindow(PIP_WINDOW_TITLE)
+ }
+ }
- all("pipLayerBecomesInvisible") {
- this.showsLayer(sPipWindowTitle)
- .then()
- .hidesLayer(sPipWindowTitle)
- }
- }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0,
+ enabled = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ Surface.ROTATION_0, bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ Surface.ROTATION_0)
- eventLog {
- focusChanges(testApp.launcherName, "NexusLauncherActivity", bugId = 151179149)
- }
- }
- }
- }
+ all("pipLayerBecomesInvisible") {
+ this.showsLayer(PIP_WINDOW_TITLE)
+ .then()
+ .hidesLayer(PIP_WINDOW_TITLE)
+ }
+ }
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ eventLog {
+ focusChanges(testApp.launcherName, "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
+ }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 99218c2c586d..24ca31164ac9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -16,24 +16,30 @@
package com.android.server.wm.flicker.rotation
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import android.view.Surface
-import com.android.server.wm.flicker.NonRotationTestBase.Companion.SCREENSHOT_LAYER
-import com.android.server.wm.flicker.RotationTestBase
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -42,84 +48,95 @@ import org.junit.runners.Parameterized
* Cycle through supported app rotations.
* To run this test: `atest FlickerTest:ChangeAppRotationTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class ChangeAppRotationTest(
- beginRotationName: String,
- endRotationName: String,
- beginRotation: Int,
- endRotation: Int
-) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
- @Test
- fun test() {
- val testApp = StandardAppHelper(instrumentation,
- "com.android.server.wm.flicker.testapp", "SimpleApp")
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ private const val SCREENSHOT_LAYER = "RotationLayer"
- flicker(instrumentation) {
- withTag {
- buildTestTag("changeAppRotation", testApp, beginRotation, endRotation)
- }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- testApp.open()
- }
- eachRun {
- this.setRotation(beginRotation)
- }
- }
- teardown {
- eachRun {
- this.setRotation(Surface.ROTATION_0)
- }
- test {
- testApp.exit()
- }
- }
- transitions {
- this.setRotation(endRotation)
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildRotationTest { configuration ->
+ withTestName {
+ buildTestTag(
+ "changeAppRotation", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ testApp.open()
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ this.setRotation(Surface.ROTATION_0)
+ }
+ test {
+ testApp.exit()
+ }
+ }
+ transitions {
+ this.setRotation(configuration.endRotation)
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- noUncoveredRegions(beginRotation, endRotation, allStates = false)
- navBarLayerRotatesAndScales(beginRotation, endRotation)
- statusBarLayerRotatesScales(beginRotation, endRotation)
- }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation,
+ configuration.endRotation, allStates = false)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ configuration.endRotation)
+ }
- layersTrace {
- val startingPos = WindowUtils.getDisplayBounds(beginRotation)
- val endingPos = WindowUtils.getDisplayBounds(endRotation)
+ layersTrace {
+ val startingPos = WindowUtils.getDisplayBounds(
+ configuration.startRotation)
+ val endingPos = WindowUtils.getDisplayBounds(
+ configuration.endRotation)
- start("appLayerRotates_StartingPos") {
- this.hasVisibleRegion(testApp.getPackage(), startingPos)
- }
+ start("appLayerRotates_StartingPos") {
+ this.hasVisibleRegion(testApp.getPackage(), startingPos)
+ }
- end("appLayerRotates_EndingPos") {
- this.hasVisibleRegion(testApp.getPackage(), endingPos)
- }
+ end("appLayerRotates_EndingPos") {
+ this.hasVisibleRegion(testApp.getPackage(), endingPos)
+ }
- all("screenshotLayerBecomesInvisible") {
- this.showsLayer(testApp.getPackage())
- .then()
- .showsLayer(SCREENSHOT_LAYER)
- .then()
+ all("screenshotLayerBecomesInvisible") {
+ this.showsLayer(testApp.getPackage())
+ .then()
+ .showsLayer(SCREENSHOT_LAYER)
+ .then()
showsLayer(testApp.getPackage())
- }
- }
+ }
+ }
- eventLog {
- focusDoesNotChange(bugId = 151179149)
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
+ }
+ }
}
- }
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 33a823d6cfc9..8ad6c46b913d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,29 +16,36 @@
package com.android.server.wm.flicker.rotation
+import android.content.ComponentName
import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.RotationTestBase
-import com.android.server.wm.flicker.dsl.flicker
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.focusDoesNotChange
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.stopPackage
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -47,150 +54,142 @@ import org.junit.runners.Parameterized
* Cycle through supported app rotations using seamless rotations.
* To run this test: `atest FlickerTests:SeamlessAppRotationTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 147659548)
class SeamlessAppRotationTest(
- testId: String,
- private val intent: Intent,
- beginRotationName: String,
- endRotationName: String,
- beginRotation: Int,
- endRotation: Int
-) : RotationTestBase(beginRotationName, endRotationName, beginRotation, endRotation) {
- @Test
- fun test() {
- var intentId = ""
- if (intent.extras?.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
- intentId = "BUSY_UI_THREAD"
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ private const val APP_LAUNCH_TIMEOUT: Long = 10000
+
+ private val Bundle.intent: Intent?
+ get() = this.getParcelable(Intent::class.java.simpleName)
+
+ private val Bundle.intentPackageName: String
+ get() = this.intent?.component?.packageName ?: ""
+
+ private val Bundle.intentId get() = if (this.intent?.getBooleanExtra(
+ ActivityOptions.EXTRA_STARVE_UI_THREAD, false) == true) {
+ "BUSY_UI_THREAD"
+ } else {
+ ""
}
- flicker(instrumentation) {
- withTag {
- "changeAppRotation_" + intentId + "_" +
- Surface.rotationToString(beginRotation) + "_" +
- Surface.rotationToString(endRotation)
+ private fun Bundle.createConfig(starveUiThread: Boolean): Bundle {
+ val config = this.deepCopy()
+ val intent = Intent()
+ intent.addCategory(Intent.CATEGORY_LAUNCHER)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ intent.component = ComponentName("com.android.server.wm.flicker.testapp",
+ "com.android.server.wm.flicker.testapp.SeamlessRotationActivity")
+
+ intent.putExtra(ActivityOptions.EXTRA_STARVE_UI_THREAD, starveUiThread)
+
+ config.putParcelable(Intent::class.java.simpleName, intent)
+ return config
+ }
+
+ @JvmStatic
+ private fun FlickerTestRunnerFactory.getConfigurations(): List<Bundle> {
+ return this.getConfigRotationTests().flatMap {
+ val defaultRun = it.createConfig(starveUiThread = false)
+ val busyUiRun = it.createConfig(starveUiThread = true)
+ listOf(defaultRun, busyUiRun)
}
- repeat { 1 }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- instrumentation.targetContext.startActivity(intent)
- device.wait(Until.hasObject(By.pkg(intent.component?.packageName)
- .depth(0)), APP_LAUNCH_TIMEOUT)
- this.setRotation(beginRotation)
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val factory = FlickerTestRunnerFactory(instrumentation)
+ val configurations = factory.getConfigurations()
+ return factory.buildRotationTest(configurations) { configuration ->
+ withTestName {
+ buildTestTag("seamlessRotation_" + configuration.intentId,
+ app = null, configuration = configuration)
}
- }
- teardown {
- eachRun {
- stopPackage(
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ instrumentation.targetContext.startActivity(configuration.intent)
+ val searchQuery = By.pkg(configuration.intent?.component?.packageName)
+ .depth(0)
+ device.wait(Until.hasObject(searchQuery), APP_LAUNCH_TIMEOUT)
+ }
+ eachRun {
+ this.setRotation(configuration.startRotation)
+ }
+ }
+ teardown {
+ test {
+ this.setRotation(Surface.ROTATION_0)
+ stopPackage(
instrumentation.targetContext,
- intent.component?.packageName
- ?: error("Unable to determine package name for intent"))
- this.setRotation(Surface.ROTATION_0)
+ configuration.intent?.component?.packageName
+ ?: error("Unable to determine package name for intent"))
+ }
}
- }
- transitions {
- this.setRotation(endRotation)
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible(bugId = 140855415)
- statusBarWindowIsAlwaysVisible(bugId = 140855415)
+ transitions {
+ this.setRotation(configuration.endRotation)
}
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible(bugId = 140855415)
+ statusBarWindowIsAlwaysVisible(bugId = 140855415)
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible(bugId = 140855415)
- noUncoveredRegions(beginRotation, endRotation, allStates = true)
- navBarLayerRotatesAndScales(beginRotation, endRotation)
- statusBarLayerRotatesScales(beginRotation, endRotation, enabled = false)
- }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(configuration.startRotation,
+ configuration.endRotation, allStates = false
+ /*, bugId = 147659548*/)
+ navBarLayerRotatesAndScales(configuration.startRotation,
+ configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.startRotation,
+ configuration.endRotation, enabled = false)
+ }
- layersTrace {
- all("appLayerRotates"/*, bugId = 147659548*/) {
- val startingPos = WindowUtils.getDisplayBounds(beginRotation)
- val endingPos = WindowUtils.getDisplayBounds(endRotation)
-
- if (startingPos == endingPos) {
- this.hasVisibleRegion(
- intent.component?.packageName ?: "",
- startingPos)
- } else {
- this.hasVisibleRegion(intent.component?.packageName ?: "", startingPos)
+ layersTrace {
+ val startingBounds = WindowUtils
+ .getDisplayBounds(configuration.startRotation)
+ val endingBounds = WindowUtils
+ .getDisplayBounds(configuration.endRotation)
+
+ all("appLayerRotates"/*, bugId = 147659548*/) {
+ if (startingBounds == endingBounds) {
+ this.hasVisibleRegion(
+ configuration.intentPackageName, startingBounds)
+ } else {
+ this.hasVisibleRegion(configuration.intentPackageName,
+ startingBounds)
.then()
- .hasVisibleRegion(intent.component?.packageName
- ?: "", endingPos)
+ .hasVisibleRegion(configuration.intentPackageName,
+ endingBounds)
+ }
}
- }
- all("noUncoveredRegions"/*, bugId = 147659548*/) {
- val startingBounds = WindowUtils.getDisplayBounds(beginRotation)
- val endingBounds = WindowUtils.getDisplayBounds(endRotation)
- if (startingBounds == endingBounds) {
- this.coversAtLeastRegion(startingBounds)
- } else {
- this.coversAtLeastRegion(startingBounds)
+ all("noUncoveredRegions"/*, bugId = 147659548*/) {
+ if (startingBounds == endingBounds) {
+ this.coversAtLeastRegion(startingBounds)
+ } else {
+ this.coversAtLeastRegion(startingBounds)
.then()
.coversAtLeastRegion(endingBounds)
+ }
}
}
- }
- eventLog {
- focusDoesNotChange(bugId = 151179149)
- }
- }
- }
- }
-
- companion object {
- private const val APP_LAUNCH_TIMEOUT: Long = 10000
-
- // launch test activity that supports seamless rotation with a busy UI thread to miss frames
- // when the app is asked to redraw
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
- val params = mutableListOf<Array<Any>>()
- val testIntents = mutableListOf<Intent>()
-
- // launch test activity that supports seamless rotation
- var intent = Intent(Intent.ACTION_MAIN)
- intent.component = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME
- intent.flags = FLAG_ACTIVITY_NEW_TASK
- testIntents.add(intent)
-
- // launch test activity that supports seamless rotation with a busy UI thread to miss frames
- // when the app is asked to redraw
- intent = Intent(intent)
- intent.putExtra(ActivityOptions.EXTRA_STARVE_UI_THREAD, true)
- intent.flags = FLAG_ACTIVITY_NEW_TASK
- testIntents.add(intent)
- for (testIntent in testIntents) {
- for (begin in supportedRotations) {
- for (end in supportedRotations) {
- if (begin != end) {
- var testId: String = Surface.rotationToString(begin) +
- "_" + Surface.rotationToString(end)
- if (testIntent.extras?.getBoolean(
- ActivityOptions.EXTRA_STARVE_UI_THREAD) == true) {
- testId += "_" + "BUSY_UI_THREAD"
- }
- params.add(arrayOf(
- testId,
- testIntent,
- Surface.rotationToString(begin),
- Surface.rotationToString(end),
- begin,
- end))
- }
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
}
}
}
- return params
}
}
} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
index 3b5e6694ef0f..ae9fcf9a3b6e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/OpenAppToSplitScreenTest.kt
@@ -16,25 +16,32 @@
package com.android.server.wm.flicker.splitscreen
-import android.view.Surface
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.isInSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -43,78 +50,79 @@ import org.junit.runners.Parameterized
* Test open app to split screen.
* To run this test: `atest FlickerTests:OpenAppToSplitScreenTest`
*/
+@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 161435597)
class OpenAppToSplitScreenTest(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- @Test
- fun test() {
- val testApp = StandardAppHelper(instrumentation,
- "com.android.server.wm.flicker.testapp", "SimpleApp")
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
- flicker(instrumentation) {
- withTag { buildTestTag("appToSplitScreen", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- testApp.open()
- this.setRotation(rotation)
- }
- }
- teardown {
- eachRun {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
+ return FlickerTestRunnerFactory(instrumentation)
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("appToSplitScreen", testApp, configuration)
}
- }
- test {
- testApp.exit()
- }
- }
- transitions {
- device.launchSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.endRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ test {
+ testApp.exit()
+ }
+ }
+ transitions {
+ device.launchSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation, enabled = false)
- navBarLayerRotatesAndScales(rotation, bugId = 140855415)
- statusBarLayerRotatesScales(rotation)
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation, enabled = false)
+ navBarLayerRotatesAndScales(configuration.endRotation,
+ bugId = 140855415)
+ statusBarLayerRotatesScales(configuration.endRotation)
- all("dividerLayerBecomesVisible") {
- this.hidesLayer(DOCKED_STACK_DIVIDER)
- .then()
- .showsLayer(DOCKED_STACK_DIVIDER)
- }
- }
+ all("dividerLayerBecomesVisible") {
+ this.hidesLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .showsLayer(DOCKED_STACK_DIVIDER)
+ }
+ }
- eventLog {
- focusChanges(testApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity",
- bugId = 151179149)
+ eventLog {
+ focusChanges(testApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
+ }
+ }
}
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
index abf41a1fd408..4b9f02412f2a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/ResizeSplitScreenTest.kt
@@ -19,32 +19,39 @@ package com.android.server.wm.flicker.splitscreen
import android.graphics.Region
import android.util.Rational
import android.view.Surface
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
-import com.android.server.wm.flicker.FlickerTestBase
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
import com.android.server.wm.flicker.focusDoesNotChange
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.isInSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
import com.android.server.wm.flicker.helpers.resizeSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
/**
* Test split screen resizing window transitions.
@@ -53,138 +60,149 @@ import org.junit.runners.MethodSorters
* Currently it runs only in 0 degrees because of b/156100803
*/
@RequiresDevice
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 159096424)
-class ResizeSplitScreenTest : FlickerTestBase() {
- @Test
- fun test() {
- val testAppTop = StandardAppHelper(instrumentation,
- "com.android.server.wm.flicker.testapp", "SimpleApp")
- val testAppBottom = ImeAppHelper(instrumentation)
-
- flicker(instrumentation) {
- withTag {
- val description = (startRatio.toString().replace("/", "-") + "_to_" +
- stopRatio.toString().replace("/", "-"))
- buildTestTag("resizeSplitScreen", testAppTop, rotation,
- rotation, testAppBottom, description)
- }
- repeat { 1 }
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(rotation)
- this.launcherStrategy.clearRecentAppsFromOverview()
- testAppBottom.open()
- device.pressHome()
- testAppTop.open()
- device.waitForIdle()
- device.launchSplitScreen()
- val snapshot = device.findObject(By.res(device.launcherPackageName, "snapshot"))
- snapshot.click()
- testAppBottom.openIME(device)
- device.pressBack()
- device.resizeSplitScreen(startRatio)
- }
- }
- teardown {
- eachRun {
- device.exitSplitScreen()
- device.pressHome()
- testAppTop.exit()
- testAppBottom.exit()
- }
- test {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- }
- }
- transitions {
- device.resizeSplitScreen(stopRatio)
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
-
- all("topAppWindowIsAlwaysVisible", bugId = 156223549) {
- this.showsAppWindow(sSimpleActivity)
- }
-
- all("bottomAppWindowIsAlwaysVisible", bugId = 156223549) {
- this.showsAppWindow(sImeActivity)
- }
- }
-
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
+class ResizeSplitScreenTest(
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ private const val sSimpleActivity = "SimpleActivity"
+ private const val sImeActivity = "ImeActivity"
+ private val startRatio = Rational(1, 3)
+ private val stopRatio = Rational(2, 3)
- all("topAppLayerIsAlwaysVisible") {
- this.showsLayer(sSimpleActivity)
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testAppTop = StandardAppHelper(instrumentation,
+ "com.android.server.wm.flicker.testapp", "SimpleApp")
+ val testAppBottom = ImeAppHelper(instrumentation)
+
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName {
+ val description = (startRatio.toString().replace("/", "-") + "_to_" +
+ stopRatio.toString().replace("/", "-"))
+ buildTestTag("resizeSplitScreen", testAppTop.launcherName,
+ configuration.startRotation, configuration.endRotation,
+ testAppBottom.launcherName, description)
}
-
- all("bottomAppLayerIsAlwaysVisible") {
- this.showsLayer(sImeActivity)
+ repeat { configuration.repetitions }
+ setup {
+ eachRun {
+ device.wakeUpAndGoToHomeScreen()
+ this.setRotation(configuration.startRotation)
+ this.launcherStrategy.clearRecentAppsFromOverview()
+ testAppBottom.open()
+ device.pressHome()
+ testAppTop.open()
+ device.waitForIdle()
+ device.launchSplitScreen()
+ val snapshot =
+ device.findObject(By.res(device.launcherPackageName, "snapshot"))
+ snapshot.click()
+ testAppBottom.openIME(device)
+ device.pressBack()
+ device.resizeSplitScreen(startRatio)
+ }
}
-
- all("dividerLayerIsAlwaysVisible") {
- this.showsLayer(DOCKED_STACK_DIVIDER)
+ teardown {
+ eachRun {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ device.pressHome()
+ testAppTop.exit()
+ testAppBottom.exit()
+ }
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
}
-
- start("appsStartingBounds", enabled = false) {
- val displayBounds = WindowUtils.displayBounds
- val entry = this.trace.entries.firstOrNull()
- ?: throw IllegalStateException("Trace is empty")
- val dividerBounds = entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
-
- val topAppBounds = Region(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
- this.hasVisibleRegion("SimpleActivity", topAppBounds)
- .and()
- .hasVisibleRegion("ImeActivity", bottomAppBounds)
+ transitions {
+ device.resizeSplitScreen(stopRatio)
}
-
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.displayBounds
- val entry = this.trace.entries.lastOrNull()
- ?: throw IllegalStateException("Trace is empty")
- val dividerBounds = entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
-
- val topAppBounds = Region(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarHeight)
-
- this.hasVisibleRegion(sSimpleActivity, topAppBounds)
- .and()
- .hasVisibleRegion(sImeActivity, bottomAppBounds)
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+
+ all("topAppWindowIsAlwaysVisible", bugId = 156223549) {
+ this.showsAppWindow(sSimpleActivity)
+ }
+
+ all("bottomAppWindowIsAlwaysVisible", bugId = 156223549) {
+ this.showsAppWindow(sImeActivity)
+ }
+ }
+
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
+
+ all("topAppLayerIsAlwaysVisible") {
+ this.showsLayer(sSimpleActivity)
+ }
+
+ all("bottomAppLayerIsAlwaysVisible") {
+ this.showsLayer(sImeActivity)
+ }
+
+ all("dividerLayerIsAlwaysVisible") {
+ this.showsLayer(DOCKED_STACK_DIVIDER)
+ }
+
+ start("appsStartingBounds", enabled = false) {
+ val displayBounds = WindowUtils.displayBounds
+ val entry = this.trace.entries.firstOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerBounds =
+ entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+ val topAppBounds = Region(0, 0, dividerBounds.right,
+ dividerBounds.top + WindowUtils.dockedStackDividerInset)
+ val bottomAppBounds = Region(0,
+ dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.navigationBarHeight)
+ this.hasVisibleRegion("SimpleActivity", topAppBounds)
+ .and()
+ .hasVisibleRegion("ImeActivity", bottomAppBounds)
+ }
+
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.displayBounds
+ val entry = this.trace.entries.lastOrNull()
+ ?: throw IllegalStateException("Trace is empty")
+ val dividerBounds =
+ entry.getVisibleBounds(DOCKED_STACK_DIVIDER).bounds
+
+ val topAppBounds = Region(0, 0, dividerBounds.right,
+ dividerBounds.top + WindowUtils.dockedStackDividerInset)
+ val bottomAppBounds = Region(0,
+ dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.right,
+ displayBounds.bottom - WindowUtils.navigationBarHeight)
+
+ this.hasVisibleRegion(sSimpleActivity, topAppBounds)
+ .and()
+ .hasVisibleRegion(sImeActivity, bottomAppBounds)
+ }
+ }
+
+ eventLog {
+ focusDoesNotChange()
+ }
}
}
-
- eventLog {
- focusDoesNotChange()
- }
- }
}
}
-
- companion object {
- private const val sSimpleActivity = "SimpleActivity"
- private const val sImeActivity = "ImeActivity"
- private val rotation = Surface.ROTATION_0
- private val startRatio = Rational(1, 3)
- private val stopRatio = Rational(2, 3)
- }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
index 7447bdae9abe..f966a66aa0a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/splitscreen/SplitScreenToLauncherTest.kt
@@ -19,23 +19,29 @@ package com.android.server.wm.flicker.splitscreen
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.NonRotationTestBase
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.dsl.flicker
+import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.isInSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -49,82 +55,80 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SplitScreenToLauncherTest(
- rotationName: String,
- rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
- @Test
- fun test() {
- val testApp = StandardAppHelper(instrumentation,
+ testName: String,
+ flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val testApp = StandardAppHelper(instrumentation,
"com.android.server.wm.flicker.testapp", "SimpleApp")
- flicker(instrumentation) {
- withTag { buildTestTag("splitScreenToLauncher", testApp, rotation) }
- repeat { 1 }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- testApp.open()
- this.setRotation(rotation)
- device.launchSplitScreen()
- device.waitForIdle()
- }
- }
- teardown {
- eachRun {
- testApp.exit()
- }
- test {
- if (device.isInSplitScreen()) {
+ // b/161435597 causes the test not to work on 90 degrees
+ return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ .buildTest { configuration ->
+ withTestName {
+ buildTestTag("splitScreenToLauncher", testApp, configuration)
+ }
+ repeat { configuration.repetitions }
+ setup {
+ test {
+ device.wakeUpAndGoToHomeScreen()
+ }
+ eachRun {
+ testApp.open()
+ this.setRotation(configuration.endRotation)
+ device.launchSplitScreen()
+ device.waitForIdle()
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit()
+ }
+ test {
+ if (device.isInSplitScreen()) {
+ device.exitSplitScreen()
+ }
+ }
+ }
+ transitions {
device.exitSplitScreen()
}
- }
- }
- transitions {
- device.exitSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- }
+ assertions {
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
- layersTrace {
- navBarLayerIsAlwaysVisible()
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(rotation)
- navBarLayerRotatesAndScales(rotation)
- statusBarLayerRotatesScales(rotation)
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ noUncoveredRegions(configuration.endRotation)
+ navBarLayerRotatesAndScales(configuration.endRotation)
+ statusBarLayerRotatesScales(configuration.endRotation)
- // b/161435597 causes the test not to work on 90 degrees
- all("dividerLayerBecomesInvisible") {
- this.showsLayer(DOCKED_STACK_DIVIDER)
- .then()
- .hidesLayer(DOCKED_STACK_DIVIDER)
- }
+ // b/161435597 causes the test not to work on 90 degrees
+ all("dividerLayerBecomesInvisible") {
+ this.showsLayer(DOCKED_STACK_DIVIDER)
+ .then()
+ .hidesLayer(DOCKED_STACK_DIVIDER)
+ }
- all("appLayerBecomesInvisible") {
- this.showsLayer(testApp.getPackage())
- .then()
- .hidesLayer(testApp.getPackage())
- }
- }
+ all("appLayerBecomesInvisible") {
+ this.showsLayer(testApp.getPackage())
+ .then()
+ .hidesLayer(testApp.getPackage())
+ }
+ }
- eventLog {
- focusDoesNotChange(bugId = 151179149)
+ eventLog {
+ focusDoesNotChange(bugId = 151179149)
+ }
+ }
}
- }
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- // b/161435597 causes the test not to work on 90 degrees
- val supportedRotations = intArrayOf(Surface.ROTATION_0)
- return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 05a59ef7fc72..9a2def935f5d 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -745,7 +745,7 @@
</activity>
<activity android:name="BlurActivity"
- android:label="Shaders/Blur"
+ android:label="RenderEffect/Blur"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/tests/HwAccelerationTest/res/layout/image_filter_activity.xml b/tests/HwAccelerationTest/res/layout/image_filter_activity.xml
new file mode 100644
index 000000000000..a0ee67ae0bef
--- /dev/null
+++ b/tests/HwAccelerationTest/res/layout/image_filter_activity.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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/image_filter_test_view"
+ android:background="#FF0000"
+ android:layout_width="200dp"
+ android:layout_height="200dp" />
+</FrameLayout> \ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java
index 033fb0ec35d2..e4ca7881f796 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java
@@ -18,11 +18,12 @@ package com.android.test.hwui;
import android.app.Activity;
import android.content.Context;
-import android.graphics.BlurShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
import android.graphics.Shader;
import android.os.Bundle;
import android.view.Gravity;
@@ -51,16 +52,27 @@ public class BlurActivity extends Activity {
}
public static class BlurGradientView extends View {
- private BlurShader mBlurShader = null;
- private Paint mPaint;
+ private final float mBlurRadius = 25f;
+ private final Paint mPaint;
+ private final RenderNode mRenderNode;
public BlurGradientView(Context c) {
super(c);
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mRenderNode = new RenderNode("BlurGradientView");
+ mRenderNode.setRenderEffect(
+ RenderEffect.createBlurEffect(
+ mBlurRadius,
+ mBlurRadius,
+ null,
+ Shader.TileMode.DECAL
+ )
+ );
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (changed || mBlurShader == null) {
+ if (changed) {
LinearGradient gradient = new LinearGradient(
0f,
0f,
@@ -70,41 +82,81 @@ public class BlurActivity extends Activity {
Color.YELLOW,
Shader.TileMode.CLAMP
);
- mBlurShader = new BlurShader(30f, 40f, gradient);
- mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setShader(mBlurShader);
+
+ mPaint.setShader(gradient);
+
+ final int width = right - left;
+ final int height = bottom - top;
+ mRenderNode.setPosition(0, 0, width, height);
+
+ Canvas canvas = mRenderNode.beginRecording();
+ canvas.drawRect(
+ mBlurRadius * 2,
+ mBlurRadius * 2,
+ width - mBlurRadius * 2,
+ height - mBlurRadius * 2,
+ mPaint
+ );
+ mRenderNode.endRecording();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
+ canvas.drawRenderNode(mRenderNode);
}
}
public static class BlurView extends View {
- private final BlurShader mBlurShader;
private final Paint mPaint;
+ private final RenderNode mRenderNode;
+ private final float mBlurRadius = 20f;
public BlurView(Context c) {
super(c);
- mBlurShader = new BlurShader(20f, 20f, null);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mPaint.setShader(mBlurShader);
+ mRenderNode = new RenderNode("blurNode");
+ mRenderNode.setRenderEffect(
+ RenderEffect.createBlurEffect(
+ mBlurRadius,
+ mBlurRadius,
+ null,
+ Shader.TileMode.DECAL
+ )
+ );
}
@Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ int width = right - left;
+ int height = bottom - top;
+ mRenderNode.setPosition(0, 0, width, height);
+ Canvas canvas = mRenderNode.beginRecording(width, height);
+ mPaint.setColor(Color.BLUE);
+
+ canvas.drawRect(
+ mBlurRadius * 2,
+ mBlurRadius * 2,
+ width - mBlurRadius * 2,
+ height - mBlurRadius * 2,
+ mPaint
+ );
- mPaint.setColor(Color.BLUE);
- canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
+ mPaint.setColor(Color.RED);
+ canvas.drawCircle((right - left) / 2f, (bottom - top) / 2f, 50f, mPaint);
+
+ mRenderNode.endRecording();
+ }
+ }
- mPaint.setColor(Color.RED);
- canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, 50f, mPaint);
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRenderNode(mRenderNode);
}
}
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
index 08144c845555..b53b78a8d5b2 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java
@@ -22,6 +22,7 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -30,6 +31,7 @@ import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.RuntimeShader;
+import android.graphics.Shader;
import android.os.Bundle;
import android.view.View;
@@ -59,9 +61,10 @@ public class ColorFiltersMutateActivity extends Activity {
private int mPorterDuffColor = 0;
static final String sSkSL =
- "uniform float param1;\n"
- + "void main(float2 xy, inout half4 color) {\n"
- + "color = half4(color.r, half(param1), color.b, 1.0);\n"
+ "in shader bitmapShader;\n"
+ + "uniform float param1;\n"
+ + "half4 main(float2 xy) {\n"
+ + " return half4(sample(bitmapShader, xy).rgb, param1);\n"
+ "}\n";
private byte[] mUniforms = new byte[4];
@@ -84,7 +87,9 @@ public class ColorFiltersMutateActivity extends Activity {
mBlendPaint.setColorFilter(new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_OVER));
mShaderPaint = new Paint();
- mShaderPaint.setShader(new RuntimeShader(sSkSL, mUniforms, true));
+ Shader[] inputShaders = { new BitmapShader(mBitmap1, Shader.TileMode.CLAMP,
+ Shader.TileMode.CLAMP) };
+ mShaderPaint.setShader(new RuntimeShader(sSkSL, mUniforms, inputShaders, true));
setShaderParam1(0.0f);
ObjectAnimator sat = ObjectAnimator.ofFloat(this, "saturation", 1.0f);
diff --git a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
index 026677e09bed..99dde859028a 100644
--- a/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
+++ b/tests/ManagedProfileLifecycleStressTest/src/com/android/test/stress/ManagedProfileLifecycleStressTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -62,13 +63,62 @@ public class ManagedProfileLifecycleStressTest extends BaseHostJUnit4Test {
CLog.w("Iteration N" + iteration);
final int userId = createManagedProfile();
startUser(userId);
- installPackageAsUser(DUMMY_DPC_APK, true /* grantPermissions */, userId, "-t");
+ installPackageAsUser(
+ DUMMY_DPC_APK, /* grantPermissions= */true, userId, /* options= */"-t");
setProfileOwner(DUMMY_DPC_COMPONENT, userId);
removeUser(userId);
}
CLog.w("Completed " + iteration + " iterations.");
}
+ /**
+ * Create, start, and kill managed profiles in a loop with waitForBroadcastIdle after each user
+ * operation.
+ */
+ @Test
+ public void testCreateStartDeleteStable() throws Exception {
+ // Disable package verifier for ADB installs.
+ getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
+ int iteration = 0;
+ final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(TIME_LIMIT_MINUTES);
+ while (System.nanoTime() < deadline) {
+ iteration++;
+ CLog.w("Iteration N" + iteration);
+ final int userId = createManagedProfile();
+ waitForBroadcastIdle();
+
+ startUser(userId);
+ waitForBroadcastIdle();
+
+ installPackageAsUser(
+ DUMMY_DPC_APK, /* grantPermissions= */true, userId, /* options= */"-t");
+
+ setProfileOwner(DUMMY_DPC_COMPONENT, userId);
+
+ removeUser(userId);
+ waitForBroadcastIdle();
+ }
+ CLog.w("Completed " + iteration + " iterations.");
+ }
+
+ private void waitForBroadcastIdle() throws Exception {
+ final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ // We allow 8min for the command to complete and 4min for the command to start to
+ // output something.
+ getDevice().executeShellCommand(
+ "am wait-for-broadcast-idle",
+ receiver,
+ /* maxTimeoutForCommand= */8,
+ /* maxTimeoutToOutputShellResponse= */4,
+ TimeUnit.MINUTES,
+ /* retryAttempts= */0);
+ final String output = receiver.getOutput();
+ if (!output.contains("All broadcast queues are idle!")) {
+ CLog.e("Output from 'am wait-for-broadcast-idle': %s", output);
+ fail("'am wait-for-broadcase-idle' did not complete.");
+ }
+ }
+
private int createManagedProfile() throws Exception {
final String output = getDevice().executeShellCommand(
"pm create-user --profileOf 0 --managed TestProfile");
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index 0f62c4fa66a3..e9227e94da98 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -107,7 +107,10 @@ class PlatformCompatCommandNotInstalledTest {
fun ParcelFileDescriptor.text() = FileReader(fileDescriptor).readText()
@After
- fun resetIdentity() = uiAutomation.dropShellPermissionIdentity()
+ fun resetChangeIdAndIdentity() {
+ command("am compat reset $TEST_CHANGE_ID $TEST_PKG")
+ uiAutomation.dropShellPermissionIdentity()
+ }
@Test
fun execute() {
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 ab2e4923c88e..52718bec9148 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -482,6 +482,26 @@ public class StagedRollbackTest {
}
@Test
+ public void testRollbackApkDataDirectories_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ }
+
+ @Test
+ public void testRollbackApkDataDirectories_Phase2() throws Exception {
+ Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+ }
+
+ @Test
+ public void testRollbackApkDataDirectories_Phase3() throws Exception {
+ RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+ RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ }
+
+ @Test
public void isCheckpointSupported() {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
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 c2fd0c39221e..6662b0e199fb 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
@@ -18,6 +18,8 @@ package com.android.tests.rollback.host;
import static com.android.tests.rollback.host.WatchdogEventLogger.watchdogEventOccurred;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.Log;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.IFileEntry;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -51,6 +54,7 @@ import java.util.stream.Collectors;
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class StagedRollbackTest extends BaseHostJUnit4Test {
+ private static final String TAG = "StagedRollbackTest";
private static final int NATIVE_CRASHES_THRESHOLD = 5;
/**
@@ -272,6 +276,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback");
// Only check directories newly created during the test
after.removeAll(before);
+ // There should be only one /data/misc_ce/0/rollback/<rollbackId> created during test
+ assertThat(after).hasSize(1);
after.forEach(dir -> assertDirectoryIsEmpty(dir));
}
@@ -358,6 +364,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
List<String> after = getSnapshotDirectories("/data/misc/apexrollback");
// Only check directories newly created during the test
after.removeAll(before);
+ // There should be only one /data/misc/apexrollback/<rollbackId> created during test
+ assertThat(after).hasSize(1);
after.forEach(dir -> assertDirectoryIsEmpty(dir));
}
@@ -405,6 +413,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback");
// Only check directories newly created during the test
after.removeAll(before);
+ // There should be only one /data/misc_de/0/apexrollback/<rollbackId> created during test
+ assertThat(after).hasSize(1);
after.forEach(dir -> assertDirectoryIsEmpty(dir));
}
@@ -450,9 +460,48 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
// Only check directories newly created during the test
after.removeAll(before);
+ // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
+ assertThat(after).hasSize(1);
after.forEach(dir -> assertDirectoryIsEmpty(dir));
}
+ /**
+ * Tests that data in DE apk data directory is restored when apk is rolled back.
+ */
+ @Test
+ public void testRollbackApkDataDirectories_De() throws Exception {
+ // Install version 1 of TESTAPP_A
+ runPhase("testRollbackApkDataDirectories_Phase1");
+
+ // Push files to apk data directory
+ String oldFilePath1 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_1;
+ String oldFilePath2 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_2;
+ assertTrue(getDevice().pushString(TEST_STRING_1, oldFilePath1));
+ assertTrue(getDevice().pushString(TEST_STRING_2, oldFilePath2));
+
+ // Install version 2 of TESTAPP_A with rollback enabled
+ runPhase("testRollbackApkDataDirectories_Phase2");
+ getDevice().reboot();
+
+ // Replace files in data directory
+ getDevice().deleteFile(oldFilePath1);
+ getDevice().deleteFile(oldFilePath2);
+ String newFilePath3 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_3;
+ String newFilePath4 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_4;
+ assertTrue(getDevice().pushString(TEST_STRING_3, newFilePath3));
+ assertTrue(getDevice().pushString(TEST_STRING_4, newFilePath4));
+
+ // Roll back the APK
+ runPhase("testRollbackApkDataDirectories_Phase3");
+ getDevice().reboot();
+
+ // Verify that old files have been restored and new files are gone
+ assertEquals(TEST_STRING_1, getDevice().pullFileContents(oldFilePath1));
+ assertEquals(TEST_STRING_2, getDevice().pullFileContents(oldFilePath2));
+ assertNull(getDevice().pullFile(newFilePath3));
+ assertNull(getDevice().pullFile(newFilePath4));
+ }
+
@Test
public void testExpireApexRollback() throws Exception {
List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
@@ -472,6 +521,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback");
// Only check directories newly created during the test
after.removeAll(before);
+ // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test
+ assertThat(after).hasSize(1);
// Expire all rollbacks and check CE snapshot directories are deleted
runPhase("testCleanUp");
for (String dir : after) {
@@ -503,16 +554,22 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName);
}
- private List<String> getSnapshotDirectories(String baseDir) {
- try {
- return getDevice().getFileEntry(baseDir).getChildren(false)
- .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?"))
- .map(entry -> entry.getFullPath())
- .collect(Collectors.toList());
- } catch (Exception e) {
- // Return an empty list if any error
+ private static String apkDataDirDe(String apexName, int userId) {
+ return String.format("/data/user_de/%d/%s", userId, apexName);
+ }
+
+ private List<String> getSnapshotDirectories(String baseDir) throws Exception {
+ IFileEntry f = getDevice().getFileEntry(baseDir);
+ if (f == null) {
+ Log.d(TAG, "baseDir doesn't exist: " + baseDir);
return Collections.EMPTY_LIST;
}
+ List<String> list = f.getChildren(false)
+ .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?"))
+ .map(entry -> entry.getFullPath())
+ .collect(Collectors.toList());
+ Log.d(TAG, "getSnapshotDirectories=" + list);
+ return list;
}
private void assertDirectoryIsEmpty(String path) {
diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml
index 050e9c33aeac..c30d76137f76 100644
--- a/tests/SilkFX/AndroidManifest.xml
+++ b/tests/SilkFX/AndroidManifest.xml
@@ -4,9 +4,9 @@
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.
@@ -40,7 +40,14 @@
android:label="Glow Examples"/>
<activity android:name=".materials.GlassActivity"
- android:label="Glass Examples"/>
+ android:label="Glass Examples"
+ android:banner="@drawable/background1"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/SilkFX/res/layout-television/activity_glass.xml b/tests/SilkFX/res/layout-television/activity_glass.xml
new file mode 100644
index 000000000000..1f566860da3d
--- /dev/null
+++ b/tests/SilkFX/res/layout-television/activity_glass.xml
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <ImageView
+ android:id="@+id/background"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:scaleType="matrix"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:srcCompat="@drawable/background1" />
+
+ <com.android.test.silkfx.materials.GlassView
+ android:id="@+id/materialView"
+ android:layout_width="400dp"
+ android:layout_height="100dp"
+ android:layout_marginEnd="64dp"
+ android:layout_marginStart="64dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/background"
+ app:layout_constraintStart_toStartOf="@id/background"
+ app:layout_constraintTop_toTopOf="parent">
+ <TextView
+ android:id="@+id/textOverlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="18dp"
+ android:layout_gravity="center"
+ android:textColor="#ffffff"
+ android:text="Lorem Ipsum dolor sit amet." />
+ </com.android.test.silkfx.materials.GlassView>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/bottomPanel"
+ android:layout_width="400dp"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorBackground"
+ android:paddingTop="24dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent">
+
+ <SeekBar
+ android:id="@+id/materialOpacity"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="16dp"
+ android:max="100"
+ android:progress="12"
+ app:layout_constraintBottom_toTopOf="@+id/scrimOpacityTitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <SeekBar
+ android:id="@+id/zoom"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginStart="12dp"
+ android:min="-100"
+ android:max="100"
+ android:progress="-15"
+ app:layout_constraintBottom_toTopOf="@+id/blurRadiusTitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <SeekBar
+ android:id="@+id/blurRadius"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginStart="12dp"
+ android:max="150"
+ android:progress="40"
+ app:layout_constraintBottom_toTopOf="@+id/materialOpacityTitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <SeekBar
+ android:id="@+id/scrimOpacity"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="16dp"
+ android:max="100"
+ android:progress="50"
+ app:layout_constraintBottom_toTopOf="@+id/noiseOpacityTitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1.0"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <SeekBar
+ android:id="@+id/noiseOpacity"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginBottom="24dp"
+ android:max="100"
+ android:progress="15"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0.0"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/scrimOpacityTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:text="Scrim Opacity"
+ android:textColor="@android:color/white"
+ app:layout_constraintBottom_toTopOf="@+id/scrimOpacity"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/materialOpacityTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:text="Soft light Opacity"
+ android:textColor="@android:color/white"
+ app:layout_constraintBottom_toTopOf="@+id/materialOpacity"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/zoomTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:text="Zoom"
+ android:textColor="@android:color/white"
+ app:layout_constraintBottom_toTopOf="@+id/zoom"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/blurRadiusTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:text="Blur Radius"
+ android:textColor="@android:color/white"
+ app:layout_constraintBottom_toTopOf="@+id/blurRadius"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/noiseOpacityTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:textColor="@android:color/white"
+ android:text="Noise Opacity"
+ app:layout_constraintBottom_toTopOf="@+id/noiseOpacity"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <ImageView
+ android:id="@+id/background1"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="16dp"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:onClick="onBackgroundClick"
+ android:scaleType="centerCrop"
+ app:layout_constraintBottom_toTopOf="@+id/lightMaterialSwitch"
+ app:layout_constraintStart_toStartOf="parent"
+ android:src="@drawable/background1" />
+
+ <ImageView
+ android:id="@+id/background2"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:layout_marginStart="8dp"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:onClick="onBackgroundClick"
+ android:scaleType="centerCrop"
+ app:layout_constraintBottom_toBottomOf="@+id/background1"
+ app:layout_constraintStart_toEndOf="@+id/background1"
+ android:src="@drawable/background2" />
+
+ <ImageView
+ android:id="@+id/background3"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:layout_marginStart="8dp"
+ android:scaleType="centerCrop"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:onClick="onBackgroundClick"
+ app:layout_constraintBottom_toBottomOf="@+id/background1"
+ app:layout_constraintStart_toEndOf="@+id/background2"
+ android:src="@drawable/background3" />
+
+ <Button
+ android:id="@+id/pickImage"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:layout_marginStart="8dp"
+ android:scaleType="centerCrop"
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:clickable="true"
+ android:onClick="onPickImageClick"
+ app:layout_constraintBottom_toBottomOf="@+id/background1"
+ app:layout_constraintStart_toEndOf="@+id/background3"
+ android:text="Pick file" />
+
+ <Switch
+ android:id="@+id/lightMaterialSwitch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="24dp"
+ android:layout_marginBottom="8dp"
+ android:text="Light Material"
+ app:layout_constraintBottom_toTopOf="@+id/zoomTitle"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/blurRadiusValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:layout_marginLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/blurRadiusTitle"
+ app:layout_constraintStart_toEndOf="@+id/blurRadiusTitle" />
+
+ <TextView
+ android:id="@+id/zoomValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:layout_marginLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/zoomTitle"
+ app:layout_constraintStart_toEndOf="@+id/zoomTitle" />
+
+ <TextView
+ android:id="@+id/materialOpacityValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:layout_marginLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/materialOpacityTitle"
+ app:layout_constraintStart_toEndOf="@+id/materialOpacityTitle" />
+
+ <TextView
+ android:id="@+id/noiseOpacityValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:layout_marginLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/noiseOpacityTitle"
+ app:layout_constraintStart_toEndOf="@+id/noiseOpacityTitle" />
+
+
+ <TextView
+ android:id="@+id/scrimOpacityValue"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="TextView"
+ android:layout_marginLeft="8dp"
+ app:layout_constraintBottom_toBottomOf="@+id/scrimOpacityTitle"
+ app:layout_constraintStart_toEndOf="@+id/scrimOpacityTitle" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt
index 711758476a62..2f2578b87f35 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt
@@ -20,13 +20,13 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapShader
import android.graphics.BlendMode
-import android.graphics.BlurShader
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Outline
import android.graphics.Paint
-import android.graphics.RadialGradient
import android.graphics.Rect
+import android.graphics.RenderEffect
+import android.graphics.RenderNode
import android.graphics.Shader
import android.hardware.Sensor
import android.hardware.SensorEvent
@@ -36,7 +36,6 @@ import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
-import com.android.internal.graphics.ColorUtils
import com.android.test.silkfx.R
import kotlin.math.sin
import kotlin.math.sqrt
@@ -152,10 +151,19 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont
var blurRadius = 150f
set(value) {
field = value
- blurPaint.shader = BlurShader(value, value, null)
+ renderNode.setRenderEffect(
+ RenderEffect.createBlurEffect(value, value, Shader.TileMode.CLAMP))
invalidate()
}
+ private var renderNodeIsDirty = true
+ private val renderNode = RenderNode("GlassRenderNode")
+
+ override fun invalidate() {
+ renderNodeIsDirty = true
+ super.invalidate()
+ }
+
init {
setWillNotDraw(false)
materialPaint.blendMode = BlendMode.SOFT_LIGHT
@@ -164,7 +172,6 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont
scrimPaint.alpha = (scrimOpacity * 255).toInt()
noisePaint.alpha = (noiseOpacity * 255).toInt()
materialPaint.alpha = (materialOpacity * 255).toInt()
- blurPaint.shader = BlurShader(blurRadius, blurRadius, null)
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View?, outline: Outline?) {
outline?.setRoundRect(Rect(0, 0, width, height), 100f)
@@ -184,20 +191,8 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont
}
override fun onDraw(canvas: Canvas?) {
- src.set(-width / 2, -height / 2, width / 2, height / 2)
- src.scale(1.0f + zoom)
- val centerX = left + width / 2
- val centerY = top + height / 2
- val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt()
- val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt()
- src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset,
- src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset)
-
- dst.set(0, 0, width, height)
- canvas?.drawBitmap(backgroundBitmap, src, dst, blurPaint)
- canvas?.drawRect(dst, materialPaint)
- canvas?.drawRect(dst, noisePaint)
- canvas?.drawRect(dst, scrimPaint)
+ updateGlassRenderNode()
+ canvas?.drawRenderNode(renderNode)
}
fun resetGyroOffsets() {
@@ -205,4 +200,31 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont
gyroYRotation = 0f
invalidate()
}
+
+ private fun updateGlassRenderNode() {
+ if (renderNodeIsDirty) {
+ renderNode.setPosition(0, 0, getWidth(), getHeight())
+
+ val canvas = renderNode.beginRecording()
+
+ src.set(-width / 2, -height / 2, width / 2, height / 2)
+ src.scale(1.0f + zoom)
+ val centerX = left + width / 2
+ val centerY = top + height / 2
+ val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt()
+ val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt()
+ src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset,
+ src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset)
+
+ dst.set(0, 0, width, height)
+ canvas.drawBitmap(backgroundBitmap, src, dst, blurPaint)
+ canvas.drawRect(dst, materialPaint)
+ canvas.drawRect(dst, noisePaint)
+ canvas.drawRect(dst, scrimPaint)
+
+ renderNode.endRecording()
+
+ renderNodeIsDirty = false
+ }
+ }
} \ No newline at end of file
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
index 9324ba0b8b72..380e29984c63 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -31,6 +30,7 @@ import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.soundtrigger.SoundTriggerDetector;
+import android.media.soundtrigger.SoundTriggerManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
@@ -232,8 +232,8 @@ public class SoundTriggerTestService extends Service {
public AudioTrack captureAudioTrack;
}
- private GenericSoundModel createNewSoundModel(ModelInfo modelInfo) {
- return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
+ private SoundTriggerManager.Model createNewSoundModel(ModelInfo modelInfo) {
+ return SoundTriggerManager.Model.create(modelInfo.modelUuid, modelInfo.vendorUuid,
modelInfo.modelData);
}
@@ -246,16 +246,16 @@ public class SoundTriggerTestService extends Service {
postMessage("Loading model: " + modelInfo.name);
- GenericSoundModel soundModel = createNewSoundModel(modelInfo);
+ SoundTriggerManager.Model soundModel = createNewSoundModel(modelInfo);
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(soundModel);
if (status) {
postToast("Successfully loaded " + modelInfo.name + ", UUID="
- + soundModel.getUuid());
+ + soundModel.getModelUuid());
setModelState(modelInfo, "Loaded");
} else {
postErrorToast("Failed to load " + modelInfo.name + ", UUID="
- + soundModel.getUuid() + "!");
+ + soundModel.getModelUuid() + "!");
setModelState(modelInfo, "Failed to load");
}
}
@@ -269,7 +269,7 @@ public class SoundTriggerTestService extends Service {
postMessage("Unloading model: " + modelInfo.name);
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ SoundTriggerManager.Model soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
if (soundModel == null) {
postErrorToast("Sound model not found for " + modelInfo.name + "!");
return;
@@ -278,11 +278,11 @@ public class SoundTriggerTestService extends Service {
boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
if (status) {
postToast("Successfully unloaded " + modelInfo.name + ", UUID="
- + soundModel.getUuid());
+ + soundModel.getModelUuid());
setModelState(modelInfo, "Unloaded");
} else {
postErrorToast("Failed to unload " +
- modelInfo.name + ", UUID=" + soundModel.getUuid() + "!");
+ modelInfo.name + ", UUID=" + soundModel.getModelUuid() + "!");
setModelState(modelInfo, "Failed to unload");
}
}
@@ -294,12 +294,12 @@ public class SoundTriggerTestService extends Service {
return;
}
postMessage("Reloading model: " + modelInfo.name);
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ SoundTriggerManager.Model soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
if (soundModel == null) {
postErrorToast("Sound model not found for " + modelInfo.name + "!");
return;
}
- GenericSoundModel updated = createNewSoundModel(modelInfo);
+ SoundTriggerManager.Model updated = createNewSoundModel(modelInfo);
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
if (status) {
postToast("Successfully reloaded " + modelInfo.name + ", UUID="
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 6b89b62bb1e3..cfe8c855ac81 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -18,7 +18,6 @@ package com.android.test.soundtrigger;
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
import android.os.RemoteException;
@@ -37,13 +36,10 @@ import java.util.UUID;
public class SoundTriggerUtil {
private static final String TAG = "SoundTriggerTestUtil";
- private final ISoundTriggerService mSoundTriggerService;
private final SoundTriggerManager mSoundTriggerManager;
private final Context mContext;
public SoundTriggerUtil(Context context) {
- mSoundTriggerService = ISoundTriggerService.Stub.asInterface(
- ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
mSoundTriggerManager = (SoundTriggerManager) context.getSystemService(
Context.SOUND_TRIGGER_SERVICE);
mContext = context;
@@ -55,15 +51,11 @@ public class SoundTriggerUtil {
*
* @param soundModel The sound model to add/update.
*/
- public boolean addOrUpdateSoundModel(GenericSoundModel soundModel) {
- try {
- if (soundModel == null) {
- throw new RuntimeException("Bad sound model");
- }
- mSoundTriggerService.updateSoundModel(soundModel);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in updateSoundModel", e);
+ public boolean addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
+ if (soundModel == null) {
+ throw new RuntimeException("Bad sound model");
}
+ mSoundTriggerManager.updateModel(soundModel);
return true;
}
@@ -71,19 +63,15 @@ public class SoundTriggerUtil {
* Gets the sound model for the given keyphrase, null if none exists.
* If a sound model for a given keyphrase exists, and it needs to be updated,
* it should be obtained using this method, updated and then passed in to
- * {@link #addOrUpdateSoundModel(GenericSoundModel)} without changing the IDs.
+ * {@link #addOrUpdateSoundModel(SoundTriggerManager.Model)} without changing the IDs.
*
* @param modelId The model ID to look-up the sound model for.
* @return The sound model if one was found, null otherwise.
*/
@Nullable
- public GenericSoundModel getSoundModel(UUID modelId) {
- GenericSoundModel model = null;
- try {
- model = mSoundTriggerService.getSoundModel(new ParcelUuid(modelId));
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
- }
+ public SoundTriggerManager.Model getSoundModel(UUID modelId) {
+ SoundTriggerManager.Model model = null;
+ model = mSoundTriggerManager.getModel(modelId);
if (model == null) {
Log.w(TAG, "No models present for the given keyphrase ID");
@@ -100,12 +88,7 @@ public class SoundTriggerUtil {
* @return {@code true} if the call succeeds, {@code false} otherwise.
*/
public boolean deleteSoundModel(UUID modelId) {
- try {
- mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in deleteSoundModel");
- return false;
- }
+ mSoundTriggerManager.deleteModel(modelId);
return true;
}
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
new file mode 100644
index 000000000000..647da2abd213
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -0,0 +1,58 @@
+// 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.
+
+android_test {
+ name: "SurfaceViewBufferTests",
+ srcs: ["**/*.java","**/*.kt"],
+ manifest: "AndroidManifest.xml",
+ test_config: "AndroidTest.xml",
+ platform_apis: true,
+ certificate: "platform",
+ use_embedded_native_libs: true,
+ jni_libs: [
+ "libsurface_jni",
+ ],
+
+ static_libs: [
+ "androidx.appcompat_appcompat",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "kotlin-stdlib",
+ "kotlinx-coroutines-android",
+ "flickerlib",
+ "truth-prebuilt",
+ ],
+}
+
+cc_library_shared {
+ name: "libsurface_jni",
+ srcs: [
+ "cpp/SurfaceProxy.cpp",
+ ],
+ shared_libs: [
+ "libutils",
+ "libgui",
+ "liblog",
+ "libandroid",
+ ],
+ include_dirs: [
+ "system/core/include"
+ ],
+ stl: "libc++_static",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+}
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
new file mode 100644
index 000000000000..95885c1ca635
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test">
+
+ <uses-sdk android:minSdkVersion="29"
+ android:targetSdkVersion="29"/>
+ <!-- Enable / Disable tracing !-->
+ <uses-permission android:name="android.permission.DUMP" />
+ <!-- Enable / Disable sv blast adapter !-->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+ <application android:allowBackup="false"
+ android:supportsRtl="true">
+ <activity android:name=".MainActivity"
+ android:taskAffinity="com.android.test.MainActivity"
+ android:theme="@style/AppTheme"
+ android:label="SurfaceViewBufferTestApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.test"
+ android:label="SurfaceViewBufferTests">
+ </instrumentation>
+</manifest>
diff --git a/tests/SurfaceViewBufferTests/AndroidTest.xml b/tests/SurfaceViewBufferTests/AndroidTest.xml
new file mode 100644
index 000000000000..b73fe4853ecf
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs SurfaceView Buffer Tests">
+ <option name="test-tag" value="SurfaceViewBufferTests" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <!-- keeps the screen on during tests -->
+ <option name="screen-always-on" value="on" />
+ <!-- prevents the phone from restarting -->
+ <option name="force-skip-system-props" value="true" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="false"/>
+ <option name="test-file-name" value="SurfaceViewBufferTests.apk"/>
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.test"/>
+ <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+ <option name="shell-timeout" value="6600s" />
+ <option name="test-timeout" value="6000s" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
new file mode 100644
index 000000000000..0c86524293e7
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#include <android/log.h>
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <android/window.h>
+#include <gui/Surface.h>
+#include <jni.h>
+#include <system/window.h>
+#include <utils/RefBase.h>
+#include <cassert>
+#include <chrono>
+#include <thread>
+
+#define TAG "SurfaceViewBufferTests"
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
+
+extern "C" {
+int i = 0;
+static ANativeWindow* sAnw;
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass,
+ jobject surfaceObject) {
+ sAnw = ANativeWindow_fromSurface(env, surfaceObject);
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+ surface->enableFrameTimestamps(true);
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed(
+ JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) {
+ using namespace std::chrono_literals;
+ assert(sAnw);
+ android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+
+ uint64_t frameNumber = static_cast<uint64_t>(jFrameNumber);
+ nsecs_t outRequestedPresentTime, outAcquireTime, outLatchTime, outFirstRefreshStartTime;
+ nsecs_t outLastRefreshStartTime, outGlCompositionDoneTime, outDequeueReadyTime;
+ nsecs_t outDisplayPresentTime = -1;
+ nsecs_t outReleaseTime;
+
+ auto start = std::chrono::steady_clock::now();
+ while (outDisplayPresentTime < 0) {
+ std::this_thread::sleep_for(8ms);
+ surface->getFrameTimestamps(frameNumber, &outRequestedPresentTime, &outAcquireTime,
+ &outLatchTime, &outFirstRefreshStartTime,
+ &outLastRefreshStartTime, &outGlCompositionDoneTime,
+ &outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime);
+ if (outDisplayPresentTime < 0) {
+ auto end = std::chrono::steady_clock::now();
+ if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() >
+ timeoutSec) {
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_draw(JNIEnv*, jclass) {
+ assert(sAnw);
+ ANativeWindow_Buffer outBuffer;
+ ANativeWindow_lock(sAnw, &outBuffer, nullptr);
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowLock(JNIEnv*, jclass) {
+ assert(sAnw);
+ ANativeWindow_Buffer outBuffer;
+ ANativeWindow_lock(sAnw, &outBuffer, nullptr);
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowUnlockAndPost(JNIEnv*,
+ jclass) {
+ assert(sAnw);
+ ANativeWindow_unlockAndPost(sAnw);
+ return 0;
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersGeometry(
+ JNIEnv* /* env */, jclass /* clazz */, jobject /* surfaceObject */, jint w, jint h,
+ jint format) {
+ assert(sAnw);
+ return ANativeWindow_setBuffersGeometry(sAnw, w, h, format);
+}
+} \ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/res/values/styles.xml b/tests/SurfaceViewBufferTests/res/values/styles.xml
new file mode 100644
index 000000000000..8b50738a06de
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 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.
+-->
+<resources>
+<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <item name="windowNoTitle">true</item>
+ <item name="windowActionBar">false</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowDisablePreview">true</item>
+</style>
+</resources>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
new file mode 100644
index 000000000000..b1e1336c4f6d
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/MainActivity.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.test
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.Gravity
+import android.view.Surface
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.widget.FrameLayout
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+class MainActivity : Activity() {
+ val mSurfaceProxy = SurfaceProxy()
+ private var mSurfaceHolder: SurfaceHolder? = null
+ private val mDrawLock = ReentrantLock()
+
+ val surface: Surface? get() = mSurfaceHolder?.surface
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ addSurfaceView(Rect(0, 0, 500, 200))
+ }
+
+ fun addSurfaceView(size: Rect): CountDownLatch {
+ val layout = findViewById<FrameLayout>(android.R.id.content)
+ val surfaceReadyLatch = CountDownLatch(1)
+ val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
+ layout.addView(surfaceView,
+ FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT)
+ .also { it.setMargins(100, 100, 0, 0) })
+ return surfaceReadyLatch
+ }
+
+ private fun createSurfaceView(
+ context: Context,
+ size: Rect,
+ surfaceReadyLatch: CountDownLatch
+ ): SurfaceView {
+ val surfaceView = SurfaceView(context)
+ surfaceView.setWillNotDraw(false)
+ surfaceView.holder.setFixedSize(size.width(), size.height())
+ surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ mDrawLock.withLock {
+ mSurfaceHolder = holder
+ mSurfaceProxy.setSurface(holder.surface)
+ }
+ surfaceReadyLatch.countDown()
+ }
+
+ override fun surfaceChanged(
+ holder: SurfaceHolder,
+ format: Int,
+ width: Int,
+ height: Int
+ ) {
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ mDrawLock.withLock {
+ mSurfaceHolder = null
+ }
+ }
+ })
+ return surfaceView
+ }
+
+ fun drawFrame(): Rect {
+ mDrawLock.withLock {
+ val holder = mSurfaceHolder ?: return Rect()
+ val canvas = holder.lockCanvas()
+ val canvasSize = Rect(0, 0, canvas.width, canvas.height)
+ canvas.drawColor(Color.GREEN)
+ val p = Paint()
+ p.color = Color.RED
+ canvas.drawRect(canvasSize, p)
+ holder.unlockCanvasAndPost(canvas)
+ return canvasSize
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
new file mode 100644
index 000000000000..884aae41446c
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.test
+
+class SurfaceProxy {
+ init {
+ System.loadLibrary("surface_jni")
+ }
+
+ external fun setSurface(surface: Any)
+ external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int)
+ external fun draw()
+
+ // android/native_window.h functions
+ external fun ANativeWindowLock()
+ external fun ANativeWindowUnlockAndPost()
+ external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int)
+}
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
new file mode 100644
index 000000000000..b48a91d49b91
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.test
+
+import android.app.Instrumentation
+import android.graphics.Rect
+import android.provider.Settings
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.monitor.LayersTraceMonitor
+import com.android.server.wm.flicker.monitor.withSFTracing
+import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.CountDownLatch
+import kotlin.properties.Delegates
+
+@RunWith(Parameterized::class)
+class SurfaceViewBufferTest(val useBlastAdapter: Boolean) {
+ private var mInitialUseBlastConfig by Delegates.notNull<Int>()
+
+ @get:Rule
+ var scenarioRule: ActivityScenarioRule<MainActivity> =
+ ActivityScenarioRule<MainActivity>(MainActivity::class.java)
+
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ val defaultBufferSize = Rect(0, 0, 640, 480)
+
+ @Before
+ fun setup() {
+ mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver,
+ "use_blast_adapter_sv", 0)
+ val enable = if (useBlastAdapter) 1 else 0
+ Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
+ enable)
+ val tmpDir = instrumentation.targetContext.dataDir.toPath()
+ LayersTraceMonitor(tmpDir).stop()
+
+ lateinit var surfaceReadyLatch: CountDownLatch
+ scenarioRule.getScenario().onActivity {
+ surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
+ }
+ surfaceReadyLatch.await()
+ }
+
+ @After
+ fun teardown() {
+ scenarioRule.getScenario().close()
+ Settings.Global.putInt(instrumentation.context.contentResolver,
+ "use_blast_adapter_sv", mInitialUseBlastConfig)
+ }
+
+ @Test
+ fun testSetBuffersGeometry_0x0_resetsBufferSize() {
+ val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
+ R8G8B8A8_UNORM)
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+ }
+ }
+
+ // verify buffer size is reset to default buffer size
+ assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
+ }
+
+ @Test
+ fun testSetBuffersGeometry_0x0_rejectsBuffer() {
+ val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+ R8G8B8A8_UNORM)
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
+ // Submit buffer one with a different size which should be rejected
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+
+ // submit a buffer with the default buffer size
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */)
+ }
+ }
+ // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
+ assertThat(trace).layer("SurfaceView", 2).doesNotExist()
+
+ // Verify the next buffer is submitted with the correct size
+ assertThat(trace).layer("SurfaceView", 3).also {
+ it.hasBufferSize(defaultBufferSize)
+ it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */)
+ }
+ }
+
+ @Test
+ fun testSetBuffersGeometry_smallerThanBuffer() {
+ val bufferSize = Rect(0, 0, 300, 200)
+ val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
+ bufferSize.height(), R8G8B8A8_UNORM)
+ it.drawFrame()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+ }
+ }
+
+ assertThat(trace).layer("SurfaceView", 1).also {
+ it.hasBufferSize(bufferSize)
+ it.hasLayerSize(defaultBufferSize)
+ it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
+ }
+ }
+
+ @Test
+ fun testSetBuffersGeometry_largerThanBuffer() {
+ val bufferSize = Rect(0, 0, 3000, 2000)
+ val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
+ bufferSize.height(), R8G8B8A8_UNORM)
+ it.drawFrame()
+ it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
+ }
+ }
+
+ assertThat(trace).layer("SurfaceView", 1).also {
+ it.hasBufferSize(bufferSize)
+ it.hasLayerSize(defaultBufferSize)
+ it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
+ }
+ }
+
+ /** Submit buffers as fast as possible and make sure they are queued */
+ @Test
+ fun testQueueBuffers() {
+ val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
+ scenarioRule.getScenario().onActivity {
+ it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
+ R8G8B8A8_UNORM)
+ for (i in 0..100) {
+ it.mSurfaceProxy.ANativeWindowLock()
+ it.mSurfaceProxy.ANativeWindowUnlockAndPost()
+ }
+ it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */)
+ }
+ }
+ for (frameNumber in 1..100) {
+ assertThat(trace).layer("SurfaceView", frameNumber.toLong())
+ }
+ }
+
+ companion object {
+ private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
+ private const val R8G8B8A8_UNORM = 1
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "blast={0}")
+ fun data(): Collection<Array<Any>> {
+ return listOf(
+ arrayOf(false), // First test: submit buffers via bufferqueue
+ arrayOf(true) // Second test: submit buffers via blast adapter
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index 83fd20803ed8..8013bd8c32f5 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -130,7 +130,7 @@ public class SmsApplicationTest {
when(mTelephonyManager.isSmsCapable()).thenReturn(true);
when(mRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)).thenReturn(true);
- when(mRoleManager.getDefaultSmsPackage(anyInt()))
+ when(mRoleManager.getSmsRoleHolder(anyInt()))
.thenReturn(TEST_COMPONENT_NAME.getPackageName());
for (String opStr : APP_OPS_TO_CHECK) {
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 124b6609f687..0fe84abcbc7b 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -63,6 +63,7 @@ android_test {
"services.net",
],
libs: [
+ "android.net.ipsec.ike.stubs.module_lib",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 9f0b41fa0cdf..85704d033634 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -67,6 +67,9 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
private NetworkAgent mNetworkAgent;
private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
+ // Controls how test network agent is going to wait before responding to keepalive
+ // start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem.
+ private long mKeepaliveResponseDelay = 0L;
private Integer mExpectedKeepaliveSlot = null;
public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context)
@@ -134,12 +137,17 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
if (mWrapper.mExpectedKeepaliveSlot != null) {
assertEquals((int) mWrapper.mExpectedKeepaliveSlot, slot);
}
- onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError);
+ mWrapper.mHandlerThread.getThreadHandler().postDelayed(
+ () -> onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError),
+ mWrapper.mKeepaliveResponseDelay);
}
@Override
public void stopSocketKeepalive(Message msg) {
- onSocketKeepaliveEvent(msg.arg1, mWrapper.mStopKeepaliveError);
+ final int slot = msg.arg1;
+ mWrapper.mHandlerThread.getThreadHandler().postDelayed(
+ () -> onSocketKeepaliveEvent(slot, mWrapper.mStopKeepaliveError),
+ mWrapper.mKeepaliveResponseDelay);
}
@Override
@@ -205,7 +213,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
public void connect() {
assertNotEquals("MockNetworkAgents can only be connected once",
- getNetworkInfo().getDetailedState(), NetworkInfo.DetailedState.CONNECTED);
+ mNetworkInfo.getDetailedState(), NetworkInfo.DetailedState.CONNECTED);
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
}
@@ -248,6 +256,10 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
mStopKeepaliveError = reason;
}
+ public void setKeepaliveResponseDelay(long delay) {
+ mKeepaliveResponseDelay = delay;
+ }
+
public void setExpectedKeepaliveSlot(Integer slot) {
mExpectedKeepaliveSlot = slot;
}
@@ -256,10 +268,6 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
return mNetworkAgent;
}
- public NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
- }
-
public NetworkCapabilities getNetworkCapabilities() {
return mNetworkCapabilities;
}
diff --git a/tests/net/java/android/net/util/IpUtilsTest.java b/tests/net/java/android/net/util/IpUtilsTest.java
deleted file mode 100644
index 193d85d0013a..000000000000
--- a/tests/net/java/android/net/util/IpUtilsTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.util;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class IpUtilsTest {
-
- private static final int IPV4_HEADER_LENGTH = 20;
- private static final int IPV6_HEADER_LENGTH = 40;
- private static final int TCP_HEADER_LENGTH = 20;
- private static final int UDP_HEADER_LENGTH = 8;
- private static final int IP_CHECKSUM_OFFSET = 10;
- private static final int TCP_CHECKSUM_OFFSET = 16;
- private static final int UDP_CHECKSUM_OFFSET = 6;
-
- private int getUnsignedByte(ByteBuffer buf, int offset) {
- return buf.get(offset) & 0xff;
- }
-
- private int getChecksum(ByteBuffer buf, int offset) {
- return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1);
- }
-
- private void assertChecksumEquals(int expected, short actual) {
- assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff));
- }
-
- // Generate test packets using Python code like this::
- //
- // from scapy import all as scapy
- //
- // def JavaPacketDefinition(bytes):
- // out = " ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n "
- // for i in xrange(len(bytes)):
- // out += "(byte) 0x%02x" % ord(bytes[i])
- // if i < len(bytes) - 1:
- // if i % 4 == 3:
- // out += ",\n "
- // else:
- // out += ", "
- // out += "\n });"
- // return out
- //
- // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") /
- // scapy.UDP(sport=12345, dport=7) /
- // "hello")
- // print JavaPacketDefinition(str(packet))
-
- @Test
- public void testIpv6TcpChecksum() throws Exception {
- // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
- // scapy.TCP(sport=12345, dport=7,
- // seq=1692871236, ack=128376451, flags=16,
- // window=32768) /
- // "hello, world")
- ByteBuffer packet = ByteBuffer.wrap(new byte[] {
- (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40,
- (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
- (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
- (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07,
- (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44,
- (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83,
- (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00,
- (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00,
- (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c,
- (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77,
- (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64
- });
-
- // Check that a valid packet has checksum 0.
- int transportLen = packet.limit() - IPV6_HEADER_LENGTH;
- assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
-
- // Check that we can calculate the checksum from scratch.
- int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET;
- int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1);
- assertEquals(0xee71, sum);
-
- packet.put(sumOffset, (byte) 0);
- packet.put(sumOffset + 1, (byte) 0);
- assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
-
- // Check that writing the checksum back into the packet results in a valid packet.
- packet.putShort(
- sumOffset,
- IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
- assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
- }
-
- @Test
- public void testIpv4UdpChecksum() {
- // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
- // scapy.UDP(sport=32012, dport=4500) /
- // "\xff")
- ByteBuffer packet = ByteBuffer.wrap(new byte[] {
- (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d,
- (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
- (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b,
- (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
- (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
- (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94,
- (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36,
- (byte) 0xff
- });
-
- // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid
- // UDP checksum, so the udpChecksum rewrites 0 to 0xffff).
- assertEquals(0, IpUtils.ipChecksum(packet, 0));
- assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
-
- // Check that we can calculate the checksums from scratch.
- final int ipSumOffset = IP_CHECKSUM_OFFSET;
- final int ipSum = getChecksum(packet, ipSumOffset);
- assertEquals(0xf68b, ipSum);
-
- packet.put(ipSumOffset, (byte) 0);
- packet.put(ipSumOffset + 1, (byte) 0);
- assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0));
-
- final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET;
- final int udpSum = getChecksum(packet, udpSumOffset);
- assertEquals(0xee36, udpSum);
-
- packet.put(udpSumOffset, (byte) 0);
- packet.put(udpSumOffset + 1, (byte) 0);
- assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
-
- // Check that writing the checksums back into the packet results in a valid packet.
- packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0));
- packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
- assertEquals(0, IpUtils.ipChecksum(packet, 0));
- assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
- }
-}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1f23bf38c2f2..7dfac9c8c357 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4292,6 +4292,32 @@ public class ConnectivityServiceTest {
myNet = connectKeepaliveNetwork(lp);
mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+ // Check that a stop followed by network disconnects does not result in crash.
+ try (SocketKeepalive ka = mCm.createSocketKeepalive(
+ myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ // Delay the response of keepalive events in networkAgent long enough to make sure
+ // the follow-up network disconnection will be processed first.
+ mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
+ ka.stop();
+
+ // Make sure the stop has been processed. Wait for executor idle is needed to prevent
+ // flaky since the actual stop call to the service is delegated to executor thread.
+ waitForIdleSerialExecutor(executor, TIMEOUT_MS);
+ waitForIdle();
+
+ mWiFiNetworkAgent.disconnect();
+ mWiFiNetworkAgent.expectDisconnected();
+ callback.expectStopped();
+ callback.assertNoCallback();
+ }
+
+ // Reconnect.
+ waitForIdle();
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+
// Check that keepalive slots start from 1 and increment. The first one gets slot 1.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
int srcPort2 = 0;
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index c76b4cd501e7..bc3db11ccd3c 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -20,6 +20,7 @@ import static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_RESTRICTED;
+import static android.net.ConnectivityManager.NetworkCallback;
import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -45,7 +46,9 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,6 +69,7 @@ import android.net.Ikev2VpnProfile;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.IpSecManager;
+import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkProperties;
import android.net.LocalSocket;
import android.net.Network;
@@ -75,6 +79,8 @@ import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnManager;
import android.net.VpnService;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.ConditionVariable;
@@ -101,6 +107,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -150,6 +157,11 @@ public class VpnTest {
private static final String TEST_VPN_IDENTITY = "identity";
private static final byte[] TEST_VPN_PSK = "psk".getBytes();
+ private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE);
+ private static final String TEST_IFACE_NAME = "TEST_IFACE";
+ private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
+ private static final long TEST_TIMEOUT_MS = 500L;
+
/**
* Names and UIDs for some fake packages. Important points:
* - UID is ordered increasing.
@@ -227,6 +239,13 @@ public class VpnTest {
// Deny all appops by default.
when(mAppOps.noteOpNoThrow(anyInt(), anyInt(), anyString()))
.thenReturn(AppOpsManager.MODE_IGNORED);
+
+ // Setup IpSecService
+ final IpSecTunnelInterfaceResponse tunnelResp =
+ new IpSecTunnelInterfaceResponse(
+ IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
+ when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
+ .thenReturn(tunnelResp);
}
@Test
@@ -988,6 +1007,52 @@ public class VpnTest {
eq(AppOpsManager.MODE_IGNORED));
}
+ private NetworkCallback triggerOnAvailableAndGetCallback() {
+ final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+ .requestNetwork(any(), networkCallbackCaptor.capture());
+
+ final NetworkCallback cb = networkCallbackCaptor.getValue();
+ cb.onAvailable(TEST_NETWORK);
+ return cb;
+ }
+
+ @Test
+ public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+ final ArgumentCaptor<IkeSessionCallback> captor =
+ ArgumentCaptor.forClass(IkeSessionCallback.class);
+ final IkeProtocolException exception = mock(IkeProtocolException.class);
+ when(exception.getErrorType())
+ .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
+
+ final Vpn vpn = startLegacyVpn(mVpnProfile);
+ final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+ // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+ // state
+ verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+ .createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+ final IkeSessionCallback ikeCb = captor.getValue();
+ ikeCb.onClosedExceptionally(exception);
+
+ verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
+ assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+ }
+
+ @Test
+ public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
+ when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
+ .thenThrow(new IllegalArgumentException());
+ final Vpn vpn = startLegacyVpn(mVpnProfile);
+ final NetworkCallback cb = triggerOnAvailableAndGetCallback();
+
+ // Wait for createIkeSession() to be called before proceeding in order to ensure consistent
+ // state
+ verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
+ assertEquals(DetailedState.FAILED, vpn.getNetworkInfo().getDetailedState());
+ }
+
private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
@@ -1090,7 +1155,7 @@ public class VpnTest {
new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
"name", profile.username, "password", profile.password,
"linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
- "idle", "1800", "mtu", "1400", "mru", "1400" },
+ "idle", "1800", "mtu", "1270", "mru", "1270" },
deps.mtpdArgs.get(10, TimeUnit.SECONDS));
// Now wait for the runner to be ready before testing for the route.
legacyRunnerReady.block(10_000);
@@ -1198,7 +1263,7 @@ public class VpnTest {
}
@Override
- public boolean checkInterfacePresent(final Vpn vpn, final String iface) {
+ public boolean isInterfacePresent(final Vpn vpn, final String iface) {
return true;
}
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
index 858358c74f80..8b730af76951 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java
@@ -22,7 +22,6 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.Manifest.permission;
import android.app.AppOpsManager;
-import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -167,13 +166,11 @@ public class NetworkStatsAccessTest {
}
private void setIsDeviceOwner(boolean isOwner) {
- when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
- .thenReturn(isOwner);
+ when(mDpmi.isActiveDeviceOwner(TEST_UID)).thenReturn(isOwner);
}
private void setIsProfileOwner(boolean isOwner) {
- when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
- .thenReturn(isOwner);
+ when(mDpmi.isActiveProfileOwner(TEST_UID)).thenReturn(isOwner);
}
private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 8f093779da11..6d2c7dc39ffd 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -150,7 +151,7 @@ public final class NetworkStatsSubscriptionsMonitorTest {
}
private void assertRatTypeChangedForSub(String subscriberId, int ratType) {
- assertEquals(mMonitor.getRatTypeForSubscriberId(subscriberId), ratType);
+ assertEquals(ratType, mMonitor.getRatTypeForSubscriberId(subscriberId));
final ArgumentCaptor<Integer> typeCaptor = ArgumentCaptor.forClass(Integer.class);
// Verify callback with the subscriberId and the RAT type should be as expected.
// It will fail if get a callback with an unexpected RAT type.
@@ -302,26 +303,84 @@ public final class NetworkStatsSubscriptionsMonitorTest {
reset(mDelegate);
// Set IMSI to null again to simulate somehow IMSI is not available, such as
- // modem crash. Verify service should not unregister listener.
+ // modem crash. Verify service should unregister listener.
updateSubscriberIdForTestSub(TEST_SUBID1, null);
- verify(mTelephonyManager, never()).listen(any(), anyInt());
- assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+ verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
+ eq(PhoneStateListener.LISTEN_NONE));
+ assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
reset(mDelegate);
+ clearInvocations(mTelephonyManager);
- // Set RAT type of sim1 to LTE. Verify RAT type of sim1 is still changed even if the IMSI
- // is not available. The monitor keeps the listener even if the IMSI disappears because
- // the IMSI can never change for any given subId, therefore even if the IMSI is updated
- // to null, the monitor should continue accepting updates of the RAT type. However,
- // telephony is never actually supposed to do this, if the IMSI disappears there should
- // not be updates, but it's still the right thing to do theoretically.
- setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+ // Simulate somehow IMSI is back. Verify service will register with
+ // another listener and fire callback accordingly.
+ final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
+ ArgumentCaptor.forClass(RatTypeListener.class);
+ updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI1);
+ verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
+ eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ reset(mDelegate);
+ clearInvocations(mTelephonyManager);
+
+ // Set RAT type of sim1 to LTE. Verify RAT type of sim1 still works.
+ setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
TelephonyManager.NETWORK_TYPE_LTE);
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE);
reset(mDelegate);
mMonitor.stop();
+ verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor2.getValue()),
+ eq(PhoneStateListener.LISTEN_NONE));
+ assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ }
+
+ /**
+ * Verify that when IMSI suddenly changed for a given subId, the service will register a new
+ * listener and unregister the old one, and report changes on updated IMSI. This is for modem
+ * feature that may be enabled for certain carrier, which changes to use a different IMSI while
+ * roaming on certain networks for multi-IMSI SIM cards, but the subId stays the same.
+ */
+ @Test
+ public void testSubscriberIdChanged() {
+ mMonitor.start();
+ // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback
+ // before changing RAT type.
+ addTestSub(TEST_SUBID1, TEST_IMSI1);
+ final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor =
+ ArgumentCaptor.forClass(RatTypeListener.class);
+ verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(),
+ eq(PhoneStateListener.LISTEN_SERVICE_STATE));
+ assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+
+ // Set RAT type of sim1 to UMTS.
+ // Verify RAT type of sim1 changes accordingly.
+ setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1,
+ TelephonyManager.NETWORK_TYPE_UMTS);
+ assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS);
+ reset(mDelegate);
+ clearInvocations(mTelephonyManager);
+
+ // Simulate IMSI of sim1 changed to IMSI2. Verify the service will register with
+ // another listener and remove the old one. The RAT type of new IMSI stays at
+ // NETWORK_TYPE_UNKNOWN until received initial callback from telephony.
+ final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor2 =
+ ArgumentCaptor.forClass(RatTypeListener.class);
+ updateSubscriberIdForTestSub(TEST_SUBID1, TEST_IMSI2);
+ verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor2.capture(),
+ eq(PhoneStateListener.LISTEN_SERVICE_STATE));
verify(mTelephonyManager, times(1)).listen(eq(ratTypeListenerCaptor.getValue()),
eq(PhoneStateListener.LISTEN_NONE));
assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ assertRatTypeNotChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ reset(mDelegate);
+
+ // Set RAT type of sim1 to UMTS for new listener to simulate the initial callback received
+ // from telephony after registration. Verify RAT type of sim1 changes with IMSI2
+ // accordingly.
+ setRatTypeForSub(ratTypeListenerCaptor2.getAllValues(), TEST_SUBID1,
+ TelephonyManager.NETWORK_TYPE_UMTS);
+ assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+ assertRatTypeChangedForSub(TEST_IMSI2, TelephonyManager.NETWORK_TYPE_UMTS);
+ reset(mDelegate);
}
}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index ea803f2aba8b..a7d0860210b4 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -49,7 +49,8 @@ public final class FrameworksTestsFilter extends SelectTest {
"android.view.WindowMetricsTest",
"android.view.PendingInsetsControllerTest",
"android.app.WindowContextTest",
- "android.window.WindowMetricsHelperTest"
+ "android.window.WindowMetricsHelperTest",
+ "android.app.activity.ActivityThreadTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tests/vcn/OWNERS b/tests/vcn/OWNERS
new file mode 100644
index 000000000000..33b9f0f75f81
--- /dev/null
+++ b/tests/vcn/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+benedictwong@google.com
+ckesting@google.com
+evitayan@google.com
+nharold@google.com
+jchalard@google.com \ No newline at end of file
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index 71c70da96109..8862405189c0 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -79,8 +79,8 @@ enum {
ISGAME_ATTR = 0x10103f4,
VERSION_ATTR = 0x01010519,
CERT_DIGEST_ATTR = 0x01010548,
- REQUIRED_FEATURE_ATTR = 0x01010557,
- REQUIRED_NOT_FEATURE_ATTR = 0x01010558,
+ REQUIRED_FEATURE_ATTR = 0x01010554,
+ REQUIRED_NOT_FEATURE_ATTR = 0x01010555,
IS_STATIC_ATTR = 0x0101055a,
REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565,
REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566,
@@ -1063,17 +1063,23 @@ class UsesPermission : public ManifestExtractor::Element {
public:
UsesPermission() = default;
std::string name;
- std::string requiredFeature;
- std::string requiredNotFeature;
+ std::vector<std::string> requiredFeatures;
+ std::vector<std::string> requiredNotFeatures;
int32_t required = true;
int32_t maxSdkVersion = -1;
void Extract(xml::Element* element) override {
name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
- requiredFeature = GetAttributeStringDefault(
- FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
- requiredNotFeature = GetAttributeStringDefault(
- FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), "");
+ std::string feature =
+ GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
+ if (!feature.empty()) {
+ requiredFeatures.push_back(feature);
+ }
+ feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), "");
+ if (!feature.empty()) {
+ requiredNotFeatures.push_back(feature);
+ }
+
required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1);
maxSdkVersion = GetAttributeIntegerDefault(
FindAttribute(element, MAX_SDK_VERSION_ATTR), -1);
@@ -1090,13 +1096,13 @@ class UsesPermission : public ManifestExtractor::Element {
if (maxSdkVersion >= 0) {
printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion));
}
- if (!requiredFeature.empty()) {
- printer->Print(StringPrintf(" requiredFeature='%s'", requiredFeature.data()));
+ printer->Print("\n");
+ for (const std::string& requiredFeature : requiredFeatures) {
+ printer->Print(StringPrintf(" required-feature='%s'\n", requiredFeature.data()));
}
- if (!requiredNotFeature.empty()) {
- printer->Print(StringPrintf(" requiredNotFeature='%s'", requiredNotFeature.data()));
+ for (const std::string& requiredNotFeature : requiredNotFeatures) {
+ printer->Print(StringPrintf(" required-not-feature='%s'\n", requiredNotFeature.data()));
}
- printer->Print("\n");
if (required == 0) {
printer->Print(StringPrintf("optional-permission: name='%s'", name.data()));
if (maxSdkVersion >= 0) {
@@ -1116,6 +1122,38 @@ class UsesPermission : public ManifestExtractor::Element {
}
};
+/** Represents <required-feature> elements. **/
+class RequiredFeature : public ManifestExtractor::Element {
+ public:
+ RequiredFeature() = default;
+ std::string name;
+
+ void Extract(xml::Element* element) override {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ auto parent_stack = extractor()->parent_stack();
+ if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) {
+ UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]);
+ uses_permission->requiredFeatures.push_back(name);
+ }
+ }
+};
+
+/** Represents <required-not-feature> elements. **/
+class RequiredNotFeature : public ManifestExtractor::Element {
+ public:
+ RequiredNotFeature() = default;
+ std::string name;
+
+ void Extract(xml::Element* element) override {
+ name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
+ auto parent_stack = extractor()->parent_stack();
+ if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) {
+ UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]);
+ uses_permission->requiredNotFeatures.push_back(name);
+ }
+ }
+};
+
/** Represents <uses-permission-sdk-23> elements. **/
class UsesPermissionSdk23 : public ManifestExtractor::Element {
public:
@@ -1845,7 +1883,8 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) {
for (xml::Element* child : element->GetChildElements()) {
if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23"
|| child->name == "permission") {
- auto permission_element = ManifestExtractor::Element::Inflate(this, child);
+ // Inflate the element and its descendants
+ auto permission_element = Visit(child);
manifest->AddChild(permission_element);
}
}
@@ -2237,38 +2276,40 @@ T* ElementCast(ManifestExtractor::Element* element) {
}
const std::unordered_map<std::string, bool> kTagCheck = {
- {"action", std::is_base_of<Action, T>::value},
- {"activity", std::is_base_of<Activity, T>::value},
- {"application", std::is_base_of<Application, T>::value},
- {"category", std::is_base_of<Category, T>::value},
- {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value},
- {"feature-group", std::is_base_of<FeatureGroup, T>::value},
- {"input-type", std::is_base_of<InputType, T>::value},
- {"intent-filter", std::is_base_of<IntentFilter, T>::value},
- {"meta-data", std::is_base_of<MetaData, T>::value},
- {"manifest", std::is_base_of<Manifest, T>::value},
- {"original-package", std::is_base_of<OriginalPackage, T>::value},
- {"overlay", std::is_base_of<Overlay, T>::value},
- {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
- {"permission", std::is_base_of<Permission, T>::value},
- {"provider", std::is_base_of<Provider, T>::value},
- {"receiver", std::is_base_of<Receiver, T>::value},
- {"screen", std::is_base_of<Screen, T>::value},
- {"service", std::is_base_of<Service, T>::value},
- {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
- {"supports-input", std::is_base_of<SupportsInput, T>::value},
- {"supports-screens", std::is_base_of<SupportsScreen, T>::value},
- {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value},
- {"uses-feature", std::is_base_of<UsesFeature, T>::value},
- {"uses-permission", std::is_base_of<UsesPermission, T>::value},
- {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
- {"uses-library", std::is_base_of<UsesLibrary, T>::value},
- {"uses-package", std::is_base_of<UsesPackage, T>::value},
- {"static-library", std::is_base_of<StaticLibrary, T>::value},
- {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
- {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value},
- {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
- {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value},
+ {"action", std::is_base_of<Action, T>::value},
+ {"activity", std::is_base_of<Activity, T>::value},
+ {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value},
+ {"application", std::is_base_of<Application, T>::value},
+ {"category", std::is_base_of<Category, T>::value},
+ {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value},
+ {"feature-group", std::is_base_of<FeatureGroup, T>::value},
+ {"input-type", std::is_base_of<InputType, T>::value},
+ {"intent-filter", std::is_base_of<IntentFilter, T>::value},
+ {"meta-data", std::is_base_of<MetaData, T>::value},
+ {"manifest", std::is_base_of<Manifest, T>::value},
+ {"original-package", std::is_base_of<OriginalPackage, T>::value},
+ {"overlay", std::is_base_of<Overlay, T>::value},
+ {"package-verifier", std::is_base_of<PackageVerifier, T>::value},
+ {"permission", std::is_base_of<Permission, T>::value},
+ {"provider", std::is_base_of<Provider, T>::value},
+ {"receiver", std::is_base_of<Receiver, T>::value},
+ {"required-feature", std::is_base_of<RequiredFeature, T>::value},
+ {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value},
+ {"screen", std::is_base_of<Screen, T>::value},
+ {"service", std::is_base_of<Service, T>::value},
+ {"static-library", std::is_base_of<StaticLibrary, T>::value},
+ {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value},
+ {"supports-input", std::is_base_of<SupportsInput, T>::value},
+ {"supports-screens", std::is_base_of<SupportsScreen, T>::value},
+ {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value},
+ {"uses-feature", std::is_base_of<UsesFeature, T>::value},
+ {"uses-library", std::is_base_of<UsesLibrary, T>::value},
+ {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value},
+ {"uses-package", std::is_base_of<UsesPackage, T>::value},
+ {"uses-permission", std::is_base_of<UsesPermission, T>::value},
+ {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value},
+ {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value},
+ {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value},
};
auto check = kTagCheck.find(element->tag());
@@ -2288,39 +2329,41 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate(
const std::unordered_map<std::string,
std::function<std::unique_ptr<ManifestExtractor::Element>()>>
kTagCheck = {
- {"action", &CreateType<Action>},
- {"activity", &CreateType<Activity>},
- {"application", &CreateType<Application>},
- {"category", &CreateType<Category>},
- {"compatible-screens", &CreateType<CompatibleScreens>},
- {"feature-group", &CreateType<FeatureGroup>},
- {"input-type", &CreateType<InputType>},
- {"intent-filter",&CreateType<IntentFilter>},
- {"manifest", &CreateType<Manifest>},
- {"meta-data", &CreateType<MetaData>},
- {"original-package", &CreateType<OriginalPackage>},
- {"overlay", &CreateType<Overlay>},
- {"package-verifier", &CreateType<PackageVerifier>},
- {"permission", &CreateType<Permission>},
- {"provider", &CreateType<Provider>},
- {"receiver", &CreateType<Receiver>},
- {"screen", &CreateType<Screen>},
- {"service", &CreateType<Service>},
- {"supports-gl-texture", &CreateType<SupportsGlTexture>},
- {"supports-input", &CreateType<SupportsInput>},
- {"supports-screens", &CreateType<SupportsScreen>},
- {"uses-configuration", &CreateType<UsesConfiguarion>},
- {"uses-feature", &CreateType<UsesFeature>},
- {"uses-permission", &CreateType<UsesPermission>},
- {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>},
- {"uses-library", &CreateType<UsesLibrary>},
- {"static-library", &CreateType<StaticLibrary>},
- {"uses-static-library", &CreateType<UsesStaticLibrary>},
- {"uses-package", &CreateType<UsesPackage>},
- {"additional-certificate", &CreateType<AdditionalCertificate>},
- {"uses-sdk", &CreateType<UsesSdkBadging>},
- {"uses-native-library", &CreateType<UsesNativeLibrary>},
- };
+ {"action", &CreateType<Action>},
+ {"activity", &CreateType<Activity>},
+ {"additional-certificate", &CreateType<AdditionalCertificate>},
+ {"application", &CreateType<Application>},
+ {"category", &CreateType<Category>},
+ {"compatible-screens", &CreateType<CompatibleScreens>},
+ {"feature-group", &CreateType<FeatureGroup>},
+ {"input-type", &CreateType<InputType>},
+ {"intent-filter", &CreateType<IntentFilter>},
+ {"manifest", &CreateType<Manifest>},
+ {"meta-data", &CreateType<MetaData>},
+ {"original-package", &CreateType<OriginalPackage>},
+ {"overlay", &CreateType<Overlay>},
+ {"package-verifier", &CreateType<PackageVerifier>},
+ {"permission", &CreateType<Permission>},
+ {"provider", &CreateType<Provider>},
+ {"receiver", &CreateType<Receiver>},
+ {"required-feature", &CreateType<RequiredFeature>},
+ {"required-not-feature", &CreateType<RequiredNotFeature>},
+ {"screen", &CreateType<Screen>},
+ {"service", &CreateType<Service>},
+ {"static-library", &CreateType<StaticLibrary>},
+ {"supports-gl-texture", &CreateType<SupportsGlTexture>},
+ {"supports-input", &CreateType<SupportsInput>},
+ {"supports-screens", &CreateType<SupportsScreen>},
+ {"uses-configuration", &CreateType<UsesConfiguarion>},
+ {"uses-feature", &CreateType<UsesFeature>},
+ {"uses-library", &CreateType<UsesLibrary>},
+ {"uses-native-library", &CreateType<UsesNativeLibrary>},
+ {"uses-package", &CreateType<UsesPackage>},
+ {"uses-permission", &CreateType<UsesPermission>},
+ {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>},
+ {"uses-sdk", &CreateType<UsesSdkBadging>},
+ {"uses-static-library", &CreateType<UsesStaticLibrary>},
+ };
// Attempt to map the xml tag to a element inflater
std::unique_ptr<ManifestExtractor::Element> element;
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index dac21d7e9200..3d8c25ebcbdd 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -393,6 +393,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,
manifest_action["protected-broadcast"];
manifest_action["adopt-permissions"];
manifest_action["uses-permission"];
+ manifest_action["uses-permission"]["required-feature"].Action(RequiredNameIsNotEmpty);
+ manifest_action["uses-permission"]["required-not-feature"].Action(RequiredNameIsNotEmpty);
manifest_action["uses-permission-sdk-23"];
manifest_action["permission"];
manifest_action["permission"]["meta-data"] = meta_data_action;
diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt
index 909472640f29..a1d0389b0041 100644
--- a/tools/codegen/src/com/android/codegen/FileInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FileInfo.kt
@@ -272,7 +272,7 @@ class FileInfo(
/** Debug info */
fun summary(): String = when(this) {
is Code -> "${javaClass.simpleName}(${lines.size} lines): ${lines.getOrNull(0)?.take(70) ?: ""}..."
- is DataClass -> "DataClass ${ast.nameAsString}:\n" +
+ is DataClass -> "DataClass ${ast.nameAsString} nested:${ast.nestedTypes.map { it.nameAsString }}:\n" +
chunks.joinToString("\n") { it.summary() } +
"\n//end ${ast.nameAsString}"
}
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 5a96cf1d9bdb..6e1ab5944eb6 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -3,7 +3,10 @@ package com.android.codegen
import com.github.javaparser.ast.body.FieldDeclaration
import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.body.VariableDeclarator
-import com.github.javaparser.ast.expr.*
+import com.github.javaparser.ast.expr.AnnotationExpr
+import com.github.javaparser.ast.expr.ArrayInitializerExpr
+import com.github.javaparser.ast.expr.LiteralExpr
+import com.github.javaparser.ast.expr.UnaryExpr
import java.io.File
@@ -703,7 +706,7 @@ fun ClassPrinter.generateSetters() {
generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
+GENERATED_MEMBER_HEADER
- "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+ "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
generateSetFrom("value")
+"return this;"
}
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index d6953c00fc0b..69ff18d3f6ab 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -41,7 +41,10 @@ private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterf
}
} + ("class ${classAst.nameAsString}" +
" extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
- " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]")
+ " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") +
+ classAst.nestedNonDataClasses.flatMap { nestedClass ->
+ generateInputSignaturesForClass(nestedClass)
+ }
}
private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
@@ -141,6 +144,8 @@ private fun ClassPrinter.getFullClassName(className: String): String {
if (className[0].isLowerCase()) return className //primitive
+ if (className[0] == '?') return className //wildcard
+
return thisPackagePrefix + className
}
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 6f740cd663e3..785aa9107f90 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.15"
+const val CODEGEN_VERSION = "1.0.17"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index c19ae3b0b11f..7cfa7847fcff 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -103,6 +103,10 @@ val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDecla
val TypeDeclaration<*>.nestedDataClasses get()
= nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
.filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } }
+val TypeDeclaration<*>.nestedNonDataClasses get()
+ = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
+ .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } }
+ .filterNot { it.isInterface }
val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line
inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) {
diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py
index afe91cda37b0..15088fc81e88 100644
--- a/tools/stringslint/stringslint.py
+++ b/tools/stringslint/stringslint.py
@@ -1,4 +1,5 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
+#-*- coding: utf-8 -*-
# Copyright (C) 2018 The Android Open Source Project
#
@@ -33,9 +34,6 @@ In general:
import re, sys, codecs
import lxml.etree as ET
-reload(sys)
-sys.setdefaultencoding('utf8')
-
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
@@ -118,7 +116,7 @@ def lint(path):
raw = f.read()
if len(raw.strip()) == 0:
return warnings
- tree = ET.fromstring(raw)
+ tree = ET.fromstring(bytes(raw, encoding='utf-8'))
root = tree #tree.getroot()
last_comment = None
@@ -231,6 +229,6 @@ for b in before:
if len(after) > 0:
for a in sorted(after.keys()):
- print after[a]
- print
+ print(after[a])
+ print()
sys.exit(1)
diff --git a/tools/stringslint/stringslint_sha.sh b/tools/stringslint/stringslint_sha.sh
index bd80bb4e6f3f..bd0569873197 100755
--- a/tools/stringslint/stringslint_sha.sh
+++ b/tools/stringslint/stringslint_sha.sh
@@ -1,5 +1,5 @@
#!/bin/bash
LOCAL_DIR="$( dirname ${BASH_SOURCE} )"
git show --name-only --pretty=format: $1 | grep values/strings.xml | while read file; do
- python $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file)
+ python3 $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file)
done
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 7c150f9c8db9..0af6266d1642 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -115,13 +115,13 @@ static bool validateFile(const char* filename) {
}
case FILETYPE_INPUTDEVICECONFIGURATION: {
- PropertyMap* map;
- status_t status = PropertyMap::load(String8(filename), &map);
- if (status) {
- error("Error %d parsing input device configuration file.\n\n", status);
+ android::base::Result<std::unique_ptr<PropertyMap>> propertyMap =
+ PropertyMap::load(String8(filename));
+ if (!propertyMap.ok()) {
+ error("Error %d parsing input device configuration file.\n\n",
+ propertyMap.error().code());
return false;
}
- delete map;
break;
}
diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp
new file mode 100644
index 000000000000..d58d0dcdc45a
--- /dev/null
+++ b/tools/xmlpersistence/Android.bp
@@ -0,0 +1,11 @@
+java_binary_host {
+ name: "xmlpersistence_cli",
+ manifest: "manifest.txt",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "javaparser-symbol-solver",
+ "javapoet",
+ ],
+}
diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS
new file mode 100644
index 000000000000..4f4d06a32676
--- /dev/null
+++ b/tools/xmlpersistence/OWNERS
@@ -0,0 +1 @@
+zhanghai@google.com
diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt
new file mode 100644
index 000000000000..6d9771998efc
--- /dev/null
+++ b/tools/xmlpersistence/manifest.txt
@@ -0,0 +1 @@
+Main-class: MainKt
diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt
new file mode 100644
index 000000000000..b2c5f4ac767b
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt
@@ -0,0 +1,576 @@
+/*
+ * 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.
+ */
+
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.NameAllocator
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeSpec
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.nio.charset.StandardCharsets
+import java.time.Year
+import java.util.Objects
+import javax.lang.model.element.Modifier
+
+// JavaPoet only supports line comments, and can't add a newline after file level comments.
+val FILE_HEADER = """
+ /*
+ * Copyright (C) ${Year.now().value} 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.
+ */
+
+ // Generated by xmlpersistence. DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ // @formatter:off
+""".trimIndent() + "\n\n"
+
+private val atomicFileType = ClassName.get("android.util", "AtomicFile")
+
+fun generate(persistence: PersistenceInfo): JavaFile {
+ val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type }
+ val type = TypeSpec.classBuilder(persistence.name)
+ .addJavadoc(
+ """
+ Generated class implementing XML persistence for${'$'}W{@link $1T}.
+ <p>
+ This class provides atomicity for persistence via {@link $2T}, however it does not provide
+ thread safety, so please bring your own synchronization mechanism.
+ """.trimIndent(), persistence.root.type, atomicFileType
+ )
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addField(generateFileField())
+ .addMethod(generateConstructor())
+ .addMethod(generateReadMethod(persistence.root))
+ .addMethod(generateParseMethod(persistence.root))
+ .addMethods(distinctClassFields.map { generateParseClassMethod(it) })
+ .addMethod(generateWriteMethod(persistence.root))
+ .addMethod(generateSerializeMethod(persistence.root))
+ .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) })
+ .addMethod(generateDeleteMethod())
+ .build()
+ return JavaFile.builder(persistence.root.type.packageName(), type)
+ .skipJavaLangImports(true)
+ .indent(" ")
+ .build()
+}
+
+private val nonNullType = ClassName.get("android.annotation", "NonNull")
+
+private fun generateFileField(): FieldSpec =
+ FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL)
+ .addAnnotation(nonNullType)
+ .build()
+
+private fun generateConstructor(): MethodSpec =
+ MethodSpec.constructorBuilder()
+ .addJavadoc(
+ """
+ Create an instance of this class.
+
+ @param file the XML file for persistence
+ """.trimIndent()
+ )
+ .addModifiers(Modifier.PUBLIC)
+ .addParameter(
+ ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build()
+ )
+ .addStatement("mFile = new \$1T(file)", atomicFileType)
+ .build()
+
+private val nullableType = ClassName.get("android.annotation", "Nullable")
+
+private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser")
+
+private val xmlType = ClassName.get("android.util", "Xml")
+
+private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException")
+
+private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("read")
+ .addJavadoc(
+ """
+ Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile.
+
+ @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist
+ @throws IllegalArgumentException if an error occurred while reading
+ """.trimIndent(), rootField.type
+ )
+ .addAnnotation(nullableType)
+ .addModifiers(Modifier.PUBLIC)
+ .returns(rootField.type)
+ .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) {
+ addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType)
+ addStatement("parser.setInput(inputStream, null)")
+ addStatement("return parse(parser)")
+ nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java)
+ addStatement("return null")
+ nextControlFlow(
+ "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType
+ )
+ addStatement("throw new IllegalArgumentException(e)")
+ }
+ .build()
+
+private val ClassFieldInfo.allClassFields: List<ClassFieldInfo>
+ get() =
+ mutableListOf<ClassFieldInfo>().apply {
+ this += this@allClassFields
+ for (field in fields) {
+ when (field) {
+ is ClassFieldInfo -> this += field.allClassFields
+ is ListFieldInfo -> this += field.element.allClassFields
+ }
+ }
+ }
+
+private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("parse")
+ .addAnnotation(nonNullType)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .returns(rootField.type)
+ .addParameter(
+ ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
+ )
+ .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType))
+ .apply {
+ addStatement("int type")
+ addStatement("int depth")
+ addStatement("int innerDepth = parser.getDepth() + 1")
+ addControlFlow(
+ "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
+ + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
+ xmlPullParserType
+ ) {
+ addControlFlow(
+ "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
+ ) {
+ addStatement("continue")
+ }
+ addControlFlow(
+ "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java,
+ rootField.tagName
+ ) {
+ addStatement("return \$1L(parser)", rootField.parseMethodName)
+ }
+ }
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Missing root tag <${rootField.tagName}>"
+ )
+ }
+ .build()
+
+private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder(classField.parseMethodName)
+ .addAnnotation(nonNullType)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .returns(classField.type)
+ .addParameter(
+ ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build()
+ )
+ .apply {
+ val (attributeFields, tagFields) = classField.fields
+ .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
+ if (tagFields.isNotEmpty()) {
+ addExceptions(
+ listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)
+ )
+ }
+ val nameAllocator = NameAllocator().apply {
+ newName("parser")
+ newName("type")
+ newName("depth")
+ newName("innerDepth")
+ }
+ for (field in attributeFields) {
+ val variableName = nameAllocator.newName(field.variableName, field)
+ when (field) {
+ is PrimitiveFieldInfo -> {
+ val stringVariableName =
+ nameAllocator.newName("${field.variableName}String")
+ addStatement(
+ "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
+ stringVariableName, field.attributeName
+ )
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", stringVariableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Missing attribute \"${field.attributeName}\""
+ )
+ }
+ }
+ val boxedType = field.type.box()
+ val parseTypeMethodName = if (field.type.isPrimitive) {
+ "parse${field.type.toString().capitalize()}"
+ } else {
+ "valueOf"
+ }
+ if (field.isRequired) {
+ addStatement(
+ "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName,
+ boxedType, parseTypeMethodName, stringVariableName
+ )
+ } else {
+ addStatement(
+ "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null",
+ field.type, variableName, stringVariableName, boxedType,
+ parseTypeMethodName
+ )
+ }
+ }
+ is StringFieldInfo ->
+ addStatement(
+ "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)",
+ variableName, field.attributeName
+ )
+ else -> error(field)
+ }
+ }
+ if (tagFields.isNotEmpty()) {
+ for (field in tagFields) {
+ val variableName = nameAllocator.newName(field.variableName, field)
+ when (field) {
+ is ClassFieldInfo ->
+ addStatement("\$1T \$2L =\$Wnull", field.type, variableName)
+ is ListFieldInfo ->
+ addStatement(
+ "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName,
+ ArrayList::class.java
+ )
+ else -> error(field)
+ }
+ }
+ addStatement("int type")
+ addStatement("int depth")
+ addStatement("int innerDepth = parser.getDepth() + 1")
+ addControlFlow(
+ "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W"
+ + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))",
+ xmlPullParserType
+ ) {
+ addControlFlow(
+ "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType
+ ) {
+ addStatement("continue")
+ }
+ addControlFlow("switch (parser.getName())") {
+ for (field in tagFields) {
+ addControlFlow("case \$1S:", field.tagName) {
+ val variableName = nameAllocator.get(field)
+ when (field) {
+ is ClassFieldInfo -> {
+ addControlFlow("if (\$1L != null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Duplicate tag \"${field.tagName}\""
+ )
+ }
+ addStatement(
+ "\$1L =\$W\$2L(parser)", variableName,
+ field.parseMethodName
+ )
+ addStatement("break")
+ }
+ is ListFieldInfo -> {
+ val elementNameAllocator = nameAllocator.clone()
+ val elementVariableName = elementNameAllocator.newName(
+ field.element.xmlName!!.toLowerCamelCase()
+ )
+ addStatement(
+ "final \$1T \$2L =\$W\$3L(parser)", field.element.type,
+ elementVariableName, field.element.parseMethodName
+ )
+ addStatement(
+ "\$1L.add(\$2L)", variableName, elementVariableName
+ )
+ addStatement("break")
+ }
+ else -> error(field)
+ }
+ }
+ }
+ }
+ }
+ }
+ for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) {
+ addControlFlow("if ($1L == null)", nameAllocator.get(field)) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>"
+ )
+ }
+ }
+ addStatement(
+ classField.fields.joinToString(",\$W", "return new \$1T(", ")") {
+ nameAllocator.get(it)
+ }, classField.type
+ )
+ }
+ .build()
+
+private val ClassFieldInfo.parseMethodName: String
+ get() = "parse${type.simpleName().toUpperCamelCase()}"
+
+private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer")
+
+private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("write")
+ .apply {
+ val nameAllocator = NameAllocator().apply {
+ newName("outputStream")
+ newName("serializer")
+ }
+ val parameterName = nameAllocator.newName(rootField.variableName)
+ addJavadoc(
+ """
+ Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile.
+
+ @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist
+ """.trimIndent(), rootField.type, parameterName
+ )
+ addAnnotation(nullableType)
+ addModifiers(Modifier.PUBLIC)
+ addParameter(
+ ParameterSpec.builder(rootField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addStatement("\$1T outputStream = null", FileOutputStream::class.java)
+ addControlFlow("try") {
+ addStatement("outputStream = mFile.startWrite()")
+ addStatement(
+ "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType
+ )
+ addStatement(
+ "serializer.setOutput(outputStream, \$1T.UTF_8.name())",
+ StandardCharsets::class.java
+ )
+ addStatement(
+ "serializer.setFeature(\$1S, true)",
+ "http://xmlpull.org/v1/doc/features.html#indent-output"
+ )
+ addStatement("serializer.startDocument(null, true)")
+ addStatement("serialize(serializer,\$W\$1L)", parameterName)
+ addStatement("serializer.endDocument()")
+ addStatement("mFile.finishWrite(outputStream)")
+ nextControlFlow("catch (Exception e)")
+ addStatement("e.printStackTrace()")
+ addStatement("mFile.failWrite(outputStream)")
+ }
+ }
+ .build()
+
+private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder("serialize")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .addParameter(
+ ParameterSpec.builder(xmlSerializerType, "serializer")
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ .apply {
+ val nameAllocator = NameAllocator().apply { newName("serializer") }
+ val parameterName = nameAllocator.newName(rootField.variableName)
+ addParameter(
+ ParameterSpec.builder(rootField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addException(IOException::class.java)
+ addStatement("serializer.startTag(null, \$1S)", rootField.tagName)
+ addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName)
+ addStatement("serializer.endTag(null, \$1S)", rootField.tagName)
+ }
+ .build()
+
+private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec =
+ MethodSpec.methodBuilder(classField.serializeMethodName)
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
+ .addParameter(
+ ParameterSpec.builder(xmlSerializerType, "serializer")
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ .apply {
+ val nameAllocator = NameAllocator().apply {
+ newName("serializer")
+ newName("i")
+ }
+ val parameterName = nameAllocator.newName(classField.serializeParameterName)
+ addParameter(
+ ParameterSpec.builder(classField.type, parameterName)
+ .addAnnotation(nonNullType)
+ .build()
+ )
+ addException(IOException::class.java)
+ val (attributeFields, tagFields) = classField.fields
+ .partition { it is PrimitiveFieldInfo || it is StringFieldInfo }
+ for (field in attributeFields) {
+ val variableName = "$parameterName.${field.name}"
+ if (!field.isRequired) {
+ beginControlFlow("if (\$1L != null)", variableName)
+ }
+ when (field) {
+ is PrimitiveFieldInfo -> {
+ if (field.isRequired && !field.type.isPrimitive) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ val stringVariableName =
+ nameAllocator.newName("${field.variableName}String")
+ addStatement(
+ "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName,
+ variableName
+ )
+ addStatement(
+ "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
+ stringVariableName
+ )
+ }
+ is StringFieldInfo -> {
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ addStatement(
+ "serializer.attribute(null, \$1S, \$2L)", field.attributeName,
+ variableName
+ )
+ }
+ else -> error(field)
+ }
+ if (!field.isRequired) {
+ endControlFlow()
+ }
+ }
+ for (field in tagFields) {
+ val variableName = "$parameterName.${field.name}"
+ if (field.isRequired) {
+ addControlFlow("if (\$1L == null)", variableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S)",
+ "Field \"${field.name}\" is null"
+ )
+ }
+ }
+ when (field) {
+ is ClassFieldInfo -> {
+ addStatement("serializer.startTag(null, \$1S)", field.tagName)
+ addStatement(
+ "\$1L(serializer, \$2L)", field.serializeMethodName, variableName
+ )
+ addStatement("serializer.endTag(null, \$1S)", field.tagName)
+ }
+ is ListFieldInfo -> {
+ val sizeVariableName = nameAllocator.newName("${field.variableName}Size")
+ addStatement(
+ "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName
+ )
+ addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) {
+ val elementNameAllocator = nameAllocator.clone()
+ val elementVariableName = elementNameAllocator.newName(
+ field.element.xmlName!!.toLowerCamelCase()
+ )
+ addStatement(
+ "final \$1T \$2L =\$W\$3L.get(i)", field.element.type,
+ elementVariableName, variableName
+ )
+ addControlFlow("if (\$1L == null)", elementVariableName) {
+ addStatement(
+ "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)",
+ "Field element \"${field.name}[", "]\" is null"
+ )
+ }
+ addStatement("serializer.startTag(null, \$1S)", field.element.tagName)
+ addStatement(
+ "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName,
+ elementVariableName
+ )
+ addStatement("serializer.endTag(null, \$1S)", field.element.tagName)
+ }
+ }
+ else -> error(field)
+ }
+ }
+ }
+ .build()
+
+private val ClassFieldInfo.serializeMethodName: String
+ get() = "serialize${type.simpleName().toUpperCamelCase()}"
+
+private val ClassFieldInfo.serializeParameterName: String
+ get() = type.simpleName().toLowerCamelCase()
+
+private val FieldInfo.variableName: String
+ get() = name.toLowerCamelCase()
+
+private val FieldInfo.attributeName: String
+ get() {
+ check(this is PrimitiveFieldInfo || this is StringFieldInfo)
+ return xmlNameOrName.toLowerCamelCase()
+ }
+
+private val FieldInfo.tagName: String
+ get() {
+ check(this is ClassFieldInfo || this is ListFieldInfo)
+ return xmlNameOrName.toLowerKebabCase()
+ }
+
+private val FieldInfo.xmlNameOrName: String
+ get() = xmlName ?: name
+
+private fun generateDeleteMethod(): MethodSpec =
+ MethodSpec.methodBuilder("delete")
+ .addJavadoc("Delete the XML file, if any.")
+ .addModifiers(Modifier.PUBLIC)
+ .addStatement("mFile.delete()")
+ .build()
+
+private inline fun MethodSpec.Builder.addControlFlow(
+ controlFlow: String,
+ vararg args: Any,
+ block: MethodSpec.Builder.() -> Unit
+): MethodSpec.Builder {
+ beginControlFlow(controlFlow, *args)
+ block()
+ endControlFlow()
+ return this
+}
diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt
new file mode 100644
index 000000000000..e271f8cb9361
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Main.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+import java.io.File
+import java.nio.file.Files
+
+fun main(args: Array<String>) {
+ val showUsage = args.isEmpty() || when (args.singleOrNull()) {
+ "-h", "--help" -> true
+ else -> false
+ }
+ if (showUsage) {
+ usage()
+ return
+ }
+
+ val files = args.flatMap {
+ File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() }
+ }
+ val persistences = parse(files)
+ for (persistence in persistences) {
+ val file = generate(persistence)
+ Files.newBufferedWriter(persistence.path).use {
+ it.write(FILE_HEADER)
+ file.writeTo(it)
+ }
+ }
+}
+
+private fun usage() {
+ println("Usage: xmlpersistence <FILES>")
+}
diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt
new file mode 100644
index 000000000000..3ea12a9aa389
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/Parser.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+import com.github.javaparser.JavaParser
+import com.github.javaparser.ParseProblemException
+import com.github.javaparser.ParseResult
+import com.github.javaparser.ParserConfiguration
+import com.github.javaparser.ast.Node
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.FieldDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.expr.AnnotationExpr
+import com.github.javaparser.ast.expr.Expression
+import com.github.javaparser.ast.expr.NormalAnnotationExpr
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration
+import com.github.javaparser.resolution.types.ResolvedPrimitiveType
+import com.github.javaparser.resolution.types.ResolvedReferenceType
+import com.github.javaparser.symbolsolver.JavaSymbolSolver
+import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration
+import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
+import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver
+import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import java.nio.file.Path
+import java.util.Optional
+
+class PersistenceInfo(
+ val name: String,
+ val root: ClassFieldInfo,
+ val path: Path
+)
+
+sealed class FieldInfo {
+ abstract val name: String
+ abstract val xmlName: String?
+ abstract val type: TypeName
+ abstract val isRequired: Boolean
+}
+
+class PrimitiveFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: TypeName,
+ override val isRequired: Boolean
+) : FieldInfo()
+
+class StringFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val isRequired: Boolean
+) : FieldInfo() {
+ override val type: TypeName = ClassName.get(String::class.java)
+}
+
+class ClassFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: ClassName,
+ override val isRequired: Boolean,
+ val fields: List<FieldInfo>
+) : FieldInfo()
+
+class ListFieldInfo(
+ override val name: String,
+ override val xmlName: String?,
+ override val type: ParameterizedTypeName,
+ val element: ClassFieldInfo
+) : FieldInfo() {
+ override val isRequired: Boolean = true
+}
+
+fun parse(files: List<Path>): List<PersistenceInfo> {
+ val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) }
+ val javaParser = JavaParser(ParserConfiguration()
+ .setSymbolResolver(JavaSymbolSolver(typeSolver)))
+ val compilationUnits = files.map { javaParser.parse(it).getOrThrow() }
+ val memoryTypeSolver = MemoryTypeSolver().apply {
+ for (compilationUnit in compilationUnits) {
+ for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) {
+ val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue
+ addDeclaration(name, typeDeclaration.resolve())
+ }
+ }
+ }
+ typeSolver.add(memoryTypeSolver)
+ return mutableListOf<PersistenceInfo>().apply {
+ for (compilationUnit in compilationUnits) {
+ val classDeclarations = compilationUnit
+ .getNodesByClass<ClassOrInterfaceDeclaration>()
+ .filter { !it.isInterface && (!it.isNestedType || it.isStatic) }
+ this += classDeclarations.mapNotNull { parsePersistenceInfo(it) }
+ }
+ }
+}
+
+private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? {
+ val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull()
+ ?: return null
+ val rootClassName = classDeclaration.nameAsString
+ val name = annotation.getMemberValue("value")?.stringLiteralValue
+ ?: "${rootClassName}Persistence"
+ val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull()
+ ?.getMemberValue("value")?.stringLiteralValue
+ val root = parseClassFieldInfo(
+ rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration
+ )
+ val path = classDeclaration.findCompilationUnit().get().storage.get().path
+ .resolveSibling("$name.java")
+ return PersistenceInfo(name, root, path)
+}
+
+private fun parseClassFieldInfo(
+ name: String,
+ xmlName: String?,
+ isRequired: Boolean,
+ classDeclaration: ClassOrInterfaceDeclaration
+): ClassFieldInfo {
+ val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) }
+ val type = classDeclaration.resolve().typeName
+ return ClassFieldInfo(name, xmlName, type, isRequired, fields)
+}
+
+private fun parseFieldInfo(field: FieldDeclaration): FieldInfo {
+ require(field.isPublic && field.isFinal)
+ val variable = field.variables.single()
+ val name = variable.nameAsString
+ val annotations = field.annotations + variable.type.annotations
+ val annotation = annotations.getByName("XmlName")
+ val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue
+ val isRequired = annotations.getByName("NonNull") != null
+ return when (val type = variable.type.resolve()) {
+ is ResolvedPrimitiveType -> {
+ val primitiveType = type.typeName
+ PrimitiveFieldInfo(name, xmlName, primitiveType, true)
+ }
+ is ResolvedReferenceType -> {
+ when (type.qualifiedName) {
+ Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name,
+ Short::class.javaObjectType.name, Char::class.javaObjectType.name,
+ Integer::class.javaObjectType.name, Long::class.javaObjectType.name,
+ Float::class.javaObjectType.name, Double::class.javaObjectType.name ->
+ PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired)
+ String::class.java.name -> StringFieldInfo(name, xmlName, isRequired)
+ List::class.java.name -> {
+ requireNotNull(xmlName)
+ val elementType = type.typeParametersValues().single()
+ require(elementType is ResolvedReferenceType)
+ val listType = ParameterizedTypeName.get(
+ ClassName.get(List::class.java), elementType.typeName
+ )
+ val element = parseClassFieldInfo(
+ "(element)", xmlName, true, elementType.classDeclaration
+ )
+ ListFieldInfo(name, xmlName, listType, element)
+ }
+ else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration)
+ }
+ }
+ else -> error(type)
+ }
+}
+
+private fun <T> ParseResult<T>.getOrThrow(): T =
+ if (isSuccessful) {
+ result.get()
+ } else {
+ throw ParseProblemException(problems)
+ }
+
+private inline fun <reified T : Node> Node.getNodesByClass(): List<T> =
+ getNodesByClass(T::class.java)
+
+private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply {
+ if (klass.isInstance(this@getNodesByClass)) {
+ this += klass.cast(this@getNodesByClass)
+ }
+ for (childNode in childNodes) {
+ this += childNode.getNodesByClass(klass)
+ }
+}
+
+private fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? =
+ find { it.name.identifier == name }
+
+private fun AnnotationExpr.getMemberValue(name: String): Expression? =
+ when (this) {
+ is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value
+ is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null
+ else -> null
+ }
+
+private val Expression.stringLiteralValue: String
+ get() {
+ require(this is StringLiteralExpr)
+ return value
+ }
+
+private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration
+ get() {
+ val resolvedClassDeclaration = typeDeclaration
+ require(resolvedClassDeclaration is JavaParserClassDeclaration)
+ return resolvedClassDeclaration.wrappedNode
+ }
+
+private val ResolvedPrimitiveType.typeName: TypeName
+ get() =
+ when (this) {
+ ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN
+ ResolvedPrimitiveType.BYTE -> TypeName.BYTE
+ ResolvedPrimitiveType.SHORT -> TypeName.SHORT
+ ResolvedPrimitiveType.CHAR -> TypeName.CHAR
+ ResolvedPrimitiveType.INT -> TypeName.INT
+ ResolvedPrimitiveType.LONG -> TypeName.LONG
+ ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT
+ ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE
+ }
+
+// This doesn't support type parameters.
+private val ResolvedReferenceType.typeName: TypeName
+ get() = typeDeclaration.typeName
+
+private val ResolvedReferenceTypeDeclaration.typeName: ClassName
+ get() {
+ val packageName = packageName
+ val classNames = className.split(".")
+ val topLevelClassName = classNames.first()
+ val nestedClassNames = classNames.drop(1)
+ return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray())
+ }
diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
new file mode 100644
index 000000000000..b4bdbba7170b
--- /dev/null
+++ b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+import java.util.Locale
+
+private val camelHumpBoundary = Regex(
+ "-"
+ + "|_"
+ + "|(?<=[0-9])(?=[^0-9])"
+ + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])"
+ + "|(?<=[a-z])(?=[^a-z])"
+)
+
+private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary)
+
+fun String.toUpperCamelCase(): String =
+ toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) }
+
+fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT)
+
+fun String.toUpperKebabCase(): String =
+ toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) }
+
+fun String.toLowerKebabCase(): String =
+ toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) }
+
+fun String.toUpperSnakeCase(): String =
+ toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) }
+
+fun String.toLowerSnakeCase(): String =
+ toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) }
diff --git a/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
index cf2cb4ae5eb4..57055f78d03d 100644
--- a/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
+++ b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net.wifi.p2p.servicediscovery;
+package android.net.wifi.p2p.nsd;
parcelable WifiP2pServiceInfo;
diff --git a/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
index d5a1e8f505a9..e4d28bb2d39f 100644
--- a/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
+++ b/wifi/aidl-export/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net.wifi.p2p.servicediscovery;
+package android.net.wifi.p2p.nsd;
parcelable WifiP2pServiceRequest;
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index 3f5c673eeb81..2d48a8325d6e 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -332,6 +332,7 @@ package android.net.wifi {
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isMultiStaConcurrencySupported();
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
@@ -491,6 +492,7 @@ package android.net.wifi {
method @IntRange(from=0) public int getPriority();
method public int getPriorityGroup();
method @Nullable public String getSsid();
+ method public int getSubscriptionId();
method public boolean isAppInteractionRequired();
method public boolean isCredentialSharedWithUser();
method public boolean isEnhancedOpen();
@@ -519,6 +521,7 @@ package android.net.wifi {
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiPassphrase(@NonNull String);
@@ -798,9 +801,15 @@ package android.net.wifi.hotspot2.pps {
method public int describeContents();
method public String getFqdn();
method public String getFriendlyName();
+ method @Nullable public long[] getMatchAllOis();
+ method @Nullable public long[] getMatchAnyOis();
+ method @Nullable public String[] getOtherHomePartners();
method public long[] getRoamingConsortiumOis();
method public void setFqdn(String);
method public void setFriendlyName(String);
+ method public void setMatchAllOis(@Nullable long[]);
+ method public void setMatchAnyOis(@Nullable long[]);
+ method public void setOtherHomePartners(@Nullable String[]);
method public void setRoamingConsortiumOis(long[]);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
diff --git a/wifi/api/system-current.txt b/wifi/api/system-current.txt
index ed5ed82e6adf..98cb849084d4 100644
--- a/wifi/api/system-current.txt
+++ b/wifi/api/system-current.txt
@@ -332,6 +332,7 @@ package android.net.wifi {
field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
+ field @Deprecated public static final int RECENT_FAILURE_MBO_OCE_DISCONNECT = 1001; // 0x3e9
field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0
field @Deprecated public boolean allowAutojoin;
field @Deprecated public int carrierId;
@@ -349,6 +350,7 @@ package android.net.wifi {
field @Deprecated public int numScorerOverrideAndSwitchedNetwork;
field @Deprecated public boolean requirePmf;
field @Deprecated public boolean shared;
+ field @Deprecated public int subscriptionId;
field @Deprecated public boolean useExternalScores;
}
@@ -507,8 +509,8 @@ package android.net.wifi {
field public static final int EASY_CONNECT_NETWORK_ROLE_AP = 1; // 0x1
field public static final int EASY_CONNECT_NETWORK_ROLE_STA = 0; // 0x0
field public static final String EXTRA_CHANGE_REASON = "changeReason";
- field public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
- field public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+ field @Deprecated public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
+ field @Deprecated public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
@@ -516,7 +518,7 @@ package android.net.wifi {
field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
- field public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+ field @Deprecated public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
field public static final String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
field public static final String EXTRA_WIFI_CREDENTIAL_SSID = "ssid";
field public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0; // 0x0
@@ -607,10 +609,12 @@ package android.net.wifi {
public final class WifiNetworkSuggestion implements android.os.Parcelable {
method @NonNull public android.net.wifi.WifiConfiguration getWifiConfiguration();
+ method public boolean isOemPaid();
}
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setOemPaid(boolean);
}
public class WifiScanner {
diff --git a/wifi/api/system-lint-baseline.txt b/wifi/api/system-lint-baseline.txt
index b9fad7f01e51..2ed1c5b4f8ae 100644
--- a/wifi/api/system-lint-baseline.txt
+++ b/wifi/api/system-lint-baseline.txt
@@ -11,11 +11,16 @@ IntentName: android.net.wifi.WifiManager#WIFI_COUNTRY_CODE_CHANGED_ACTION:
Intent action constant name must be ACTION_FOO: WIFI_COUNTRY_CODE_CHANGED_ACTION
MissingGetterMatchingBuilder: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
- android.net.wifi.rtt.RangingRequest does not declare a `getResponders()` method matching method android.net.wifi.rtt.RangingRequest.Builder.addResponder(android.net.wifi.rtt.ResponderConfig)
+
+
MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+MutableBareField: android.net.wifi.WifiConfiguration#subscriptionId:
+ Bare field subscriptionId must be marked final, or moved behind accessors if mutable
+
+
MutableBareField: android.net.wifi.WifiConfiguration#shareThisAp:
Bare field shareThisAp must be marked final, or moved behind accessors if mutable
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index e253ae25659e..eef08b54f570 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -70,7 +70,6 @@ rule android.net.util.NetworkConstants* com.android.wifi.x.@0
rule android.net.util.InterfaceParams* com.android.wifi.x.@0
rule android.net.util.SharedLog* com.android.wifi.x.@0
rule android.net.util.NetUtils* com.android.wifi.x.@0
-rule android.net.util.IpUtils* com.android.wifi.x.@0
rule androidx.annotation.** com.android.wifi.x.@0
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 753869de09e2..0d7d93509faf 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -33,7 +33,6 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -821,10 +820,6 @@ public final class SoftApConfiguration implements Parcelable {
}
} else {
Preconditions.checkStringNotEmpty(passphrase);
- final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
- if (!asciiEncoder.canEncode(passphrase)) {
- throw new IllegalArgumentException("passphrase not ASCII encodable");
- }
if (securityType == SECURITY_TYPE_WPA2_PSK
|| securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index e62cbbcd1eb2..47dd99aa9791 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -35,6 +35,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -902,6 +904,14 @@ public class WifiConfiguration implements Parcelable {
public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
/**
+ * The subscription ID identifies the SIM card for which this network configuration is valid.
+ * See {@link SubscriptionInfo#getSubscriptionId()}
+ * @hide
+ */
+ @SystemApi
+ public int subscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ /**
* @hide
* Auto-join is allowed by user for this network.
* Default true.
@@ -1013,6 +1023,16 @@ public class WifiConfiguration implements Parcelable {
public boolean trusted;
/**
+ * Indicate whether the network is oem paid or not. Networks are considered oem paid
+ * if the corresponding connection is only available to system apps.
+ *
+ * This bit can only be used by suggestion network, see
+ * {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)}
+ * @hide
+ */
+ public boolean oemPaid;
+
+ /**
* True if this Wifi configuration is created from a {@link WifiNetworkSuggestion},
* false otherwise.
*
@@ -1227,12 +1247,18 @@ public class WifiConfiguration implements Parcelable {
/**
* @hide
- * The wall clock time of when |mRandomizedMacAddress| should be re-randomized in aggressive
- * randomization mode.
+ * The wall clock time of when |mRandomizedMacAddress| should be re-randomized in enhanced
+ * MAC randomization mode.
*/
public long randomizedMacExpirationTimeMs = 0;
/**
+ * The wall clock time of when |mRandomizedMacAddress| is last modified.
+ * @hide
+ */
+ public long randomizedMacLastModifiedTimeMs = 0;
+
+ /**
* @hide
* Checks if the given MAC address can be used for Connected Mac Randomization
* by verifying that it is non-null, unicast, locally assigned, and not default mac.
@@ -2104,7 +2130,8 @@ public class WifiConfiguration implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "RECENT_FAILURE_", value = {
RECENT_FAILURE_NONE,
- RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA})
+ RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA,
+ RECENT_FAILURE_MBO_OCE_DISCONNECT})
public @interface RecentFailureReason {}
/**
@@ -2122,6 +2149,13 @@ public class WifiConfiguration implements Parcelable {
public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
/**
+ * This network recently disconnected as a result of MBO/OCE.
+ * @hide
+ */
+ @SystemApi
+ public static final int RECENT_FAILURE_MBO_OCE_DISCONNECT = 1001;
+
+ /**
* Get the failure reason for the most recent connection attempt, or
* {@link #RECENT_FAILURE_NONE} if there was no failure.
*
@@ -2189,6 +2223,7 @@ public class WifiConfiguration implements Parcelable {
ephemeral = false;
osu = false;
trusted = true; // Networks are considered trusted by default.
+ oemPaid = false;
fromWifiNetworkSuggestion = false;
fromWifiNetworkSpecifier = false;
meteredHint = false;
@@ -2313,11 +2348,12 @@ public class WifiConfiguration implements Parcelable {
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
if (this.trusted) sbuf.append(" trusted");
+ if (this.oemPaid) sbuf.append(" oemPaid");
if (this.fromWifiNetworkSuggestion) sbuf.append(" fromWifiNetworkSuggestion");
if (this.fromWifiNetworkSpecifier) sbuf.append(" fromWifiNetworkSpecifier");
if (this.meteredHint) sbuf.append(" meteredHint");
if (this.useExternalScores) sbuf.append(" useExternalScores");
- if (this.validatedInternetAccess || this.ephemeral || this.trusted
+ if (this.validatedInternetAccess || this.ephemeral || this.trusted || this.oemPaid
|| this.fromWifiNetworkSuggestion || this.fromWifiNetworkSpecifier
|| this.meteredHint || this.useExternalScores) {
sbuf.append("\n");
@@ -2330,6 +2366,9 @@ public class WifiConfiguration implements Parcelable {
sbuf.append(" randomizedMacExpirationTimeMs: ")
.append(randomizedMacExpirationTimeMs == 0 ? "<none>"
: logTimeOfDay(randomizedMacExpirationTimeMs)).append("\n");
+ sbuf.append(" randomizedMacLastModifiedTimeMs: ")
+ .append(randomizedMacLastModifiedTimeMs == 0 ? "<none>"
+ : logTimeOfDay(randomizedMacLastModifiedTimeMs)).append("\n");
sbuf.append(" KeyMgmt:");
for (int k = 0; k < this.allowedKeyManagement.size(); k++) {
if (this.allowedKeyManagement.get(k)) {
@@ -2653,6 +2692,25 @@ public class WifiConfiguration implements Parcelable {
return key;
}
+ /**
+ * Get a key for this WifiConfig to generate Persist random Mac Address.
+ * @hide
+ */
+ public String getMacRandomKey() {
+ // Passpoint ephemeral networks have their unique identifier set. Return it as is to be
+ // able to match internally.
+ if (mPasspointUniqueId != null) {
+ return mPasspointUniqueId;
+ }
+
+ String key = getSsidAndSecurityTypeString();
+ if (!shared) {
+ key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier();
+ }
+
+ return key;
+ }
+
/** @hide
* return the SSID + security type in String format.
*/
@@ -2879,6 +2937,7 @@ public class WifiConfiguration implements Parcelable {
ephemeral = source.ephemeral;
osu = source.osu;
trusted = source.trusted;
+ oemPaid = source.oemPaid;
fromWifiNetworkSuggestion = source.fromWifiNetworkSuggestion;
fromWifiNetworkSpecifier = source.fromWifiNetworkSpecifier;
meteredHint = source.meteredHint;
@@ -2910,9 +2969,11 @@ public class WifiConfiguration implements Parcelable {
macRandomizationSetting = source.macRandomizationSetting;
randomizedMacExpirationTimeMs = source.randomizedMacExpirationTimeMs;
+ randomizedMacLastModifiedTimeMs = source.randomizedMacLastModifiedTimeMs;
requirePmf = source.requirePmf;
updateIdentifier = source.updateIdentifier;
carrierId = source.carrierId;
+ subscriptionId = source.subscriptionId;
mPasspointUniqueId = source.mPasspointUniqueId;
}
}
@@ -2962,6 +3023,7 @@ public class WifiConfiguration implements Parcelable {
dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
dest.writeInt(ephemeral ? 1 : 0);
dest.writeInt(trusted ? 1 : 0);
+ dest.writeInt(oemPaid ? 1 : 0);
dest.writeInt(fromWifiNetworkSuggestion ? 1 : 0);
dest.writeInt(fromWifiNetworkSpecifier ? 1 : 0);
dest.writeInt(meteredHint ? 1 : 0);
@@ -2989,8 +3051,10 @@ public class WifiConfiguration implements Parcelable {
dest.writeInt(macRandomizationSetting);
dest.writeInt(osu ? 1 : 0);
dest.writeLong(randomizedMacExpirationTimeMs);
+ dest.writeLong(randomizedMacLastModifiedTimeMs);
dest.writeInt(carrierId);
dest.writeString(mPasspointUniqueId);
+ dest.writeInt(subscriptionId);
}
/** Implement the Parcelable interface {@hide} */
@@ -3041,6 +3105,7 @@ public class WifiConfiguration implements Parcelable {
config.isLegacyPasspointConfig = in.readInt() != 0;
config.ephemeral = in.readInt() != 0;
config.trusted = in.readInt() != 0;
+ config.oemPaid = in.readInt() != 0;
config.fromWifiNetworkSuggestion = in.readInt() != 0;
config.fromWifiNetworkSpecifier = in.readInt() != 0;
config.meteredHint = in.readInt() != 0;
@@ -3068,8 +3133,10 @@ public class WifiConfiguration implements Parcelable {
config.macRandomizationSetting = in.readInt();
config.osu = in.readInt() != 0;
config.randomizedMacExpirationTimeMs = in.readLong();
+ config.randomizedMacLastModifiedTimeMs = in.readLong();
config.carrierId = in.readInt();
config.mPasspointUniqueId = in.readString();
+ config.subscriptionId = in.readInt();
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 9d0eff64cb2e..555401cd3060 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -159,6 +159,11 @@ public class WifiInfo implements Parcelable {
private boolean mTrusted;
/**
+ * Whether the network is oem paid or not.
+ */
+ private boolean mOemPaid;
+
+ /**
* OSU (Online Sign Up) AP for Passpoint R2.
*/
private boolean mOsuAp;
@@ -364,6 +369,7 @@ public class WifiInfo implements Parcelable {
mMeteredHint = source.mMeteredHint;
mEphemeral = source.mEphemeral;
mTrusted = source.mTrusted;
+ mTrusted = source.mOemPaid;
mRequestingPackageName =
source.mRequestingPackageName;
mOsuAp = source.mOsuAp;
@@ -730,6 +736,16 @@ public class WifiInfo implements Parcelable {
}
/** {@hide} */
+ public void setOemPaid(boolean oemPaid) {
+ mOemPaid = oemPaid;
+ }
+
+ /** {@hide} */
+ public boolean isOemPaid() {
+ return mOemPaid;
+ }
+
+ /** {@hide} */
public void setOsuAp(boolean osuAp) {
mOsuAp = osuAp;
}
@@ -988,6 +1004,7 @@ public class WifiInfo implements Parcelable {
dest.writeInt(mMeteredHint ? 1 : 0);
dest.writeInt(mEphemeral ? 1 : 0);
dest.writeInt(mTrusted ? 1 : 0);
+ dest.writeInt(mOemPaid ? 1 : 0);
dest.writeInt(score);
dest.writeLong(txSuccess);
dest.writeDouble(mSuccessfulTxPacketsPerSecond);
@@ -1035,6 +1052,7 @@ public class WifiInfo implements Parcelable {
info.mMeteredHint = in.readInt() != 0;
info.mEphemeral = in.readInt() != 0;
info.mTrusted = in.readInt() != 0;
+ info.mOemPaid = in.readInt() != 0;
info.score = in.readInt();
info.txSuccess = in.readLong();
info.mSuccessfulTxPacketsPerSecond = in.readDouble();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6a7ccf1a8343..56fe5abb28cb 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -44,6 +44,7 @@ import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -981,9 +982,11 @@ public class WifiManager {
* This can be as a result of adding/updating/deleting a network.
* <br />
* {@link #EXTRA_CHANGE_REASON} contains whether the configuration was added/changed/removed.
- * {@link #EXTRA_WIFI_CONFIGURATION} is never set starting in Android 11.
+ * {@link #EXTRA_WIFI_CONFIGURATION} is never set beginning in
+ * {@link android.os.Build.VERSION_CODES#R}.
* {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set for backwards compatibility reasons, but
- * its value is always true, even if only a single network changed.
+ * its value is always true beginning in {@link android.os.Build.VERSION_CODES#R}, even if only
+ * a single network changed.
* <br />
* The {@link android.Manifest.permission#ACCESS_WIFI_STATE ACCESS_WIFI_STATE} permission is
* required to receive this broadcast.
@@ -997,17 +1000,22 @@ public class WifiManager {
* The lookup key for a {@link android.net.wifi.WifiConfiguration} object representing
* the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION}
* broadcast is sent.
- * Note: this extra is never set starting in Android 11.
+ * @deprecated This extra is never set beginning in {@link android.os.Build.VERSION_CODES#R},
+ * regardless of the target SDK version. Use {@link #getConfiguredNetworks} to get the full list
+ * of configured networks.
* @hide
*/
+ @Deprecated
@SystemApi
public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
/**
* Multiple network configurations have changed.
* @see #CONFIGURED_NETWORKS_CHANGED_ACTION
- * Note: this extra is always true starting in Android 11.
+ * @deprecated This extra's value is always true beginning in
+ * {@link android.os.Build.VERSION_CODES#R}, regardless of the target SDK version.
* @hide
*/
+ @Deprecated
@SystemApi
public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
/**
@@ -1103,9 +1111,6 @@ public class WifiManager {
/**
* Broadcast intent action indicating that the link configuration changed on wifi.
- * <br />Included Extras:
- * <br />{@link #EXTRA_LINK_PROPERTIES}: {@link android.net.LinkProperties} object associated
- * with the Wi-Fi network.
* <br /> No permissions are required to listen to this broadcast.
* @hide
*/
@@ -1121,8 +1126,12 @@ public class WifiManager {
* Included in the {@link #ACTION_LINK_CONFIGURATION_CHANGED} broadcast.
*
* Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+ *
+ * @deprecated this extra is no longer populated.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
public static final String EXTRA_LINK_PROPERTIES = "android.net.wifi.extra.LINK_PROPERTIES";
@@ -2628,6 +2637,18 @@ public class WifiManager {
}
/**
+ * Query whether the device supports 2 or more concurrent stations (STA) or not.
+ *
+ * @return true if this device supports multiple STA concurrency, false otherwise.
+ */
+ public boolean isMultiStaConcurrencySupported() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
+ }
+
+ /**
* @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
* with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
* {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2660,14 +2681,6 @@ public class WifiManager {
}
/**
- * @return true if this adapter supports multiple simultaneous connections
- * @hide
- */
- public boolean isAdditionalStaSupported() {
- return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
- }
-
- /**
* @return true if this adapter supports Tunnel Directed Link Setup
*/
public boolean isTdlsSupported() {
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index e992c830aa87..c1f900519fc5 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -24,9 +24,13 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.net.MacAddress;
+import android.net.NetworkCapabilities;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -119,6 +123,12 @@ public final class WifiNetworkSuggestion implements Parcelable {
private int mCarrierId;
/**
+ * The Subscription ID identifies the SIM card for which this network configuration is
+ * valid.
+ */
+ private int mSubscriptionId;
+
+ /**
* Whether this network is shared credential with user to allow user manually connect.
*/
private boolean mIsSharedWithUser;
@@ -150,6 +160,11 @@ public final class WifiNetworkSuggestion implements Parcelable {
private boolean mIsNetworkUntrusted;
/**
+ * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added).
+ */
+ private boolean mIsNetworkOemPaid;
+
+ /**
* Whether this network will use enhanced MAC randomization.
*/
private boolean mIsEnhancedMacRandomizationEnabled;
@@ -175,8 +190,10 @@ public final class WifiNetworkSuggestion implements Parcelable {
mWapiPskPassphrase = null;
mWapiEnterpriseConfig = null;
mIsNetworkUntrusted = false;
+ mIsNetworkOemPaid = false;
mPriorityGroup = 0;
mIsEnhancedMacRandomizationEnabled = false;
+ mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
/**
@@ -345,6 +362,27 @@ public final class WifiNetworkSuggestion implements Parcelable {
}
/**
+ * Set the subscription ID of the SIM card for which this suggestion is targeted.
+ * The suggestion will only apply to that SIM card.
+ * <p>
+ * The subscription ID must belong to a carrier ID which meets either of the following
+ * conditions:
+ * <li>The carrier ID specified by the cross carrier provider, or</li>
+ * <li>The carrier ID which is used to validate the suggesting carrier-privileged app, see
+ * {@link TelephonyManager#hasCarrierPrivileges()}</li>
+ *
+ * @param subId subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setSubscriptionId(int subId) {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ mSubscriptionId = subId;
+ return this;
+ }
+
+ /**
* Set the priority group ID, {@link #setPriority(int)} will only impact the network
* suggestions from the same priority group within the same app.
*
@@ -352,6 +390,9 @@ public final class WifiNetworkSuggestion implements Parcelable {
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setPriorityGroup(int priorityGroup) {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
mPriorityGroup = priorityGroup;
return this;
}
@@ -543,7 +584,7 @@ public final class WifiNetworkSuggestion implements Parcelable {
/**
* Specifies whether the system will bring up the network (if selected) as untrusted. An
- * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
+ * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
* capability removed. The Wi-Fi network selection process may use this information to
* influence priority of the suggested network for Wi-Fi network selection (most likely to
* reduce it). The connectivity service may use this information to influence the overall
@@ -562,6 +603,41 @@ public final class WifiNetworkSuggestion implements Parcelable {
return this;
}
+ /**
+ * Specifies whether the system will bring up the network (if selected) as OEM paid. An
+ * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability
+ * added.
+ * Note:
+ * <li>The connectivity service may use this information to influence the overall
+ * network configuration of the device. This network is typically only available to system
+ * apps.
+ * <li>On devices which support only 1 concurrent connection (indicated via
+ * {@link WifiManager#isMultiStaConcurrencySupported()}, Wi-Fi network selection process may
+ * use this information to influence priority of the suggested network for Wi-Fi network
+ * selection (most likely to reduce it).
+ * <li>On devices which support more than 1 concurrent connections (indicated via
+ * {@link WifiManager#isMultiStaConcurrencySupported()}, these OEM paid networks will be
+ * brought up as a secondary concurrent connection (primary connection will be used
+ * for networks available to the user and all apps.
+ * <p>
+ * <li> An OEM paid network's credentials may not be shared with the user using
+ * {@link #setCredentialSharedWithUser(boolean)}.</li>
+ * <li> If not set, defaults to false (i.e. network is not OEM paid).</li>
+ *
+ * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid
+ * (if true) or not OEM paid (if false).
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull Builder setOemPaid(boolean isOemPaid) {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ mIsNetworkOemPaid = isOemPaid;
+ return this;
+ }
+
private void setSecurityParamsInWifiConfiguration(
@NonNull WifiConfiguration configuration) {
if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
@@ -628,9 +704,11 @@ public final class WifiNetworkSuggestion implements Parcelable {
wifiConfiguration.meteredOverride = mMeteredOverride;
wifiConfiguration.carrierId = mCarrierId;
wifiConfiguration.trusted = !mIsNetworkUntrusted;
+ wifiConfiguration.oemPaid = mIsNetworkOemPaid;
wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
? WifiConfiguration.RANDOMIZATION_ENHANCED
: WifiConfiguration.RANDOMIZATION_PERSISTENT;
+ wifiConfiguration.subscriptionId = mSubscriptionId;
return wifiConfiguration;
}
@@ -659,7 +737,10 @@ public final class WifiNetworkSuggestion implements Parcelable {
wifiConfiguration.priority = mPriority;
wifiConfiguration.meteredOverride = mMeteredOverride;
wifiConfiguration.trusted = !mIsNetworkUntrusted;
+ wifiConfiguration.oemPaid = mIsNetworkOemPaid;
+ wifiConfiguration.subscriptionId = mSubscriptionId;
mPasspointConfiguration.setCarrierId(mCarrierId);
+ mPasspointConfiguration.setSubscriptionId(mSubscriptionId);
mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
wifiConfiguration.macRandomizationSetting = mIsEnhancedMacRandomizationEnabled
? WifiConfiguration.RANDOMIZATION_ENHANCED
@@ -764,7 +845,15 @@ public final class WifiNetworkSuggestion implements Parcelable {
if (mIsSharedWithUserSet && mIsSharedWithUser) {
throw new IllegalStateException("Should not be both"
+ "setCredentialSharedWithUser and +"
- + "setIsNetworkAsUntrusted to true");
+ + "setUntrusted to true");
+ }
+ mIsSharedWithUser = false;
+ }
+ if (mIsNetworkOemPaid) {
+ if (mIsSharedWithUserSet && mIsSharedWithUser) {
+ throw new IllegalStateException("Should not be both"
+ + "setCredentialSharedWithUser and +"
+ + "setOemPaid to true");
}
mIsSharedWithUser = false;
}
@@ -893,7 +982,8 @@ public final class WifiNetworkSuggestion implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
- wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
+ wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey(),
+ wifiConfiguration.subscriptionId, wifiConfiguration.carrierId);
}
/**
@@ -917,7 +1007,9 @@ public final class WifiNetworkSuggestion implements Parcelable {
&& Objects.equals(this.wifiConfiguration.allowedKeyManagement,
lhs.wifiConfiguration.allowedKeyManagement)
&& TextUtils.equals(this.wifiConfiguration.getKey(),
- lhs.wifiConfiguration.getKey());
+ lhs.wifiConfiguration.getKey())
+ && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId
+ && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId;
}
@Override
@@ -931,6 +1023,7 @@ public final class WifiNetworkSuggestion implements Parcelable {
.append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
.append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
.append(", isUnTrusted=").append(!wifiConfiguration.trusted)
+ .append(", isOemPaid=").append(wifiConfiguration.oemPaid)
.append(", priorityGroup=").append(priorityGroup)
.append(" ]");
return sb.toString();
@@ -1026,6 +1119,18 @@ public final class WifiNetworkSuggestion implements Parcelable {
}
/**
+ * @see Builder#setOemPaid(boolean)
+ * @hide
+ */
+ @SystemApi
+ public boolean isOemPaid() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return wifiConfiguration.oemPaid;
+ }
+
+ /**
* Get the WifiEnterpriseConfig, or null if unset.
* @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
* @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
@@ -1057,6 +1162,19 @@ public final class WifiNetworkSuggestion implements Parcelable {
* @see Builder#setPriorityGroup(int)
*/
public int getPriorityGroup() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
return priorityGroup;
}
+
+ /**
+ * @see Builder#setSubscriptionId(int)
+ */
+ public int getSubscriptionId() {
+ if (!SdkLevelUtil.isAtLeastS()) {
+ throw new UnsupportedOperationException();
+ }
+ return wifiConfiguration.subscriptionId;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 61a6e16f9c43..357c5bcfa265 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -30,6 +30,8 @@ import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -412,6 +414,12 @@ public final class PasspointConfiguration implements Parcelable {
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
/**
+ * The subscription ID identifies the SIM card who provides this network configuration.
+ * See {@link SubscriptionInfo#getSubscriptionId()}
+ */
+ private int mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ /**
* Set the carrier ID associated with current configuration.
* @param carrierId {@code mCarrierId}
* @hide
@@ -430,6 +438,24 @@ public final class PasspointConfiguration implements Parcelable {
}
/**
+ * Set the subscription ID associated with current configuration.
+ * @param subscriptionId {@code mSubscriptionId}
+ * @hide
+ */
+ public void setSubscriptionId(int subscriptionId) {
+ this.mSubscriptionId = subscriptionId;
+ }
+
+ /**
+ * Get the carrier ID associated with current configuration.
+ * @return {@code mSubscriptionId}
+ * @hide
+ */
+ public int getSubscriptionId() {
+ return mSubscriptionId;
+ }
+
+ /**
* The auto-join configuration specifies whether or not the Passpoint Configuration is
* considered for auto-connection. If true then yes, if false then it isn't considered as part
* of auto-connection - but can still be manually connected to.
@@ -604,6 +630,7 @@ public final class PasspointConfiguration implements Parcelable {
mServiceFriendlyNames = source.mServiceFriendlyNames;
mAaaServerTrustedNames = source.mAaaServerTrustedNames;
mCarrierId = source.mCarrierId;
+ mSubscriptionId = source.mSubscriptionId;
mIsAutojoinEnabled = source.mIsAutojoinEnabled;
mIsMacRandomizationEnabled = source.mIsMacRandomizationEnabled;
mIsEnhancedMacRandomizationEnabled = source.mIsEnhancedMacRandomizationEnabled;
@@ -641,6 +668,7 @@ public final class PasspointConfiguration implements Parcelable {
dest.writeBoolean(mIsMacRandomizationEnabled);
dest.writeBoolean(mIsEnhancedMacRandomizationEnabled);
dest.writeInt(mMeteredOverride);
+ dest.writeInt(mSubscriptionId);
}
@Override
@@ -671,6 +699,7 @@ public final class PasspointConfiguration implements Parcelable {
&& mUsageLimitDataLimit == that.mUsageLimitDataLimit
&& mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
&& mCarrierId == that.mCarrierId
+ && mSubscriptionId == that.mSubscriptionId
&& mIsAutojoinEnabled == that.mIsAutojoinEnabled
&& mIsMacRandomizationEnabled == that.mIsMacRandomizationEnabled
&& mIsEnhancedMacRandomizationEnabled == that.mIsEnhancedMacRandomizationEnabled
@@ -686,7 +715,7 @@ public final class PasspointConfiguration implements Parcelable {
mSubscriptionExpirationTimeMillis, mUsageLimitUsageTimePeriodInMinutes,
mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
mServiceFriendlyNames, mCarrierId, mIsAutojoinEnabled, mIsMacRandomizationEnabled,
- mIsEnhancedMacRandomizationEnabled, mMeteredOverride);
+ mIsEnhancedMacRandomizationEnabled, mMeteredOverride, mSubscriptionId);
}
@Override
@@ -740,6 +769,7 @@ public final class PasspointConfiguration implements Parcelable {
builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
}
builder.append("CarrierId:" + mCarrierId);
+ builder.append("SubscriptionId:" + mSubscriptionId);
builder.append("IsAutojoinEnabled:" + mIsAutojoinEnabled);
builder.append("mIsMacRandomizationEnabled:" + mIsMacRandomizationEnabled);
builder.append("mIsEnhancedMacRandomizationEnabled:" + mIsEnhancedMacRandomizationEnabled);
@@ -853,6 +883,7 @@ public final class PasspointConfiguration implements Parcelable {
config.mIsMacRandomizationEnabled = in.readBoolean();
config.mIsEnhancedMacRandomizationEnabled = in.readBoolean();
config.mMeteredOverride = in.readInt();
+ config.mSubscriptionId = in.readInt();
return config;
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
index 8f34579f6a5d..35a8ff6095e0 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -16,6 +16,7 @@
package android.net.wifi.hotspot2.pps;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -139,16 +140,26 @@ public final class HomeSp implements Parcelable {
* (MO) tree for more detail.
*/
private long[] mMatchAllOis = null;
+
/**
- * @hide
+ * Set a list of HomeOIs such that all OIs in the list must match an OI in the Roaming
+ * Consortium advertised by a hotspot operator. The list set by this API will have precedence
+ * over {@link #setMatchAnyOis(long[])}, meaning the list set in {@link #setMatchAnyOis(long[])}
+ * will only be used for matching if the list set by this API is null or empty.
+ *
+ * @param matchAllOis An array of longs containing the HomeOIs
*/
- public void setMatchAllOis(long[] matchAllOis) {
+ public void setMatchAllOis(@Nullable long[] matchAllOis) {
mMatchAllOis = matchAllOis;
}
+
/**
- * @hide
+ * Get the list of HomeOIs such that all OIs in the list must match an OI in the Roaming
+ * Consortium advertised by a hotspot operator.
+ *
+ * @return An array of longs containing the HomeOIs
*/
- public long[] getMatchAllOis() {
+ public @Nullable long[] getMatchAllOis() {
return mMatchAllOis;
}
@@ -159,23 +170,34 @@ public final class HomeSp implements Parcelable {
* of that Hotspot provider (e.g. successful authentication with such Hotspot
* is possible).
*
- * {@link #mMatchAllOIs} will have precedence over this one, meaning this list will
- * only be used for matching if {@link #mMatchAllOIs} is null or empty.
+ * The list set by {@link #setMatchAllOis(long[])} will have precedence over this one, meaning
+ * this list will only be used for matching if the list set by {@link #setMatchAllOis(long[])}
+ * is null or empty.
*
* Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
* (MO) tree for more detail.
*/
private long[] mMatchAnyOis = null;
+
/**
- * @hide
+ * Set a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
+ * advertised by a hotspot operator. The list set by {@link #setMatchAllOis(long[])}
+ * will have precedence over this API, meaning this list will only be used for matching if the
+ * list set by {@link #setMatchAllOis(long[])} is null or empty.
+ *
+ * @param matchAnyOis An array of longs containing the HomeOIs
*/
- public void setMatchAnyOis(long[] matchAnyOis) {
+ public void setMatchAnyOis(@Nullable long[] matchAnyOis) {
mMatchAnyOis = matchAnyOis;
}
+
/**
- * @hide
+ * Get a list of HomeOIs such that any OI in the list matches an OI in the Roaming Consortium
+ * advertised by a hotspot operator.
+ *
+ * @return An array of longs containing the HomeOIs
*/
- public long[] getMatchAnyOis() {
+ public @Nullable long[] getMatchAnyOis() {
return mMatchAnyOis;
}
@@ -186,16 +208,25 @@ public final class HomeSp implements Parcelable {
* operator merges between the providers.
*/
private String[] mOtherHomePartners = null;
+
/**
- * @hide
+ * Set the list of FQDN (Fully Qualified Domain Name) of other Home partner providers.
+ *
+ * @param otherHomePartners Array of Strings containing the FQDNs of other Home partner
+ * providers
*/
- public void setOtherHomePartners(String[] otherHomePartners) {
+ public void setOtherHomePartners(@Nullable String[] otherHomePartners) {
mOtherHomePartners = otherHomePartners;
}
+
/**
- * @hide
+ * Get the list of FQDN (Fully Qualified Domain Name) of other Home partner providers set in
+ * the profile.
+ *
+ * @return Array of Strings containing the FQDNs of other Home partner providers set in the
+ * profile
*/
- public String[] getOtherHomePartners() {
+ public @Nullable String[] getOtherHomePartners() {
return mOtherHomePartners;
}
diff --git a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
index 76585282d483..0b0862a7a277 100644
--- a/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -153,15 +153,23 @@ public class WifiNl80211Manager {
@Override
public void OnScanResultReady() {
Log.d(TAG, "Scan result ready event");
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onScanResultReady());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onScanResultReady());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void OnScanFailed() {
Log.d(TAG, "Scan failed event");
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onScanFailed());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onScanFailed());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
}
@@ -345,15 +353,23 @@ public class WifiNl80211Manager {
@Override
public void OnPnoNetworkFound() {
Log.d(TAG, "Pno scan result event");
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onScanResultReady());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onScanResultReady());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void OnPnoScanFailed() {
Log.d(TAG, "Pno Scan failed event");
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onScanFailed());
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onScanFailed());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
}
@@ -376,9 +392,13 @@ public class WifiNl80211Manager {
@Override
public void onSoftApChannelSwitched(int frequency, int bandwidth) {
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency,
- toFrameworkBandwidth(bandwidth)));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency,
+ toFrameworkBandwidth(bandwidth)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) {
@@ -431,8 +451,12 @@ public class WifiNl80211Manager {
if (mVerboseLoggingEnabled) {
Log.e(TAG, "Timed out waiting for ACK");
}
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
});
mWasCalled = false;
@@ -447,8 +471,12 @@ public class WifiNl80211Manager {
// post to main thread
mEventHandler.post(() -> runIfFirstCall(() -> {
mAlarmManager.cancel(mTimeoutCallback);
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}));
}
@@ -458,8 +486,12 @@ public class WifiNl80211Manager {
// post to main thread
mEventHandler.post(() -> runIfFirstCall(() -> {
mAlarmManager.cancel(mTimeoutCallback);
- Binder.clearCallingIdentity();
- mExecutor.execute(() -> mCallback.onFailure(reason));
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onFailure(reason));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}));
}
}
diff --git a/wifi/java/android/net/wifi/util/SdkLevelUtil.java b/wifi/java/android/net/wifi/util/SdkLevelUtil.java
index 042634c7125c..d08d4fd742b7 100644
--- a/wifi/java/android/net/wifi/util/SdkLevelUtil.java
+++ b/wifi/java/android/net/wifi/util/SdkLevelUtil.java
@@ -23,17 +23,17 @@ import android.os.Build;
*
* This can be used to disable new Wifi APIs added in Mainline updates on older SDK versions.
*
+ * Note: if certain functionality is gated with SdkLevelUtil, its corresponding unit tests should
+ * also be gated by the same condition. Then, those unit tests will only be exercised on a base
+ * system image satisfying that condition.
+ * Alternatively, it can be tested via static mocking.
+ *
* @hide
*/
public class SdkLevelUtil {
- /** This class is instantiable to allow easy mocking. */
- public SdkLevelUtil() { }
-
- /** See {@link #isAtLeastS()}. This version is non-static to allow easy mocking. */
- public boolean isAtLeastSMockable() {
- return isAtLeastS();
- }
+ /** This class is not instantiable. */
+ private SdkLevelUtil() {}
/** Returns true if the Android platform SDK is at least "S", false otherwise. */
public static boolean isAtLeastS() {
diff --git a/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java b/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java
index b9c640cc3980..3c935058787b 100644
--- a/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java
@@ -16,10 +16,11 @@
package android.net.wifi;
-import android.os.Build;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import androidx.test.filters.SmallTest;
@@ -92,14 +93,12 @@ public class SoftApCapabilityTest {
@Test(expected = IllegalArgumentException.class)
public void testGetSupportedChannelListWithInvalidBand() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
| SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD;
SoftApCapability capability = new SoftApCapability(testSoftApFeature);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
- capability.getSupportedChannelList(
- SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
- } else {
- throw new IllegalArgumentException("API doesn't support in current SDK version");
- }
+ capability.getSupportedChannelList(
+ SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ);
}
}
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 254434b81f8f..7877ea11ca1f 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNull;
import android.net.MacAddress;
-import android.os.Build;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -81,18 +81,18 @@ public class SoftApConfigurationTest {
assertThat(original.getChannel()).isEqualTo(0);
assertThat(original.isHiddenSsid()).isEqualTo(false);
assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+ if (SdkLevelUtil.isAtLeastS()) {
assertThat(original.getMacRandomizationSetting())
.isEqualTo(SoftApConfiguration.RANDOMIZATION_PERSISTENT);
}
SoftApConfiguration unparceled = parcelUnparcel(original);
- assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isNotSameInstanceAs(original);
assertThat(unparceled).isEqualTo(original);
assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
- assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isNotSameInstanceAs(original);
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
@@ -111,12 +111,12 @@ public class SoftApConfigurationTest {
assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
SoftApConfiguration unparceled = parcelUnparcel(original);
- assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isNotSameInstanceAs(original);
assertThat(unparceled).isEqualTo(original);
assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
- assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isNotSameInstanceAs(original);
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
@@ -137,7 +137,7 @@ public class SoftApConfigurationTest {
.setClientControlByUserEnabled(true)
.setBlockedClientList(testBlockedClientList)
.setAllowedClientList(testAllowedClientList);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+ if (SdkLevelUtil.isAtLeastS()) {
originalBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
}
SoftApConfiguration original = originalBuilder.build();
@@ -153,18 +153,18 @@ public class SoftApConfigurationTest {
assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
+ if (SdkLevelUtil.isAtLeastS()) {
assertThat(original.getMacRandomizationSetting())
.isEqualTo(SoftApConfiguration.RANDOMIZATION_NONE);
}
SoftApConfiguration unparceled = parcelUnparcel(original);
- assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isNotSameInstanceAs(original);
assertThat(unparceled).isEqualTo(original);
assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
- assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isNotSameInstanceAs(original);
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
@@ -185,12 +185,12 @@ public class SoftApConfigurationTest {
SoftApConfiguration unparceled = parcelUnparcel(original);
- assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isNotSameInstanceAs(original);
assertThat(unparceled).isEqualTo(original);
assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
- assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isNotSameInstanceAs(original);
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
@@ -212,12 +212,12 @@ public class SoftApConfigurationTest {
SoftApConfiguration unparceled = parcelUnparcel(original);
- assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isNotSameInstanceAs(original);
assertThat(unparceled).isEqualTo(original);
assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
- assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isNotSameInstanceAs(original);
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 62220a6237b1..b82c67b658aa 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -48,6 +48,7 @@ import org.junit.Test;
*/
@SmallTest
public class WifiConfigurationTest {
+ private static final String TEST_PASSPOINT_UNIQUE_ID = "uniqueId";
@Before
public void setUp() {
@@ -67,11 +68,14 @@ public class WifiConfigurationTest {
WifiConfiguration config = new WifiConfiguration();
config.setPasspointManagementObjectTree(cookie);
config.trusted = false;
+ config.oemPaid = true;
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
MacAddress macBeforeParcel = config.getRandomizedMacAddress();
+ config.subscriptionId = 1;
+ config.carrierId = 1189;
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
byte[] bytes = parcelW.marshall();
@@ -388,6 +392,79 @@ public class WifiConfigurationTest {
}
/**
+ * Verifies that getMacRandomKey returns the correct String for networks of
+ * various different security types, the result should be stable.
+ */
+ @Test
+ public void testGetMacRandomKeyString() {
+ WifiConfiguration config = new WifiConfiguration();
+ final String mSsid = "TestAP";
+ config.SSID = mSsid;
+
+ // Test various combinations
+ config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_PSK],
+ config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WPA_EAP],
+ config.getMacRandomKey());
+
+ config.wepKeys[0] = "TestWep";
+ config.allowedKeyManagement.clear();
+ assertEquals(mSsid + "WEP", config.getMacRandomKey());
+
+ // set WEP key and give a valid index.
+ config.wepKeys[0] = null;
+ config.wepKeys[2] = "TestWep";
+ config.wepTxKeyIndex = 2;
+ config.allowedKeyManagement.clear();
+ assertEquals(mSsid + "WEP", config.getMacRandomKey());
+
+ // set WEP key but does not give a valid index.
+ config.wepKeys[0] = null;
+ config.wepKeys[2] = "TestWep";
+ config.wepTxKeyIndex = 0;
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.OWE);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getMacRandomKey());
+
+ config.wepKeys[0] = null;
+ config.wepTxKeyIndex = 0;
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.OWE);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.SAE);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SAE], config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.SUITE_B_192);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.SUITE_B_192],
+ config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.NONE);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.NONE], config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.WAPI_PSK);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_PSK],
+ config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.WAPI_CERT);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.WAPI_CERT],
+ config.getMacRandomKey());
+
+ config.allowedKeyManagement.clear();
+ config.setPasspointUniqueId(TEST_PASSPOINT_UNIQUE_ID);
+ assertEquals(TEST_PASSPOINT_UNIQUE_ID, config.getMacRandomKey());
+ }
+
+ /**
* Ensure that the {@link NetworkSelectionStatus.DisableReasonInfo}s are populated in
* {@link NetworkSelectionStatus#DISABLE_REASON_INFOS} for reason codes from 0 to
* {@link NetworkSelectionStatus#NETWORK_SELECTION_DISABLED_MAX} - 1.
diff --git a/wifi/tests/src/android/net/wifi/WifiInfoTest.java b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
index 311bbc41b8fe..06ae13a21e38 100644
--- a/wifi/tests/src/android/net/wifi/WifiInfoTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiInfoTest.java
@@ -61,6 +61,7 @@ public class WifiInfoTest {
writeWifiInfo.txBad = TEST_TX_BAD;
writeWifiInfo.rxSuccess = TEST_RX_SUCCESS;
writeWifiInfo.setTrusted(true);
+ writeWifiInfo.setOemPaid(true);
writeWifiInfo.setOsuAp(true);
writeWifiInfo.setFQDN(TEST_FQDN);
writeWifiInfo.setProviderFriendlyName(TEST_PROVIDER_NAME);
@@ -81,6 +82,7 @@ public class WifiInfoTest {
assertEquals(TEST_TX_BAD, readWifiInfo.txBad);
assertEquals(TEST_RX_SUCCESS, readWifiInfo.rxSuccess);
assertTrue(readWifiInfo.isTrusted());
+ assertTrue(readWifiInfo.isOemPaid());
assertTrue(readWifiInfo.isOsuAp());
assertTrue(readWifiInfo.isPasspointAp());
assertEquals(TEST_PACKAGE_NAME, readWifiInfo.getRequestingPackageName());
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index e7f1916c9e82..bda776db733c 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -33,6 +33,8 @@ import static android.net.wifi.WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_ADDITIONAL_STA;
+import static android.net.wifi.WifiManager.WIFI_FEATURE_AP_STA;
import static android.net.wifi.WifiManager.WIFI_FEATURE_DPP;
import static android.net.wifi.WifiManager.WIFI_FEATURE_OWE;
import static android.net.wifi.WifiManager.WIFI_FEATURE_P2P;
@@ -49,6 +51,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
@@ -83,6 +86,7 @@ import android.net.wifi.WifiManager.SoftApCallback;
import android.net.wifi.WifiManager.SuggestionConnectionStatusListener;
import android.net.wifi.WifiManager.TrafficStateCallback;
import android.net.wifi.WifiManager.WifiConnectedNetworkScorer;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -1708,6 +1712,34 @@ public class WifiManagerTest {
}
/**
+ * Test behavior of isStaApConcurrencySupported
+ */
+ @Test
+ public void testIsStaApConcurrencyOpenSupported() throws Exception {
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(WIFI_FEATURE_AP_STA));
+ assertTrue(mWifiManager.isStaApConcurrencySupported());
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(~WIFI_FEATURE_AP_STA));
+ assertFalse(mWifiManager.isStaApConcurrencySupported());
+ }
+
+ /**
+ * Test behavior of isMultiStaConcurrencySupported
+ */
+ @Test
+ public void testIsMultiStaConcurrencyOpenSupported() throws Exception {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(WIFI_FEATURE_ADDITIONAL_STA));
+ assertTrue(mWifiManager.isMultiStaConcurrencySupported());
+ when(mWifiService.getSupportedFeatures())
+ .thenReturn(new Long(~WIFI_FEATURE_ADDITIONAL_STA));
+ assertFalse(mWifiManager.isMultiStaConcurrencySupported());
+ }
+
+ /**
* Test behavior of {@link WifiManager#addNetwork(WifiConfiguration)}
*/
@Test
@@ -1858,7 +1890,6 @@ public class WifiManagerTest {
assertFalse(mWifiManager.isDeviceToDeviceRttSupported());
assertFalse(mWifiManager.isDeviceToApRttSupported());
assertFalse(mWifiManager.isPreferredNetworkOffloadSupported());
- assertFalse(mWifiManager.isAdditionalStaSupported());
assertFalse(mWifiManager.isTdlsSupported());
assertFalse(mWifiManager.isOffChannelTdlsSupported());
assertFalse(mWifiManager.isEnhancedPowerReportingSupported());
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 668d2385a4ae..3f9ce5b675ff 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -22,10 +22,12 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.PasspointTestUtils;
+import android.net.wifi.util.SdkLevelUtil;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -192,6 +194,34 @@ public class WifiNetworkSuggestionTest {
/**
* Validate correctness of WifiNetworkSuggestion object created by
+ * {@link WifiNetworkSuggestion.Builder#build()} for OWE network.
+ */
+ @Test
+ public void testWifiNetworkSuggestionBuilderForOemPaidEnhancedOpenNetworkWithBssid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setBssid(MacAddress.fromString(TEST_BSSID))
+ .setOemPaid(true)
+ .setIsEnhancedOpen(true)
+ .build();
+
+ assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
+ assertEquals(TEST_BSSID, suggestion.wifiConfiguration.BSSID);
+ assertTrue(suggestion.wifiConfiguration.allowedKeyManagement
+ .get(WifiConfiguration.KeyMgmt.OWE));
+ assertNull(suggestion.wifiConfiguration.preSharedKey);
+ assertTrue(suggestion.wifiConfiguration.requirePmf);
+ assertTrue(suggestion.wifiConfiguration.oemPaid);
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
+ assertNull(suggestion.getEnterpriseConfig());
+ }
+
+ /**
+ * Validate correctness of WifiNetworkSuggestion object created by
* {@link WifiNetworkSuggestion.Builder#build()} for SAE network.
*/
@Test
@@ -734,6 +764,32 @@ public class WifiNetworkSuggestionTest {
}
/**
+ * Verify that the builder creates the appropriate SIM credential suggestion with SubId, also
+ * verify {@link WifiNetworkSuggestion#equals(Object)} consider suggestion with different SubId
+ * as different suggestions.
+ */
+ @Test
+ public void testSimCredentialNetworkWithSubId() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+ WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+ enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+ enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.NONE);
+ WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2EnterpriseConfig(enterpriseConfig)
+ .setSubscriptionId(1)
+ .build();
+ assertEquals(1, suggestion1.getSubscriptionId());
+ WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2EnterpriseConfig(enterpriseConfig)
+ .setSubscriptionId(2)
+ .build();
+ assertEquals(2, suggestion2.getSubscriptionId());
+ assertNotEquals(suggestion1, suggestion2);
+ }
+
+ /**
* Check that parcel marshalling/unmarshalling works
*/
@Test
@@ -1010,6 +1066,41 @@ public class WifiNetworkSuggestionTest {
}
/**
+ * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+ * correct value to the WifiConfiguration.
+ */
+ @Test
+ public void testSetIsNetworkAsOemPaid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setOemPaid(true)
+ .build();
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ }
+
+ /**
+ * Validate {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} set the
+ * correct value to the WifiConfiguration.
+ * Also the {@link WifiNetworkSuggestion#isUserAllowedToManuallyConnect} should be false;
+ */
+ @Test
+ public void testSetIsNetworkAsOemPaidOnPasspointNetwork() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ PasspointConfiguration passpointConfiguration = PasspointTestUtils.createConfig();
+ WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ .setPasspointConfig(passpointConfiguration)
+ .setOemPaid(true)
+ .build();
+ assertTrue(suggestion.isOemPaid());
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ }
+
+ /**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
* when set {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} to true and
* set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
@@ -1027,6 +1118,24 @@ public class WifiNetworkSuggestionTest {
/**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when set {@link WifiNetworkSuggestion.Builder#setOemPaid(boolean)} to true and
+ * set {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to true
+ * together.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetCredentialSharedWithUserWithSetIsNetworkAsOemPaid() {
+ assumeTrue(SdkLevelUtil.isAtLeastS());
+
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setCredentialSharedWithUser(true)
+ .setOemPaid(true)
+ .build();
+ }
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
* when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutojoinEnabled(boolean)}
* and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
* to false on a passpoint suggestion.
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
index 8d55acb87f15..5830a1e84e13 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointTestUtils.java
@@ -30,6 +30,7 @@ import java.util.Map;
public class PasspointTestUtils {
private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
+ private static final int TEST_SUB_ID = 1;
/**
* Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -156,6 +157,7 @@ public class PasspointTestUtils {
friendlyNames.put("en", "ServiceName1");
friendlyNames.put("kr", "ServiceName2");
config.setServiceFriendlyNames(friendlyNames);
+ config.setSubscriptionId(TEST_SUB_ID);
return config;
}